diff --git a/.gitignore b/.gitignore index 81ff83462..f01b25411 100644 --- a/.gitignore +++ b/.gitignore @@ -138,3 +138,4 @@ Tests/Telerik.Sitefinity.Frontend.ClientTest/templates.js Tests/Telerik.Sitefinity.Frontend.ClientTest/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache Feather.sln.GhostDoc.xml /.vs/config/applicationhost.config +*.aiis diff --git a/.nuget/NuGet.Config b/.nuget/NuGet.Config deleted file mode 100644 index 67f8ea046..000000000 --- a/.nuget/NuGet.Config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.nuget/NuGet.targets b/.nuget/NuGet.targets deleted file mode 100644 index 45769361d..000000000 --- a/.nuget/NuGet.targets +++ /dev/null @@ -1,148 +0,0 @@ - - - - $(MSBuildProjectDirectory)\..\ - - - false - - - false - - - true - - - true - - - - - - - - - - - - - - - - - - $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) - - - - - $(SolutionDir).nuget - - - - $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config - $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config - - - - $(MSBuildProjectDirectory)\packages.config - $(PackagesProjectConfig) - - - - - $(NuGetToolsPath)\NuGet.exe - @(PackageSource) - - "$(NuGetExePath)" - mono --runtime=v4.0.30319 $(NuGetExePath) - - $(TargetDir.Trim('\\')) - - -RequireConsent - -NonInteractive - - "$(SolutionDir) " - "$(SolutionDir)" - - - $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir) -NoCache - $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols - - - - RestorePackages; - $(BuildDependsOn); - - - - - $(BuildDependsOn); - BuildPackage; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/AssemblyInfoShare/SharedAssemblyInfo.cs b/AssemblyInfoShare/SharedAssemblyInfo.cs deleted file mode 100644 index f7af0e319..000000000 --- a/AssemblyInfoShare/SharedAssemblyInfo.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyCompany("Telerik")] -[assembly: AssemblyProduct("Telerik Sitefinity CMS")] -[assembly: AssemblyCopyright("Copyright © 2005-2016 Telerik AD")] -[assembly: AssemblyTrademark("Sitefinity")] - -// 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)] - -// 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 Build and Revision Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("1.6.540.0")] -[assembly: AssemblyFileVersion("1.6.540.0")] diff --git a/Build/FeatherPrecompilation.targets b/Build/FeatherPrecompilation.targets index d70d1fc7f..79e1bc33d 100644 --- a/Build/FeatherPrecompilation.targets +++ b/Build/FeatherPrecompilation.targets @@ -69,7 +69,7 @@ + Outputs="%(FeatherRazorSrcFiles.PackageName).csFilesGenerated" Condition="'$(Language)'!='VB'"> + + net48 + false + 0 + + + + + true + 3 + + \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 000000000..e25482532 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,17 @@ + + + true + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Feather.sln b/Feather.sln index 7b08aa848..5bde174c4 100644 --- a/Feather.sln +++ b/Feather.sln @@ -1,99 +1,31 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telerik.Sitefinity.Frontend", "Telerik.Sitefinity.Frontend\Telerik.Sitefinity.Frontend.csproj", "{17ED04C3-0AB9-4055-9908-6C70720DAF77}" +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33205.214 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Telerik.Sitefinity.Frontend", "Telerik.Sitefinity.Frontend\Telerik.Sitefinity.Frontend.csproj", "{17ED04C3-0AB9-4055-9908-6C70720DAF77}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E3E1F3A5-2C9C-4223-ADBC-86904842C274}" - ProjectSection(SolutionItems) = preProject - Feather.ruleset = Feather.ruleset - Feather.vsmdi = Feather.vsmdi - Tests\TestSettings.testsettings = Tests\TestSettings.testsettings - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{F9885515-25E7-4C15-AE1B-032098DE1A90}" - ProjectSection(SolutionItems) = preProject - .nuget\NuGet.Config = .nuget\NuGet.Config - .nuget\NuGet.exe = .nuget\NuGet.exe - .nuget\NuGet.targets = .nuget\NuGet.targets - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UITests", "UITests", "{EFF407A4-62ED-4B78-9573-7F65D70482C6}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{D7E812B2-E16E-4B90-9EF8-1C5F9F907E96}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telerik.Sitefinity.Frontend.ClientTest", "Tests\Telerik.Sitefinity.Frontend.ClientTest\Telerik.Sitefinity.Frontend.ClientTest.csproj", "{889A8B70-2B62-472A-AB7C-33CA9A7F48A0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telerik.Sitefinity.Frontend.TestIntegration", "Tests\Telerik.Sitefinity.Frontend.TestIntegration\Telerik.Sitefinity.Frontend.TestIntegration.csproj", "{49B06F58-0510-4279-B6F1-90F23AB3768D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telerik.Sitefinity.Frontend.TestUtilities", "Tests\Telerik.Sitefinity.Frontend.TestUtilities\Telerik.Sitefinity.Frontend.TestUtilities.csproj", "{1C663AB5-AEBC-41B5-A861-F635C37DAB47}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telerik.Sitefinity.Frontend.TestUI.Arrangements", "Tests\Telerik.Sitefinity.Frontend.TestUI.Arrangements\Telerik.Sitefinity.Frontend.TestUI.Arrangements.csproj", "{637A9A4D-A6D1-4C84-9A00-58AA440C2D58}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telerik.Sitefinity.Frontend.TestUnit", "Tests\Telerik.Sitefinity.Frontend.TestUnit\Telerik.Sitefinity.Frontend.TestUnit.csproj", "{D607959E-5DE3-43B9-8731-C22E495FCEF7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telerik.Sitefinity.Frontend.Data", "Telerik.Sitefinity.Frontend.Data\Telerik.Sitefinity.Frontend.Data.csproj", "{A9C3B088-8C44-4F0A-93E8-82FDB99A4205}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Telerik.Sitefinity.Frontend.Data", "Telerik.Sitefinity.Frontend.Data\Telerik.Sitefinity.Frontend.Data.csproj", "{A9C3B088-8C44-4F0A-93E8-82FDB99A4205}" EndProject Global - GlobalSection(TestCaseManagementSettings) = postSolution - CategoryFile = Feather.vsmdi - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - Release Pro|Any CPU = Release Pro|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {17ED04C3-0AB9-4055-9908-6C70720DAF77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {17ED04C3-0AB9-4055-9908-6C70720DAF77}.Debug|Any CPU.Build.0 = Debug|Any CPU - {17ED04C3-0AB9-4055-9908-6C70720DAF77}.Release Pro|Any CPU.ActiveCfg = Release|Any CPU - {17ED04C3-0AB9-4055-9908-6C70720DAF77}.Release Pro|Any CPU.Build.0 = Release|Any CPU {17ED04C3-0AB9-4055-9908-6C70720DAF77}.Release|Any CPU.ActiveCfg = Release|Any CPU {17ED04C3-0AB9-4055-9908-6C70720DAF77}.Release|Any CPU.Build.0 = Release|Any CPU - {889A8B70-2B62-472A-AB7C-33CA9A7F48A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {889A8B70-2B62-472A-AB7C-33CA9A7F48A0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {889A8B70-2B62-472A-AB7C-33CA9A7F48A0}.Release Pro|Any CPU.ActiveCfg = Release|Any CPU - {889A8B70-2B62-472A-AB7C-33CA9A7F48A0}.Release Pro|Any CPU.Build.0 = Release|Any CPU - {889A8B70-2B62-472A-AB7C-33CA9A7F48A0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {889A8B70-2B62-472A-AB7C-33CA9A7F48A0}.Release|Any CPU.Build.0 = Release|Any CPU - {49B06F58-0510-4279-B6F1-90F23AB3768D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {49B06F58-0510-4279-B6F1-90F23AB3768D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {49B06F58-0510-4279-B6F1-90F23AB3768D}.Release Pro|Any CPU.ActiveCfg = Release|Any CPU - {49B06F58-0510-4279-B6F1-90F23AB3768D}.Release Pro|Any CPU.Build.0 = Release|Any CPU - {49B06F58-0510-4279-B6F1-90F23AB3768D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {49B06F58-0510-4279-B6F1-90F23AB3768D}.Release|Any CPU.Build.0 = Release|Any CPU - {1C663AB5-AEBC-41B5-A861-F635C37DAB47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1C663AB5-AEBC-41B5-A861-F635C37DAB47}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1C663AB5-AEBC-41B5-A861-F635C37DAB47}.Release Pro|Any CPU.ActiveCfg = Release|Any CPU - {1C663AB5-AEBC-41B5-A861-F635C37DAB47}.Release Pro|Any CPU.Build.0 = Release|Any CPU - {1C663AB5-AEBC-41B5-A861-F635C37DAB47}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1C663AB5-AEBC-41B5-A861-F635C37DAB47}.Release|Any CPU.Build.0 = Release|Any CPU - {637A9A4D-A6D1-4C84-9A00-58AA440C2D58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {637A9A4D-A6D1-4C84-9A00-58AA440C2D58}.Debug|Any CPU.Build.0 = Debug|Any CPU - {637A9A4D-A6D1-4C84-9A00-58AA440C2D58}.Release Pro|Any CPU.ActiveCfg = Release|Any CPU - {637A9A4D-A6D1-4C84-9A00-58AA440C2D58}.Release Pro|Any CPU.Build.0 = Release|Any CPU - {637A9A4D-A6D1-4C84-9A00-58AA440C2D58}.Release|Any CPU.ActiveCfg = Release|Any CPU - {637A9A4D-A6D1-4C84-9A00-58AA440C2D58}.Release|Any CPU.Build.0 = Release|Any CPU - {D607959E-5DE3-43B9-8731-C22E495FCEF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D607959E-5DE3-43B9-8731-C22E495FCEF7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D607959E-5DE3-43B9-8731-C22E495FCEF7}.Release Pro|Any CPU.ActiveCfg = Release|Any CPU - {D607959E-5DE3-43B9-8731-C22E495FCEF7}.Release Pro|Any CPU.Build.0 = Release|Any CPU - {D607959E-5DE3-43B9-8731-C22E495FCEF7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D607959E-5DE3-43B9-8731-C22E495FCEF7}.Release|Any CPU.Build.0 = Release|Any CPU {A9C3B088-8C44-4F0A-93E8-82FDB99A4205}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A9C3B088-8C44-4F0A-93E8-82FDB99A4205}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A9C3B088-8C44-4F0A-93E8-82FDB99A4205}.Release Pro|Any CPU.ActiveCfg = Release|Any CPU - {A9C3B088-8C44-4F0A-93E8-82FDB99A4205}.Release Pro|Any CPU.Build.0 = Release|Any CPU {A9C3B088-8C44-4F0A-93E8-82FDB99A4205}.Release|Any CPU.ActiveCfg = Release|Any CPU {A9C3B088-8C44-4F0A-93E8-82FDB99A4205}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {637A9A4D-A6D1-4C84-9A00-58AA440C2D58} = {EFF407A4-62ED-4B78-9573-7F65D70482C6} - {889A8B70-2B62-472A-AB7C-33CA9A7F48A0} = {D7E812B2-E16E-4B90-9EF8-1C5F9F907E96} - {49B06F58-0510-4279-B6F1-90F23AB3768D} = {D7E812B2-E16E-4B90-9EF8-1C5F9F907E96} - {1C663AB5-AEBC-41B5-A861-F635C37DAB47} = {D7E812B2-E16E-4B90-9EF8-1C5F9F907E96} - {D607959E-5DE3-43B9-8731-C22E495FCEF7} = {D7E812B2-E16E-4B90-9EF8-1C5F9F907E96} + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BDAC553B-9127-44F3-B607-F1DBADC730B6} EndGlobalSection EndGlobal diff --git a/Feather.vsmdi b/Feather.vsmdi deleted file mode 100644 index c211b16c2..000000000 --- a/Feather.vsmdi +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/FxCop/CustomDictionary.xml b/FxCop/CustomDictionary.xml deleted file mode 100644 index bf28f89d1..000000000 --- a/FxCop/CustomDictionary.xml +++ /dev/null @@ -1,587 +0,0 @@ - - - - - - cb - ch - csc - elem - gt - idx - img - lg - multi - num - ps - pw - scp - si - sig - tk - tw - val - meth - - - - - - mvc - Thumbler - Reddit - SiteMapNode - ContentBlock - HttpContext - ControllerContext - Params - Param - Unshare - Js - Sf - Img - Div - Configs - Infos - RouteData - Templatable - - - - Dropbox - Everlive - Lucene - Crontab - Impl - Marketo - Telerik - Sitefinity - frontend - backend - - - accessor - accessors - acos - aes - ansi - aptca - arg - args - asin - asm - aspx - async - atan - baml - bcl - bidi - bindable - bitrate - blittable - blog - bool - bootstrapper - bootstrappers - browsable - cacheability - callee - callees - canonicalize - cdecl - cdo - chtml - cim - cloneable - clr - clr's - cls - clsid - clsids - cmd - cmdlet - cmdlets - cng - comparand - concat - config - const - contravariant - cookieless - cos - crm - css - csv - cyclomatic - db - debuggable - decommission - deformatter - delegator - dequeue - dereferenced - des - deserialization - deserialize - deserialized - deserializing - dev - dhcp - diffie - discardable - dll - dns - documentable - dsa - dsig - dtd - dtds - eg - em - email - emails - emf - encodable - endian - enqueue - enum - enums - equatable - etc - eventing - expando - finalizer - finalizers - fixup - fixups - formattable - func - fx - fxcop - gac - goto - guid - guids - hashtable - hashtables - hashtable's - hdc - hijri - hmac - href - idn - ie - iis - il - ime - init - initializer - initializers - inlining - int - interop - intrinsics - ipv - iterator - iterators - jit - json - lcid - lcids - ldap - linq - localhost - loopback - loopbacks - mapper - mappers - marshaler - marshalers - mdi - mergable - misc - miscased - monitorable - mscoree - mscorlib - msdn - msh - multiline - multipanel - multipanels - multiview - multiviews - mutator - mutators - mutex - mutexes - ndpsec - nls - nop - ntfs - ntlm - nullable - numerics - obj - odbc - oks - overridable - pageable - partitioner - partitioners - parameterless - pdb - persister - persistable - playlist - pragma - prepend - prog - ptr - queryable - ras - rect - rects - recurse - refactor - reg - regex - remoted - remoting - representable - res - resolver - resolvers - rethrow - rethrows - rijndael - rpc - rsa - rtc - rtm - runspace - rva - sdl - saml - sdk - searchspace - searchspaces - seekable - seq - serializable - serializer - serializers - silverlight - smtp - specifier - specifiers - spline - sortable - sql - sddl - ssl - sta - stickies - struct - structs - stylesheet - subaddress - subaddresses - subclass - subclasses - subdirectories - subdirectory - subexpression - subexpressions - subitem - subitems - subkey - subkeys - submenu - submenus - subpath - subpaths - subsegment - subsegments - subtree - subtrees - tcp - templated - thunk - thunks - tlb - tuple - tuples - udp - udt - ui - unbox - unboxing - unc - uncategorize - uncheck - unchecked - unchecking - unescape - unindent - uninitialize - uninitialized - uninstantiated - unmaintainable - unmarshal - unregister - unregistering - unregisters - unregistration - unrepresentable - unterminated - untrusted - uri - uris - url - urls - utc - utf - validator - var - vs - vsa - virtualize - virtualizes - virtualizing - visualizer - visualizers - weblog - wiki - wcf - wmf - wmi - wml - wpf - wql - wsdl - xaml - xhtml - xmlns - xor - xrml - xsd - xsi - xsl - xslt - - - - complus - cancelled - cancelling - cancelation - cancelations - indices - login - logout - signon - signoff - writeable - cant - arent - dont - doesnt - didnt - couldnt - wouldnt - shouldnt - wont - havent - hasnt - hadnt - isnt - wasnt - werent - flag - flags - - - - databind - databinds - databinding - datastore - datastores - dataset - datasets - textbox - textboxes - codepage - codepages - checkbox - checkboxes - pushbutton - pushbuttons - dropdown - dropdowns - toolbar - toolbars - scrollbar - scrollbars - bitflag - bitflags - filename - filenames - fileserver - fileservers - username - usernames - hostname - hostnames - fieldname - fieldnames - pathname - pathnames - assemblyname - assemblynames - whitespace - whitespaces - logon - logons - logoff - logoffs - signin - signins - signout - signouts - stylesheet - stylesheets - frontend - frontends - backend - backends - sitemap - sitemaps - datatype - datatypes - designtime - designtimes - readonly - truetype - netbios - autodetect - autodetects - autoscroll - autoscrolls - autocomplete - autocompletes - autosave - autosaves - javascript - jscript - voiceview - appletalk - mapinfo - newline - newlines - qword - qwords - keyset - keysets - codebase - codebases - gridline - gridlines - opcode - opcodes - fxcop - - - - onset - inset - byname - setout - countertype - editor - longtime - drawstring - hookup - cleanup - breakout - setline - maybe - nods - classis - gettable - inform - beset - settable - standalone - threadlike - infield - infields - meantime - mackey - jscript - ipv - tooltip - tooltips - indispose - webhost - webhosts - preshow - superfast - goto - endpoint - strongbox - nonbreaking - nonliteral - insource - charset - timespan - - - - - Pv - Pi - Na - NESW - NWSE - Op - - - diff --git a/FxCop/Feather.ruleset b/FxCop/Feather.ruleset deleted file mode 100644 index f44a1861e..000000000 --- a/FxCop/Feather.ruleset +++ /dev/null @@ -1,371 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index a908c4d24..000000000 --- a/Gruntfile.js +++ /dev/null @@ -1,90 +0,0 @@ -module.exports = function (grunt) { - 'use strict'; - - //Project Configuration - grunt.initConfig({ - - pkg: grunt.file.readJSON('package.json'), - - jshint: { - //define the files to lint - files: ['gruntfile.js', - 'Telerik.Sitefinity.Frontend/Designers/Scripts/*.js', - 'Telerik.Sitefinity.Frontend/MVC/Scripts/*.js', - 'Telerik.Sitefinity.Frontend/MVC/Scripts/Designer/*.js', - 'Telerik.Sitefinity.Frontend/client-components/**/*.js', - 'Tests/Telerik.Sitefinity.Frontend.ClientTest/unit/**/*.js' - ] - }, - - jasmine: { - unit:{ - src: [ - 'Tests/Telerik.Sitefinity.Frontend.ClientTest/templates.js', - 'Telerik.Sitefinity.Frontend/Designers/Scripts/*.js', - 'Telerik.Sitefinity.Frontend/MVC/Scripts/Designer/*.js', - 'Telerik.Sitefinity.Frontend/MVC/Scripts/*.js', - 'Telerik.Sitefinity.Frontend/client-components/selectors/common/sf-services.js', - 'Telerik.Sitefinity.Frontend/client-components/selectors/common/sf-selectors.js', - 'Telerik.Sitefinity.Frontend/client-components/selectors/common/sf-list-selector.js', - 'Telerik.Sitefinity.Frontend/client-components/selectors/common/sf-items-tree.js', - 'Telerik.Sitefinity.Frontend/client-components/selectors/common/sf-selected-items-view.js', - 'Telerik.Sitefinity.Frontend/client-components/fields/sf-fields.js', - 'Telerik.Sitefinity.Frontend/client-components/**/*.js', - '!Telerik.Sitefinity.Frontend/Designers/Scripts/page-editor.js', - 'Tests/Telerik.Sitefinity.Frontend.ClientTest/helpers/mocks/*.js'], - options: { - vendor:[ - 'Tests/Telerik.Sitefinity.Frontend.ClientTest/helpers/jquery.min.js', - 'Tests/Telerik.Sitefinity.Frontend.ClientTest/helpers/angular.js', - 'Tests/Telerik.Sitefinity.Frontend.ClientTest/helpers/angular-resource.js', - 'Tests/Telerik.Sitefinity.Frontend.ClientTest/helpers/angular-route.js', - 'Tests/Telerik.Sitefinity.Frontend.ClientTest/helpers/angular-sanitize.min.js', - 'Tests/Telerik.Sitefinity.Frontend.ClientTest/helpers/angular-mocks.js', - 'Tests/Telerik.Sitefinity.Frontend.ClientTest/helpers/kendo.all.min.js', - 'Tests/Telerik.Sitefinity.Frontend.ClientTest/helpers/common-methods.js', - 'Tests/Telerik.Sitefinity.Frontend.ClientTest/helpers/jasmine-matchers.js', - 'Telerik.Sitefinity.Frontend/MVC/Scripts/Bootstrap/js/*.js', - '!Telerik.Sitefinity.Frontend/Mvc/Scripts/Angular/**', - 'Telerik.Sitefinity.Frontend/MVC/Scripts/ng-tags-input.min/ng-tags-input.min.js' - ], - specs: ['Tests/Telerik.Sitefinity.Frontend.ClientTest/unit/**/*.js'], - junit: { - path: 'Tests/Telerik.Sitefinity.Frontend.ClientTest/TestResults' - }, - template: require('grunt-template-jasmine-istanbul'), - templateOptions: { - coverage: 'Tests/Telerik.Sitefinity.Frontend.ClientTest/coverage/coverage.json', - report: [ - {type: 'html', options: {dir: 'Tests/Telerik.Sitefinity.Frontend.ClientTest/coverage'}}, - {type: 'cobertura', options: {dir: 'Tests/Telerik.Sitefinity.Frontend.ClientTest/coverage/cobertura'}}, - {type: 'text-summary'} - ] - } - } - } - }, - - //Converts directive's external html templates into javascript strings and stores them in the Angular's $templateCache service. - html2js: { - options: { - singleModule: true, - module: 'templates', - base: 'Telerik.Sitefinity.Frontend' - }, - main: { - src: ['Telerik.Sitefinity.Frontend/client-components/**/*.html', 'Telerik.Sitefinity.Frontend/client-components/**/*.sf-cshtml'], - dest: 'Tests/Telerik.Sitefinity.Frontend.ClientTest/templates.js' - }, - }, - }); - - //Load the needed plugins - grunt.loadNpmTasks('grunt-contrib-jshint'); - grunt.loadNpmTasks('grunt-contrib-jasmine'); - grunt.loadNpmTasks('grunt-html2js'); - - //Default task(s) - grunt.registerTask('default', ['jshint','html2js', 'jasmine']); - -}; \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md index 280aaba30..edbff986d 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,9 +1,12 @@ -This project has been released under the Apache License, version 2.0, the text of which is included below. This license applies ONLY to the project-specific source of each repository and does not extend to Telerik Sitefinity CMS itself, or any other 3rd party libraries used in a repository. For licensing information about Telerik Sitefinity CMS, see the [License Agreements page](http://www.sitefinity.com/purchase/license-agreement) at [Sitefinity.com](http://www.sitefinity.com/). -Copyright © 2005-2016 Telerik AD + +This project has been released under the Apache License, version 2.0, the text of which is included below. This license applies ONLY to the source of this repository and does not extend to any other Sitefinity distribution or variant, or any other 3rd party libraries used in a repository. For licensing information about Sitefinity, see https://www.progress.com/legal/license-agreements/sitefinity/license-agreement. + +Copyright © 2018 Progress Software Corporation and/or one of its subsidiaries or affiliates. All rights reserved. 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/NuGet.Config b/NuGet.Config new file mode 100644 index 000000000..0970df1f5 --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 2a401b456..3c8c4b347 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,9 @@ This repository contains the core infrastructure related to [Project Feather](ht | Feather version | Sitefinity version | |----|----| -| v.1.6.500.0 - latest | 9.1.6100.0 to 9.1.9999 | +| v.1.9.800.0 - latest | 10.0.6400.0 to 10.0.9999 | +| v.1.7.600.0 - 1.7.690.0 | 9.2.6200.0 to 9.2.9999 | +| v.1.6.500.0 - 1.6.560.0 | 9.1.6100.0 to 9.1.9999 | | v.1.5.470.0 - 1.5.490.0 | 9.0.6000.0 to 9.0.9999 | | v.1.4.360.0 - 1.4.460.0 | 8.2.5900.0 to 8.2.9999 | | v.1.3.300.1 - 1.3.350.0 | 8.1.5800.0 to 8.1.9999 | @@ -46,7 +48,7 @@ This repository contains the core infrastructure related to [Project Feather](ht This project has been released under the Apache License, version 2.0, the text of which is included below. This license applies ONLY to the project-specific source of each repository and does not extend to Telerik Sitefinity CMS itself, or any other 3rd party libraries used in a repository. For licensing information about Telerik Sitefinity CMS, see the [License Agreements page](http://www.sitefinity.com/purchase/license-agreement) at [Sitefinity.com](http://www.sitefinity.com/). -Copyright © 2005-2016 Telerik AD +Copyright © 2005-2017 Telerik AD 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 diff --git a/Settings.StyleCop b/Settings.StyleCop deleted file mode 100644 index 2ea453b5b..000000000 --- a/Settings.StyleCop +++ /dev/null @@ -1,294 +0,0 @@ - - - NoMerge - - \..\Builds\StyleCop\StyleCop 4.7\ - - - flag - Sitemap - Sitefinity - Ninject - css - queryable - Guid - Unshare - parameterless - Json - - - - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - True - True - - - - - - - False - - - - - False - - - - - - - - - - False - - - - - - - - - - False - - - - - - - - - - False - - - - - - - - - id - is - ui - - - - - \ No newline at end of file diff --git a/StyleCop/StyleCop 4.7/CustomDictionary.en-GB.xml b/StyleCop/StyleCop 4.7/CustomDictionary.en-GB.xml deleted file mode 100644 index 1eb0de215..000000000 --- a/StyleCop/StyleCop 4.7/CustomDictionary.en-GB.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - canonicalise - canonicalised - canonicaliser - canonicalisers - canonicalises - deserialise - deserialised - deserialiser - deserialisers - deserialises - favourited - initialiser - initialisers - serialisable - serialiser - uninitialise - uninitialises - - - preprocessed - shortlived - - - - diff --git a/StyleCop/StyleCop 4.7/CustomDictionary.en-US.xml b/StyleCop/StyleCop 4.7/CustomDictionary.en-US.xml deleted file mode 100644 index 95004ff18..000000000 --- a/StyleCop/StyleCop 4.7/CustomDictionary.en-US.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - canonicalize - canonicalized - canonicalizer - canonicalizers - canonicalizes - deserialize - deserialized - deserializes - deserialized - favorited - initializer - initializers - serializable - serializer - uninitialize - uninitializes - - - - diff --git a/StyleCop/StyleCop 4.7/CustomDictionary.xml b/StyleCop/StyleCop 4.7/CustomDictionary.xml deleted file mode 100644 index efbb52253..000000000 --- a/StyleCop/StyleCop 4.7/CustomDictionary.xml +++ /dev/null @@ -1,108 +0,0 @@ - - - - - typeparam - accessor - accessors - addin - addins - Args - async - backend - cacheability - callee - captcha - CodePlex - comparand - comparands - config - csproj - declarator - declarators - decryptable - dependant - dialog - Elasticsearch - elif - endif - enqueue - enqueued - enqueueing - enqueues - Env - etc - Foo - foreach - frontend - goto - guids - hexidecimal - ietf - inlined - iterator - iterators - leaderboard - leaderboards - leafname - lexer - loggable - lossiness - minification - mutex - muxer - muxing - overridable - ownable - packshot - plugin - plugins - prepend - prepends - readonly - refactored - reformatter - remoted - restartable - rethrow - retriable - retval - rewindable - rfc - seekable - settings - signalled - Sitefinity - sizeof - stackalloc - streamable - struct - structs - StyleCop - Sitemap - sitemap - subkey - typeof - typeless - uname - unassessed - unbias - undefine - undefines - unencoded - unescaped - Unescapes - unix - unowned - Unsecures - unsecuring - uploader - url - Usings - utils - wildcarded - xpath - - - - diff --git a/StyleCop/StyleCop 4.7/Settings.StyleCop b/StyleCop/StyleCop 4.7/Settings.StyleCop deleted file mode 100644 index 8df264eb7..000000000 --- a/StyleCop/StyleCop 4.7/Settings.StyleCop +++ /dev/null @@ -1,38 +0,0 @@ - - - - preprocessor,pre-processor - shortlived,short-lived - - - - - - - \.g\.cs$ - \.generated\.cs$ - \.g\.i\.cs$ - - - - - - - - - as - do - id - if - in - is - my - no - on - to - ui - - - - - \ No newline at end of file diff --git a/StyleCop/StyleCop 4.7/StyleCop.CSharp.Rules.dll b/StyleCop/StyleCop 4.7/StyleCop.CSharp.Rules.dll deleted file mode 100644 index 16ca241c5..000000000 Binary files a/StyleCop/StyleCop 4.7/StyleCop.CSharp.Rules.dll and /dev/null differ diff --git a/StyleCop/StyleCop 4.7/StyleCop.CSharp.dll b/StyleCop/StyleCop 4.7/StyleCop.CSharp.dll deleted file mode 100644 index 0ab21df4d..000000000 Binary files a/StyleCop/StyleCop 4.7/StyleCop.CSharp.dll and /dev/null differ diff --git a/StyleCop/StyleCop 4.7/StyleCop.VSPackage.dll b/StyleCop/StyleCop 4.7/StyleCop.VSPackage.dll deleted file mode 100644 index 4b9f05a55..000000000 Binary files a/StyleCop/StyleCop 4.7/StyleCop.VSPackage.dll and /dev/null differ diff --git a/StyleCop/StyleCop 4.7/StyleCop.dll b/StyleCop/StyleCop 4.7/StyleCop.dll deleted file mode 100644 index 38943720c..000000000 Binary files a/StyleCop/StyleCop 4.7/StyleCop.dll and /dev/null differ diff --git a/StyleCop/StyleCop 4.7/StyleCopSettingsEditor.exe b/StyleCop/StyleCop 4.7/StyleCopSettingsEditor.exe deleted file mode 100644 index 06966218c..000000000 Binary files a/StyleCop/StyleCop 4.7/StyleCopSettingsEditor.exe and /dev/null differ diff --git a/StyleCop/StyleCop 4.7/de-DE/StyleCop.CSharp.Rules.resources.dll b/StyleCop/StyleCop 4.7/de-DE/StyleCop.CSharp.Rules.resources.dll deleted file mode 100644 index 42db53cb3..000000000 Binary files a/StyleCop/StyleCop 4.7/de-DE/StyleCop.CSharp.Rules.resources.dll and /dev/null differ diff --git a/StyleCop/StyleCop 4.7/en-GB/StyleCop.CSharp.Rules.resources.dll b/StyleCop/StyleCop 4.7/en-GB/StyleCop.CSharp.Rules.resources.dll deleted file mode 100644 index e63f98e6d..000000000 Binary files a/StyleCop/StyleCop 4.7/en-GB/StyleCop.CSharp.Rules.resources.dll and /dev/null differ diff --git a/StyleCop/StyleCop 4.7/fr-FR/StyleCop.CSharp.Rules.resources.dll b/StyleCop/StyleCop 4.7/fr-FR/StyleCop.CSharp.Rules.resources.dll deleted file mode 100644 index 2ac90783d..000000000 Binary files a/StyleCop/StyleCop 4.7/fr-FR/StyleCop.CSharp.Rules.resources.dll and /dev/null differ diff --git a/StyleCop/StyleCop 4.7/mssp7en.dll b/StyleCop/StyleCop 4.7/mssp7en.dll deleted file mode 100644 index 392381f67..000000000 Binary files a/StyleCop/StyleCop 4.7/mssp7en.dll and /dev/null differ diff --git a/StyleCop/StyleCop 4.7/mssp7en.lex b/StyleCop/StyleCop 4.7/mssp7en.lex deleted file mode 100644 index da4c60930..000000000 Binary files a/StyleCop/StyleCop 4.7/mssp7en.lex and /dev/null differ diff --git a/StyleCop/StyleCop 4.7/pl-PL/StyleCop.CSharp.Rules.resources.dll b/StyleCop/StyleCop 4.7/pl-PL/StyleCop.CSharp.Rules.resources.dll deleted file mode 100644 index 0d3c0f791..000000000 Binary files a/StyleCop/StyleCop 4.7/pl-PL/StyleCop.CSharp.Rules.resources.dll and /dev/null differ diff --git a/StyleCop/StyleCop 4.7/pt-BR/StyleCop.CSharp.Rules.resources.dll b/StyleCop/StyleCop 4.7/pt-BR/StyleCop.CSharp.Rules.resources.dll deleted file mode 100644 index a6aad0f0e..000000000 Binary files a/StyleCop/StyleCop 4.7/pt-BR/StyleCop.CSharp.Rules.resources.dll and /dev/null differ diff --git a/StyleCop/StyleCop 4.7/ru-RU/StyleCop.CSharp.Rules.resources.dll b/StyleCop/StyleCop 4.7/ru-RU/StyleCop.CSharp.Rules.resources.dll deleted file mode 100644 index 5e32c6a3a..000000000 Binary files a/StyleCop/StyleCop 4.7/ru-RU/StyleCop.CSharp.Rules.resources.dll and /dev/null differ diff --git a/StyleCop/StyleCop 4.7/tools/StyleCopReport.xsl b/StyleCop/StyleCop 4.7/tools/StyleCopReport.xsl deleted file mode 100644 index e1e038360..000000000 --- a/StyleCop/StyleCop 4.7/tools/StyleCopReport.xsl +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - StyleCop Report - - - No StyleCop information in build logs. - - - - - - - - - - - error - - - success - - - Number of Violations: - - - - - - - - - - - - - - \ No newline at end of file diff --git a/StyleCop/StyleCop.targets b/StyleCop/StyleCop.targets deleted file mode 100644 index f839b9c56..000000000 --- a/StyleCop/StyleCop.targets +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - $(BuildDependsOn);StyleCop - StyleCopForceFullAnalysis;$(RebuildDependsOn) - - - - - $(SourceAnalysisForceFullAnalysis) - - - false - - - - - $(SourceAnalysisCacheResults) - - - true - - - - - false - - - false - - - - - $(SourceAnalysisEnabled) - - - true - - - - - $(SourceAnalysisOverrideSettingsFile) - - - - - - - - $(SourceAnalysisOutputFile) - - - $(IntermediateOutputPath)StyleCopViolations.xml - - - - - - 0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Telerik.Sitefinity.Frontend.Data/FileMonitorFluentMapping.cs b/Telerik.Sitefinity.Frontend.Data/FileMonitorFluentMapping.cs index d72610236..18c0c1d5a 100644 --- a/Telerik.Sitefinity.Frontend.Data/FileMonitorFluentMapping.cs +++ b/Telerik.Sitefinity.Frontend.Data/FileMonitorFluentMapping.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using Telerik.OpenAccess; using Telerik.OpenAccess.Metadata.Fluent; +using Telerik.OpenAccess.Metadata.Fluent.Advanced; using Telerik.Sitefinity.Model; namespace Telerik.Sitefinity.Frontend.FilesMonitoring.Data @@ -49,6 +50,8 @@ private void MapFileData(List mappings) fileDataMapping.HasProperty(p => p.FilePath).ToColumn("file_path"); fileDataMapping.HasProperty(p => p.PackageName).ToColumn("package_name"); + fileDataMapping.HasIndex(p => new { p.FilePath }).IsUnique().WithName("idx_sf_fls_mntr_dta_path"); + mappings.Add(fileDataMapping); } diff --git a/Telerik.Sitefinity.Frontend.Data/OpenAccessNuGet.targets b/Telerik.Sitefinity.Frontend.Data/OpenAccessNuGet.targets deleted file mode 100644 index edbd4849e..000000000 --- a/Telerik.Sitefinity.Frontend.Data/OpenAccessNuGet.targets +++ /dev/null @@ -1,158 +0,0 @@ - - - - - false - false - true - enhancer.exe - 3 - true - false - true - - - - - - - - - - - - - - - - - - - - - - OpenAccessEnhancerCheck; - - - OpenAccessDeploy; - $(BuildDependsOn) - - - - OpenAccessEnhancerCheck; - - - $(CleanDependsOn); - DeleteEnhancementFlag; - - - $(CleanDependsOn); - OpenAccessClean; - - - - OpenAccessEnhancerCheck; - - - $(CompileDependsOn); - OpenAccessEnhancer; - - - - ValidateMappingCheck; - - - $(CompileDependsOn); - OpenAccessValidateMapping; - - - - - - - - - - - - - - -xmlMapping:"@(OpenAccessEmbeddedResource,'" -xmlMapping:"')" - -attributeMapping - -fluentMapping - $(XmlMappingResourcesArg)$(AttributesMappingArg)$(FluentMappingArg) - -keyFile:"$(AssemblyOriginatorKeyFile)" - -keyContainer:"$(KeyContainerName)" - -reference:"@(ReferencePath,'" -reference:"')" - -DoNotCheckVersionOfReferencedAssembliesArgs+ - $(EnhancerMappingArgs)$(KeyFileArg)$(KeyContainerArg)$(ReferencesArgs)$(DoNotCheckVersionOfReferencedAssembliesArgs) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Telerik.Sitefinity.Frontend.Data/Telerik.Sitefinity.Frontend.Data.csproj b/Telerik.Sitefinity.Frontend.Data/Telerik.Sitefinity.Frontend.Data.csproj index e78e16106..971cd5129 100644 --- a/Telerik.Sitefinity.Frontend.Data/Telerik.Sitefinity.Frontend.Data.csproj +++ b/Telerik.Sitefinity.Frontend.Data/Telerik.Sitefinity.Frontend.Data.csproj @@ -1,155 +1,21 @@ - - - - - Debug - AnyCPU - {A9C3B088-8C44-4F0A-93E8-82FDB99A4205} - Library - Properties - Telerik.Sitefinity.Frontend.Data - Telerik.Sitefinity.Frontend.Data - v4.5 - 512 - ..\ - true - $(SolutionDir)\packages\Telerik.DataAccess.Fluent.2016.1.224.1\tools\enhancer\enhancer.exe - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - ..\packages\Telerik.DataAccess.Core.2016.1.224.1\lib\net40\Telerik.OpenAccess.dll - False - True - - - ..\packages\Telerik.DataAccess.Core.2016.1.224.1\lib\net40\Telerik.OpenAccess.35.Extensions.dll - False - True - - - ..\packages\Telerik.DataAccess.Core.2016.1.224.1\lib\net40\Telerik.OpenAccess.40.Extensions.dll - False - True - - - ..\packages\Telerik.DataAccess.Core.2016.1.224.1\lib\net40\Telerik.OpenAccess.Runtime.dll - False - True - - - ..\packages\Telerik.Sitefinity.Core.9.1.6100.0\lib\net40\Telerik.Sitefinity.dll - True - False - - - ..\packages\Telerik.Sitefinity.Core.9.1.6100.0\lib\net40\Telerik.Sitefinity.Model.dll - True - False - - - ..\packages\Telerik.Sitefinity.Core.9.1.6100.0\lib\net40\Telerik.Sitefinity.Personalization.dll - True - False - - - False - ..\packages\Telerik.Sitefinity.Core.9.1.6100.0\lib\net40\Telerik.Sitefinity.Resources.dll - - - ..\packages\Telerik.Sitefinity.Core.9.1.6100.0\lib\net40\Telerik.Sitefinity.Search.dll - True - False - - - ..\packages\Telerik.Sitefinity.Core.9.1.6100.0\lib\net40\Telerik.Sitefinity.Services.Comments.dll - True - False - - - ..\packages\Telerik.Sitefinity.Core.9.1.6100.0\lib\net40\Telerik.Sitefinity.Services.Documents.dll - True - False - - - ..\packages\Telerik.Sitefinity.Core.9.1.6100.0\lib\net40\Telerik.Sitefinity.Services.Events.dll - True - False - - - ..\packages\Telerik.Sitefinity.Core.9.1.6100.0\lib\net40\Telerik.Sitefinity.Services.Notifications.dll - True - False - - - ..\packages\Telerik.Sitefinity.Core.9.1.6100.0\lib\net40\Telerik.Sitefinity.Services.Statistics.dll - True - False - - - ..\packages\Telerik.Sitefinity.Core.9.1.6100.0\lib\net40\Telerik.Sitefinity.Utilities.dll - True - False - - - - - Properties\SharedAssemblyInfo.cs - - - - - - - - - - - - - Designer - - - Designer - - - - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - + + + + + + + + + + + + + + + + $(PkgTelerik_DataAccess_Fluent)\tools\enhancer\enhancer.exe + + + + \ No newline at end of file diff --git a/Telerik.Sitefinity.Frontend.Data/app.config b/Telerik.Sitefinity.Frontend.Data/app.config deleted file mode 100644 index 89a685bbc..000000000 --- a/Telerik.Sitefinity.Frontend.Data/app.config +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Telerik.Sitefinity.Frontend.Data/packages.config b/Telerik.Sitefinity.Frontend.Data/packages.config deleted file mode 100644 index 0b4c51ce9..000000000 --- a/Telerik.Sitefinity.Frontend.Data/packages.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/Telerik.Sitefinity.Frontend/ControllerProvider.cs b/Telerik.Sitefinity.Frontend/ControllerProvider.cs new file mode 100644 index 000000000..91757aeec --- /dev/null +++ b/Telerik.Sitefinity.Frontend/ControllerProvider.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Controllers; +using Telerik.Sitefinity.Mvc.Store; + +namespace Telerik.Sitefinity.Frontend +{ + internal class ControllerProvider : IControllerProvider + { + public IEnumerable GetControllers() + { + var controllerContainerInitializer = new ControllerContainerInitializer(); + return controllerContainerInitializer.GetControllers(); + } + } +} diff --git a/Telerik.Sitefinity.Frontend/Designers/DesignerInitializer.cs b/Telerik.Sitefinity.Frontend/Designers/DesignerInitializer.cs index 85c89d964..e4012c865 100644 --- a/Telerik.Sitefinity.Frontend/Designers/DesignerInitializer.cs +++ b/Telerik.Sitefinity.Frontend/Designers/DesignerInitializer.cs @@ -7,6 +7,7 @@ using System.Web.UI; using Telerik.Microsoft.Practices.Unity; using Telerik.Sitefinity.Abstractions; +using Telerik.Sitefinity.Frontend.Mvc.Helpers; using Telerik.Sitefinity.Frontend.Resources; using Telerik.Sitefinity.Services; using Telerik.Sitefinity.Web.UI; @@ -56,12 +57,12 @@ private void RegisteringScriptsHandler(IScriptsRegisteringEvent @event) { if (@event.Sender.GetType() == typeof(ZoneEditor)) { - var scriptRootPath = "~/" + FrontendManager.VirtualPathBuilder.GetVirtualPath(this.GetType().Assembly); + var scriptRootPath = VirtualPathUtility.ToAbsolute("~/" + FrontendManager.VirtualPathBuilder.GetVirtualPath(this.GetType().Assembly)); - @event.Scripts.Add(new ScriptReference(scriptRootPath + "Mvc/Scripts/Angular/angular.min.js")); - @event.Scripts.Add(new ScriptReference(scriptRootPath + "Mvc/Scripts/Angular/angular-route.min.js")); - @event.Scripts.Add(new ScriptReference(scriptRootPath + "Mvc/Scripts/Angular/angular-sanitize.min.js")); - @event.Scripts.Add(new ScriptReference(scriptRootPath + "Mvc/Scripts/Bootstrap/js/ui-bootstrap-tpls.min.js")); + @event.Scripts.Add(new ScriptReference(UrlHelpers.AppendVersion(scriptRootPath + "Mvc/Scripts/Angular/angular.min.js"))); + @event.Scripts.Add(new ScriptReference(UrlHelpers.AppendVersion(scriptRootPath + "Mvc/Scripts/Angular/angular-route.min.js"))); + @event.Scripts.Add(new ScriptReference(UrlHelpers.AppendVersion(scriptRootPath + "Mvc/Scripts/Angular/angular-sanitize.min.js"))); + @event.Scripts.Add(new ScriptReference(UrlHelpers.AppendVersion(scriptRootPath + "Mvc/Scripts/Bootstrap/js/ui-bootstrap-tpls.min.js"))); ////var references = PageManager.GetScriptReferences(ScriptRef.KendoAll); ////foreach (var scriptRef in references) @@ -69,10 +70,10 @@ private void RegisteringScriptsHandler(IScriptsRegisteringEvent @event) //// @event.Scripts.Add(scriptRef); ////} - @event.Scripts.Add(new ScriptReference(scriptRootPath + "Designers/Scripts/page-editor-services.js")); - @event.Scripts.Add(new ScriptReference(scriptRootPath + "Designers/Scripts/page-editor.js")); - - @event.Scripts.Add(new ScriptReference(scriptRootPath + "Mvc/Scripts/LABjs/LAB.min.js")); + @event.Scripts.Add(new ScriptReference(UrlHelpers.AppendVersion(scriptRootPath + "Designers/Scripts/page-editor-services.js"))); + @event.Scripts.Add(new ScriptReference(UrlHelpers.AppendVersion(scriptRootPath + "Designers/Scripts/page-editor.js"))); + + @event.Scripts.Add(new ScriptReference(UrlHelpers.AppendVersion(scriptRootPath + "Mvc/Scripts/LABjs/LAB.min.js"))); var currentPackage = new PackageManager().GetCurrentPackage(); if (!currentPackage.IsNullOrEmpty()) diff --git a/Telerik.Sitefinity.Frontend/Designers/DesignerResolver.cs b/Telerik.Sitefinity.Frontend/Designers/DesignerResolver.cs index 10e401bde..ad4a32feb 100644 --- a/Telerik.Sitefinity.Frontend/Designers/DesignerResolver.cs +++ b/Telerik.Sitefinity.Frontend/Designers/DesignerResolver.cs @@ -1,8 +1,14 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; +using System.Web.Mvc; using Telerik.Sitefinity.Abstractions; +using Telerik.Sitefinity.Frontend.Mvc.Helpers; using Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Controllers; using Telerik.Sitefinity.Frontend.Resources; +using Telerik.Sitefinity.Mvc; +using Telerik.Sitefinity.Mvc.Proxy; using Telerik.Sitefinity.Web.UI.ControlDesign; namespace Telerik.Sitefinity.Frontend.Designers @@ -47,6 +53,44 @@ public string GetUrl(Type widgetType) return this.packageManager.EnhanceUrl(designerUrl); } + /// + /// Checks if there are separate custom desiger views for the particular control type. + /// + /// The type of the control. + /// + public bool HasCustomDesigners(string controlType) + { + var mvcProxy = new MvcControllerProxy(); + mvcProxy.ControllerName = controlType; + var controller = mvcProxy.GetController() as Controller; + + if (controller == null) + return true; + + var designerNamePattern = "DesignerView.*"; + var views = controller.GetViews(null).Where(view => Regex.IsMatch(view, designerNamePattern)); + + return views.Count() > 0; + } + + /// + /// Gets all view names that match a pattern. + /// + /// The control type. + /// The view name pattern + /// + public IEnumerable GetViewNames(string controlType, string viewNamePattern) + { + var mvcProxy = new MvcControllerProxy(); + mvcProxy.ControllerName = controlType; + var controller = mvcProxy.GetController() as Controller; + + if (controller == null) + return new List(); + + return ViewSelectorHelpers.GetViewNames(null, controller, viewNamePattern); + } + #endregion #region Private members @@ -116,6 +160,8 @@ private string GetDefaultUrl(Type widgetType) private const string DefaultGridActionUrlTemplate = "~/Telerik.Sitefinity.Frontend/GridDesigner/Master/GridDesigner"; private readonly PackageManager packageManager; + public object ViewSelectorHelper { get; private set; } + #endregion } } diff --git a/Telerik.Sitefinity.Frontend/Designers/Scripts/page-editor-services.js b/Telerik.Sitefinity.Frontend/Designers/Scripts/page-editor-services.js index af74885e9..a605cac44 100644 --- a/Telerik.Sitefinity.Frontend/Designers/Scripts/page-editor-services.js +++ b/Telerik.Sitefinity.Frontend/Designers/Scripts/page-editor-services.js @@ -72,7 +72,7 @@ */ var getUrl = function () { if (!httpGetUrl) { - httpGetUrl = widgetContext.webServiceUrl + widgetContext.widgetId + '/?pageId=' + widgetContext.pageId; + httpGetUrl = widgetContext.webServiceUrl + widgetContext.widgetId + '/?pageId=' + widgetContext.pageId + "&mediaType=" + widgetContext.mediaType; } return httpGetUrl; }; @@ -122,16 +122,15 @@ var deferred = $q.defer(); $http.get(getUrl(), requestOptions()) - .success(function (data) { - properties = data; + .then(function (response) { + properties = response.data; //clone the result in initialData - initialData = $.extend(true, {}, data); + initialData = $.extend(true, {}, response.data); loadingPromise = null; - deferred.resolve(data); - }) - .error(function (data) { + deferred.resolve(response.data); + }, function (err) { loadingPromise = null; - deferred.reject(data); + deferred.reject(err); }); loadingPromise = deferred.promise; @@ -178,17 +177,24 @@ save: function (saveMode, modifiedData) { if (!modifiedData || modifiedData.length === 0) { modifiedData = this.getDirty(); + + // If any of the property values is not string - convert it to json + for (var i = 0; i < modifiedData.length; i++) { + var value = modifiedData[i].PropertyValue; + if (value !== null && typeof value !== 'string') { + modifiedData[i].PropertyValue = JSON.stringify(value); + } + } } var deferred = $q.defer(); $http.put(updateUrl(saveMode), modifiedData, requestOptions()) - .success(function (data) { + .then(function (response) { initialData = $.extend(true, {}, properties); - deferred.resolve(data); - }) - .error(function (data) { - deferred.reject(data); + deferred.resolve(response.data); + }, function (err) { + deferred.reject(err); }); return deferred.promise; @@ -261,7 +267,7 @@ var getInnerElements = function (layoutRoot) { var gridElements = []; - var elements = $(layoutRoot).find('[data-sf-element]'); + var elements = $(layoutRoot).findExcludeNested('[data-sf-element]'); //Sort out the inner columns for (var i = 0; i < elements.length; i++) { @@ -283,7 +289,7 @@ var tempLayout = $(gridContext.layoutRoot).clone(); for (var i = 0; i < elements.length; i++) { - var innerDiv = elements[i].id ? $(tempLayout).find('#' + elements[i].id) : $(tempLayout).find('[data-sf-element=' + elements[i].name + ']'); + var innerDiv = elements[i].id ? $(tempLayout).findExcludeNested('#' + elements[i].id) : $(tempLayout).findExcludeNested('[data-sf-element="' + elements[i].name + '"]'); if (innerDiv) { var label = elements[i].label; @@ -311,7 +317,7 @@ var refreshContainer = function (elements) { for (var i = 0; i < elements.length; i++) { - var innerDiv = elements[i].id ? $(gridContext.layoutContainer).find('#' + elements[i].id) : $(gridContext.layoutContainer).find('[data-sf-element=' + elements[i].name + ']'); + var innerDiv = elements[i].id ? $(gridContext.layoutContainer).findExcludeNested('#' + elements[i].id) : $(gridContext.layoutContainer).findExcludeNested('[data-sf-element="' + elements[i].name + '"]'); if (innerDiv) { var css = elements[i].isPlaceholder ? 'sf_colsIn ' + elements[i].css : elements[i].css; @@ -338,6 +344,27 @@ } }; + // find all child elements until you find child grid widget inside the current grid + $.fn.findExcludeNested = function (selector, result) { + + // Default result to an empty jQuery object if not provided + result = typeof result !== 'undefined' ? result : new jQuery(); + + // Iterate through all children, except those with the RadDockZone class + this.children().each(function () { + + var thisObject = jQuery(this); + if (thisObject.is(selector)) + result.push(this); + + // Recursively seek children without RadDockZone class + if (!thisObject.hasClass('RadDockZone')) + thisObject.findExcludeNested(selector, result); + }); + + return result; + }; + var invokePut = function (url, data, deferred, success) { var wRequest = new Sys.Net.WebRequest(); wRequest.set_url(url); diff --git a/Telerik.Sitefinity.Frontend/Designers/Scripts/page-editor.js b/Telerik.Sitefinity.Frontend/Designers/Scripts/page-editor.js index f8202b848..c664c7aec 100644 --- a/Telerik.Sitefinity.Frontend/Designers/Scripts/page-editor.js +++ b/Telerik.Sitefinity.Frontend/Designers/Scripts/page-editor.js @@ -3,197 +3,228 @@ var sitefinity = sitefinity || {}; (function ($) { - - var loader, + $(function () { + var loader, loaderMarkup = '', loaderTemplate = kendo.template(loaderMarkup), - dialog; + dialog, + pageScripts = $('script').map(function () { + return $(this).attr('src'); + }).toArray(); - function isScriptTag(tag) { - return tag.tagName == 'SCRIPT' && (!tag.type || tag.type.toLowerCase() == 'text/javascript'); - } + function isScriptTag(tag) { + return tag.tagName == 'SCRIPT' && (!tag.type || tag.type.toLowerCase() == 'text/javascript'); + } - function extractScripts(markup) { - var div = document.createElement('div'); - div.innerHTML = markup; - var scripts = div.getElementsByTagName('script'); + function extractScripts(markup) { + var div = document.createElement('div'); + div.innerHTML = markup; + var scripts = div.getElementsByTagName('script'); - var result = []; - for (var i = 0; i < scripts.length; i++) { - if (isScriptTag(scripts[i])) { - result.push(scripts[i]); + var result = []; + for (var i = 0; i < scripts.length; i++) { + if (isScriptTag(scripts[i])) { + result.push(scripts[i]); + } } - } - return result; - } + return result; + } - function stripScripts(markup) { - var div = document.createElement('div'); - div.innerHTML = markup; - var scripts = div.getElementsByTagName('script'); + function stripScripts(markup) { + var div = document.createElement('div'); + div.innerHTML = markup; + var scripts = div.getElementsByTagName('script'); - var i = scripts.length; - while (i--) { - if (isScriptTag(scripts[i])) { - scripts[i].parentNode.removeChild(scripts[i]); + var i = scripts.length; + while (i--) { + if (isScriptTag(scripts[i])) { + scripts[i].parentNode.removeChild(scripts[i]); + } } - } - return div.innerHTML; - } - - function loadScripts(container, scriptTags, loadHandler) { - var lab = $LAB.setOptions({ - AlwaysPreserveOrder: true, - AllowDuplicates: true - }); + return div.innerHTML; + } - for (var i = 0; i < scriptTags.length; i++) { - if (scriptTags[i].src) { - lab = lab.script(scriptTags[i].src); - } - else if (scriptTags[i].text) { - var text = scriptTags[i].text; - lab = lab.wait(function () { eval(text); }); // jshint ignore:line + function loadScripts(container, scriptTags, loadHandler) { + var lab = $LAB.setOptions({ + AlwaysPreserveOrder: true, + AllowDuplicates: true, + }); + + var labNoDuplicates = $LAB.setOptions({ + AlwaysPreserveOrder: true, + AllowDuplicates: false + }); + + for (var i = 0; i < scriptTags.length; i++) { + if (scriptTags[i].src) { + // Hack to not load bootstrap multiple times, should be removed with the next version of bootstrap or with feather designer script refactoring + if (scriptTags[i].src.indexOf('Mvc/Scripts/Bootstrap/js/bootstrap.min.js') >= 0) { + var src = $(scriptTags[i]).attr('src'); + + if (pageScripts.indexOf(src) < 0) { + labNoDuplicates.script(src); + } + } + else { + lab = lab.script(scriptTags[i].src); + } + } + else if (scriptTags[i].text) { + var text = scriptTags[i].text; + lab = lab.wait(function () { eval(text); }); // jshint ignore:line + } } + + labNoDuplicates.wait(function () { + lab.wait(loadHandler); + }); } - lab.wait(loadHandler); - } - - /** - * Represents the Sitefinity page editor. - */ - sitefinity.pageEditor = { - /** - * Shows the loading animation in the page editor. - * - * @param {String} appPath The relative path of the application used to resolve the loading image. - */ - showLoader: function (appPath) { - if (loader) { - loader.show(); - } else { - loader = $(loaderTemplate({ appPath: appPath })); - $('body').append(loader); - } - }, - - /** - * Hides the loading animation in the page editor. - */ - hideLoader: function () { - $(loader).hide(); - }, - - /** - * Renders the dialog within page editor with the provided markup. - * - * @param {String} markup The HTML markup to be rendered within the dialog. - */ - renderDialog: function (markup) { - dialog = $(''); - $('body').append(dialog); - - var scriptTags = extractScripts(markup); - markup = stripScripts(markup); - - dialog.append(markup); - dialog.on('hidden.bs.modal', this.destroyDialog); - - var that = this; - loadScripts(dialog[0], scriptTags, function () { - that.hideLoader(); - - if (typeof ($telerik) != 'undefined') { - $telerik.$(document).trigger('dialogRendered'); - } - }); - }, - - /** - * Event handler that handles the needDialog event from the Sitefinity - * page editor. - */ - openDialog: function (ev, args) { - this.showLoader(args.AppPath); - this.widgetContext = args; - - var separator; - if (this.widgetContext.url.indexOf('?') > -1) - separator = '&'; - else - separator = '?'; - - var url = this.widgetContext.url + separator + 'controlId=' + this.widgetContext.Id; - $.get(url) - .done($.proxy(this.renderDialog, this)) - .fail(function (data) { - alert('There is a problem with loading the widget designer: ' + data); - }); - }, - - openGridDialog: function (ev, args) { - this.showLoader(args.AppPath); - this.gridContext = args; - - var separator; - if (this.gridContext.url.indexOf('?') > -1) - separator = '&'; - else - separator = '?'; - - var url = this.gridContext.url + separator + 'controlId=' + this.gridContext.Id; - url += '&gridTitle=' + this.gridContext.GridTitle; - $.get(url) - .done($.proxy(this.renderDialog, this)) - .fail(function (data) { - alert('There is a problem with loading the grid designer: ' + data); - }); - }, - - openPersonalizationDialog: function (ev, args) { - this.showLoader(args.AppPath); - this.widgetContext = args; - - var url = this.widgetContext.AppPath + "Telerik.Sitefinity.Frontend/PersonalizationDesigner/Master/PersonalizationDesigner/"; - $.get(url) - .done($.proxy(this.renderDialog, this)) - .fail(function (data) { - alert('There is a problem with loading the widget designer: ' + data); - }); - }, - - /** - * Destroys the currently opened dialog. - */ - destroyDialog: function () { - if (dialog) { - dialog.remove(); - this.widgetContext = null; - this.gridContext = null; - } - }, - - /** - * Provides the context for the currently active widget. - */ - widgetContext: null, - gridContext: null - - }; - - /** - * Register the global Sitefinity events with the pageEditor component. - */ - if (typeof ($telerik) != 'undefined') { - $telerik.$(document).on('needsModalDialog', $.proxy(sitefinity.pageEditor.openDialog, sitefinity.pageEditor)); - - $telerik.$(document).on('needsPersonalizationModalDialog', $.proxy(sitefinity.pageEditor.openPersonalizationDialog, sitefinity.pageEditor)); - - $telerik.$(document).on('needsGridModalDialog', $.proxy(sitefinity.pageEditor.openGridDialog, sitefinity.pageEditor)); - $telerik.$(document).on('modalDialogClosed', $.proxy(sitefinity.pageEditor.destroyDialog, sitefinity.pageEditor)); - $telerik.$(document).on('gridModalDialogClosed', $.proxy(sitefinity.pageEditor.destroyDialog, sitefinity.pageEditor)); - } + /** + * Represents the Sitefinity page editor. + */ + sitefinity.pageEditor = { + /** + * Shows the loading animation in the page editor. + * + * @param {String} appPath The relative path of the application used to resolve the loading image. + */ + showLoader: function (appPath) { + if (loader) { + loader.show(); + } else { + loader = $(loaderTemplate({ appPath: appPath })); + $('body').append(loader); + } + }, + + /** + * Hides the loading animation in the page editor. + */ + hideLoader: function () { + $(loader).hide(); + }, + + /** + * Renders the dialog within page editor with the provided markup. + * + * @param {String} markup The HTML markup to be rendered within the dialog. + */ + renderDialog: function (markup) { + dialog = $(''); + $('body').append(dialog); + + var scriptTags = extractScripts(markup); + markup = stripScripts(markup); + + dialog.append(markup); + dialog.on('hidden.bs.modal', this.destroyDialog); + + var that = this; + loadScripts(dialog[0], scriptTags, function () { + that.hideLoader(); + + if (typeof ($telerik) != 'undefined') { + $telerik.$(document).trigger('dialogRendered'); + } + }); + }, + + /** + * Event handler that handles the needDialog event from the Sitefinity + * page editor. + */ + openDialog: function (args) { + if (args.detail.openNewEditor) + return true; + + this.showLoader(args.detail.AppPath); + this.widgetContext = args.detail; + + var separator; + if (this.widgetContext.url.indexOf('?') > -1) + separator = '&'; + else + separator = '?'; + + var url = this.widgetContext.url + separator + 'controlId=' + this.widgetContext.Id; + if (this.widgetContext.ModuleName) { + url += '&moduleName=' + this.widgetContext.ModuleName; + } + if (this.widgetContext.MediaType) { + url += '&mediaType=' + this.widgetContext.MediaType; + } + + $.get(url) + .done($.proxy(this.renderDialog, this)) + .fail(function (data) { + alert('There is a problem with loading the widget designer: ' + data); + }); + }, + + openGridDialog: function (ev, args) { + this.showLoader(args.AppPath); + this.gridContext = args; + + var separator; + if (this.gridContext.url.indexOf('?') > -1) + separator = '&'; + else + separator = '?'; + + var url = this.gridContext.url + separator + 'controlId=' + this.gridContext.Id; + url += '&gridTitle=' + this.gridContext.GridTitle; + $.get(url) + .done($.proxy(this.renderDialog, this)) + .fail(function (data) { + alert('There is a problem with loading the grid designer: ' + data); + }); + }, + + openPersonalizationDialog: function (ev, args) { + this.showLoader(args.AppPath); + this.widgetContext = args; + + var url = this.widgetContext.AppPath + "Telerik.Sitefinity.Frontend/PersonalizationDesigner/Master/PersonalizationDesigner/"; + $.get(url) + .done($.proxy(this.renderDialog, this)) + .fail(function (data) { + alert('There is a problem with loading the widget designer: ' + data); + }); + }, + + /** + * Destroys the currently opened dialog. + */ + destroyDialog: function () { + if (dialog) { + dialog.remove(); + this.widgetContext = null; + this.gridContext = null; + } + }, + + /** + * Provides the context for the currently active widget. + */ + widgetContext: null, + gridContext: null + + }; + + /** + * Register the global Sitefinity events with the pageEditor component. + */ + if (typeof ($telerik) != 'undefined') { + $telerik.$(document).on('needsModalDialog', $.proxy(sitefinity.pageEditor.openDialog, sitefinity.pageEditor)); + + $telerik.$(document).on('needsPersonalizationModalDialog', $.proxy(sitefinity.pageEditor.openPersonalizationDialog, sitefinity.pageEditor)); + + $telerik.$(document).on('needsGridModalDialog', $.proxy(sitefinity.pageEditor.openGridDialog, sitefinity.pageEditor)); + $telerik.$(document).on('modalDialogClosed', $.proxy(sitefinity.pageEditor.destroyDialog, sitefinity.pageEditor)); + $telerik.$(document).on('gridModalDialogClosed', $.proxy(sitefinity.pageEditor.destroyDialog, sitefinity.pageEditor)); + } + }); })(jQuery); diff --git a/Telerik.Sitefinity.Frontend/FeatherConfig.cs b/Telerik.Sitefinity.Frontend/FeatherConfig.cs index f7a854dd4..232ea2dfd 100644 --- a/Telerik.Sitefinity.Frontend/FeatherConfig.cs +++ b/Telerik.Sitefinity.Frontend/FeatherConfig.cs @@ -75,5 +75,68 @@ public bool LogPrecompiledViewUsage this["logPrecompiledViewUsage"] = value; } } + + /// + /// Gets or sets a value indicating whether to require parameter naming in the widget routings. + /// + /// + /// true if the routes will work only with named params (e.g /tag/london/page/2); otherwise, false when the route will be /london/2. + /// + [ConfigurationProperty("useNamedParametersRouting")] + [ObjectInfo(typeof(InfrastructureResources), Title = "UseNamedParametersRoutingCaption", Description = "UseNamedParametersRoutingDescription")] + public bool UseNamedParametersRouting + { + get + { + return (bool)this["useNamedParametersRouting"]; + } + + set + { + this["useNamedParametersRouting"] = value; + } + } + + /// + /// Gets or sets a value indicating whether cached controller assemblies to be used + /// + /// + /// true if the cached controller assemblies are used; otherwise, false + /// + [ConfigurationProperty("useCachedControllerContainerAssemblies", DefaultValue = true)] + [ObjectInfo(typeof(InfrastructureResources), Title = "UseCachedControllerContainerAssembliesTitle", Description = "UseCachedControllerContainerAssembliesDescription")] + public bool UseCachedControllerContainerAssemblies + { + get + { + return (bool)this["useCachedControllerContainerAssemblies"]; + } + + set + { + this["useCachedControllerContainerAssemblies"] = value; + } + } + + /// + /// Gets or sets a value indicating whether the Ninject kernel should automatically load extensions at startup + /// + /// + /// true if the Ninject kernel should automatically load extensions at startup; otherwise, false + /// + [ConfigurationProperty("ninjectLoadExtensions", DefaultValue = true)] + [ObjectInfo(typeof(InfrastructureResources), Title = "NinjectLoadExtensionsTitle", Description = "NinjectLoadExtensionsDescription")] + public bool NinjectLoadExtensions + { + get + { + return (bool)this["ninjectLoadExtensions"]; + } + + set + { + this["ninjectLoadExtensions"] = value; + } + } } } diff --git a/Telerik.Sitefinity.Frontend/FilesMonitoring/FileMonitor.cs b/Telerik.Sitefinity.Frontend/FilesMonitoring/FileMonitor.cs index 8cc801cf7..4f4b0435a 100644 --- a/Telerik.Sitefinity.Frontend/FilesMonitoring/FileMonitor.cs +++ b/Telerik.Sitefinity.Frontend/FilesMonitoring/FileMonitor.cs @@ -33,7 +33,7 @@ public void Start(IList directoriesInfo) { this.AddRootWatcher(); } - + foreach (var directory in directoriesInfo) { var direcotryPath = this.MapPath(directory.Path); @@ -87,10 +87,11 @@ public void Dispose() /// /// Takes appropriate actions based on the resource file type /// - /// The file path. + /// The file path. /// Type of the change. + /// The file data. /// The old file path. - protected virtual void FileChanged(string filePath, FileChangeType changeType, string oldFilePath = "") + protected virtual void FileChanged(string filePath, FileChangeType changeType, string oldFilePath = "", FileData fileData = null) { var virtualFilePath = this.ConvertToVirtualPath(filePath); var oldFileVirtualPath = this.ConvertToVirtualPath(oldFilePath); @@ -122,8 +123,10 @@ protected virtual void FileChanged(string filePath, FileChangeType changeType, s OldFilePath = oldFileVirtualPath, PackageName = packageName, ResourceFolder = resourceFolder, - FileName = fileName + FileName = fileName, + FileData = fileData }); + SystemManager.RunWithElevatedPrivilege(elevDelegate); } @@ -165,7 +168,8 @@ protected virtual void RemoveNonExistingData() /// /// Gets the resource file manager. /// - /// Type of the resource. + /// The resource folder. + /// The resource path. /// FileManager protected virtual IFileManager GetResourceFileManager(string resourceFolder, string resourcePath) { @@ -186,7 +190,8 @@ protected virtual IFileManager GetResourceFileManager(string resourceFolder, str /// Gets the type of the resource. /// /// The resource folder. - /// + /// The resource path. + /// Resource type protected virtual ResourceType? GetResourceType(string resourceFolder, string resourcePath) { if (resourcePath.Contains("/GridSystem/Templates/")) @@ -329,7 +334,28 @@ private string ConvertToVirtualPath(string path) // converting the file path to a virtual file path if (!appPhysicalPath.IsNullOrEmpty() && !path.IsNullOrEmpty()) - path = path.Substring(appPhysicalPath.Length - 1); + { + if (path.StartsWith(appPhysicalPath)) + { + // path within site root + path = path.Substring(appPhysicalPath.Length - 1); + } + else + { + // path within virtual directory + foreach (var monitoredDirectory in this.WatchedFoldersAndPackages) + { + var physicalPath = this.MapPath(monitoredDirectory.Path); + if (path.StartsWith(physicalPath)) + { + // path within site root + path = path.Substring(physicalPath.Length).Replace('\\', '/'); + path = string.Concat(monitoredDirectory.Path, path); + return path; + } + } + } + } var virtualFilePath = path.Replace('\\', '/').Insert(0, "~"); @@ -344,12 +370,19 @@ private void ProcessDirecotryFiles(DirectoryInfo dirInfo) { var files = dirInfo.GetFiles("*", SearchOption.AllDirectories); + var fileManager = FileMonitorDataManager.GetManager(); + var fileDatasList = fileManager.GetFilesData().ToList(); + foreach (var file in files) { try { - var filePath = file.FullName; - this.FileChanged(filePath, FileChangeType.Created); + var virtualFilePath = this.ConvertToVirtualPath(file.FullName); + + // Get the file monitoring data for the files that are already monitored. If the file data exist we don't have to create new monitoring for this file. + var fileData = fileDatasList.Where(x => x.FilePath.Equals(virtualFilePath, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); + + this.FileChanged(file.FullName, FileChangeType.Created, fileData: fileData); } catch (IOException ex) { @@ -503,7 +536,7 @@ private SystemManager.RunWithElevatedPrivilegeDelegate GetFileChangedDelegate(Fi { case FileChangeType.Created: { - resourceFilesManager.FileAdded(args.FileName, args.FilePath, args.PackageName); + resourceFilesManager.FileAdded(args.FileName, args.FilePath, args.FileData, args.PackageName); break; } @@ -608,6 +641,8 @@ private class FileChangedDelegateArguments public string ResourceFolder { get; set; } public string FileName { get; set; } + + public FileData FileData { get; set; } } #endregion diff --git a/Telerik.Sitefinity.Frontend/FilesMonitoring/FileMonitoringInitializer.cs b/Telerik.Sitefinity.Frontend/FilesMonitoring/FileMonitoringInitializer.cs index 99141737d..c1111b12d 100644 --- a/Telerik.Sitefinity.Frontend/FilesMonitoring/FileMonitoringInitializer.cs +++ b/Telerik.Sitefinity.Frontend/FilesMonitoring/FileMonitoringInitializer.cs @@ -1,7 +1,11 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Configuration; using Telerik.Microsoft.Practices.Unity; using Telerik.Sitefinity.Abstractions; +using Telerik.Sitefinity.Configuration; using Telerik.Sitefinity.Frontend.Resources; +using Telerik.Sitefinity.Utilities.TypeConverters; namespace Telerik.Sitefinity.Frontend.FilesMonitoring { @@ -15,17 +19,17 @@ internal class FileMonitoringInitializer : IInitializer /// public void Initialize() { - if (!ObjectFactory.Container.IsRegistered(typeof(IFileMonitor))) + if (!ObjectFactory.IsTypeRegistered(typeof(IFileMonitor))) { ObjectFactory.Container.RegisterType(new ContainerControlledLifetimeManager()); } - if (!ObjectFactory.Container.IsRegistered(typeof(IFileManager), ResourceType.Layouts.ToString())) + if (!ObjectFactory.IsTypeRegistered(ResourceType.Layouts.ToString())) { ObjectFactory.Container.RegisterType(ResourceType.Layouts.ToString(), new ContainerControlledLifetimeManager()); } - if (!ObjectFactory.Container.IsRegistered(typeof(IFileManager), ResourceType.Grid.ToString())) + if (!ObjectFactory.IsTypeRegistered(ResourceType.Grid.ToString())) { ObjectFactory.Container.RegisterType(ResourceType.Grid.ToString(), new ContainerControlledLifetimeManager()); } @@ -49,13 +53,72 @@ public void Uninitialize() /// private void RegisterFileObservers() { - this.fileMonitor = ObjectFactory.Resolve(); + if (this.IsFileMonitoringEnabled()) + { + this.fileMonitor = ObjectFactory.Resolve(); + + var monitoredDirectories = new List(); + + monitoredDirectories.Add(new MonitoredDirectory("~/" + PackageManager.PackagesFolder, true)); + monitoredDirectories.Add(new MonitoredDirectory("~/Mvc/Views/Layouts", false)); + monitoredDirectories.Add(new MonitoredDirectory("~/GridSystem/Templates", false)); + + this.fileMonitor.Start(monitoredDirectories); + } + } + + private bool IsFileMonitoringEnabled() + { + // temporary solution to avoid any public APIs + var enableWatcherSetting = ConfigurationManager.AppSettings["sf:featherFileSystemWatcherBehaviour"]; + if (!string.IsNullOrEmpty(enableWatcherSetting)) + { + if (string.Equals(enableWatcherSetting, "true", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + else if (string.Equals(enableWatcherSetting, "false", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + ////var sectionType = TypeResolutionService.ResolveType("Telerik.Sitefinity.SiteSync.Configuration.SiteSyncConfig", false); + ////if (sectionType != null) + ////{ + //// try + //// { + //// var siteSyncConfig = Config.Get(sectionType); + //// var isTarget = (bool)siteSyncConfig["enabledAsTarget"]; + //// if (isTarget) + //// return false; + //// } + //// catch (Exception) + //// { + //// // the config is not available + //// } + ////} + + ////var sectionType = TypeResolutionService.ResolveType("Telerik.Sitefinity.Packaging.Configuration.PackagingConfig", false); + ////if (sectionType != null) + ////{ + //// try + //// { + //// var packagingConfig = Config.Get(sectionType); + //// var packagingMode = packagingConfig["packagingMode"]; - var monitoredDirectories = new List(); - monitoredDirectories.Add(new MonitoredDirectory("~/" + PackageManager.PackagesFolder, true)); - monitoredDirectories.Add(new MonitoredDirectory("~/Mvc/Views/Layouts", false)); + //// var enumType = TypeResolutionService.ResolveType("Telerik.Sitefinity.Packaging.Restriction.PackagingMode", true); + //// var enumName = Enum.GetName(enumType, packagingMode); + //// if (enumName == "Target") + //// return false; + //// } + //// catch (Exception) + //// { + //// // the config is not available + //// } + ////} - this.fileMonitor.Start(monitoredDirectories); + return true; } private IFileMonitor fileMonitor; diff --git a/Telerik.Sitefinity.Frontend/FilesMonitoring/GridFileManager.cs b/Telerik.Sitefinity.Frontend/FilesMonitoring/GridFileManager.cs index 552857f9e..7b885edb5 100644 --- a/Telerik.Sitefinity.Frontend/FilesMonitoring/GridFileManager.cs +++ b/Telerik.Sitefinity.Frontend/FilesMonitoring/GridFileManager.cs @@ -2,20 +2,10 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Web; using System.Web.Hosting; -using System.Web.Mvc; -using Telerik.Sitefinity.Abstractions; -using Telerik.Sitefinity.Configuration; -using Telerik.Sitefinity.Data; using Telerik.Sitefinity.Frontend.FilesMonitoring.Data; using Telerik.Sitefinity.Frontend.GridSystem; -using Telerik.Sitefinity.Modules.Pages; -using Telerik.Sitefinity.Modules.Pages.Configuration; -using Telerik.Sitefinity.Multisite; -using Telerik.Sitefinity.Project.Configuration; -using Telerik.Sitefinity.Services; -using Telerik.Sitefinity.Web.UI; +using Telerik.Sitefinity.Frontend.Resources; namespace Telerik.Sitefinity.Frontend.FilesMonitoring { @@ -64,16 +54,18 @@ protected virtual GridWidgetRegistrator WidgetRegistrator /// /// Process the file if such is added to the existing folder. /// - /// - /// + /// Name of the file. + /// The file path. + /// The file data. /// Name of the package. - public void FileAdded(string fileName, string filePath, string packageName = "") + public void FileAdded(string fileName, string filePath, FileData fileData, string packageName = "") { var fileMonitorDataManager = FileMonitorDataManager.GetManager(); - var fileData = fileMonitorDataManager.GetFilesData().Where(file => file.FilePath.Equals(filePath, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); - this.AddToToolboxAndFileData(fileMonitorDataManager, fileName, filePath, packageName, fileData); - fileMonitorDataManager.SaveChanges(); + if (this.AddToToolboxAndFileData(fileMonitorDataManager, fileName, filePath, packageName, fileData)) + { + fileMonitorDataManager.SaveChanges(); + } } /// @@ -108,25 +100,38 @@ public void FileRenamed(string newFileName, string oldFileName, string newFilePa var fileMonitorDataManager = FileMonitorDataManager.GetManager(); var fileData = fileMonitorDataManager.GetFilesData().Where(file => file.FilePath.Equals(oldFilePath, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); - this.AddToToolboxAndFileData(fileMonitorDataManager, newFileName, newFilePath, packageName, fileData, oldFileName); - - this.WidgetRegistrator.UpdateControlData(newFileName, oldFileName); - - fileMonitorDataManager.SaveChanges(); + if (fileData != null) + { + fileData.FilePath = newFilePath; + if (this.AddToToolboxAndFileData(fileMonitorDataManager, newFileName, newFilePath, packageName, fileData, oldFileName)) + { + this.WidgetRegistrator.UpdateControlData(newFileName, oldFileName); + fileMonitorDataManager.SaveChanges(); + } + else + { + fileMonitorDataManager.Delete(fileData); + fileMonitorDataManager.SaveChanges(); + } + } + else + { + this.FileAdded(newFileName, newFilePath, null, packageName); + } } #endregion #region Private methods - private void AddToToolboxAndFileData(FileMonitorDataManager fileMonitorDataManager, string newFileName, string newFilePath, string packageName, FileData fileData, string oldFileName = "") + private bool AddToToolboxAndFileData(FileMonitorDataManager fileMonitorDataManager, string newFileName, string newFilePath, string packageName, FileData fileData, string oldFileName = "") { if (!this.IsFileValid(newFileName, newFilePath, packageName)) - return; + return false; this.WidgetRegistrator.RegisterToolboxItem(newFileName, oldFileName); - this.CreateOrUpdateFileData(fileMonitorDataManager, newFileName, newFilePath, packageName, fileData); + return this.CreateOrUpdateFileData(fileMonitorDataManager, newFileName, newFilePath, packageName, fileData); } /// @@ -154,7 +159,13 @@ private bool IsFileInValidFolder(string filePath, string packageName = "") if (!string.IsNullOrEmpty(packageName)) expectedGridFolderStructure = expectedGridFolderStructure.Insert(0, packageName + Path.DirectorySeparatorChar); - if (directory.FullName.EndsWith(expectedGridFolderStructure, StringComparison.OrdinalIgnoreCase) && directory.FullName.StartsWith(HostingEnvironment.ApplicationPhysicalPath, StringComparison.OrdinalIgnoreCase)) + var resourcePackagesPath = FrontendManager.VirtualPathBuilder.MapPath(string.Concat("~/", PackageManager.PackagesFolder)); + if (directory.FullName.EndsWith(expectedGridFolderStructure, StringComparison.OrdinalIgnoreCase) && + ( + directory.FullName.StartsWith(HostingEnvironment.ApplicationPhysicalPath, StringComparison.OrdinalIgnoreCase) || + directory.FullName.StartsWith(resourcePackagesPath, StringComparison.OrdinalIgnoreCase) + ) + ) isFileInValidFolder = true; return isFileInValidFolder; @@ -173,19 +184,34 @@ private bool IsFileValid(string fileName, string filePath, string packageName) var absolutePath = FrontendManager.VirtualPathBuilder.MapPath(filePath); var isSupproted = extension == GridFileManager.GridTemplateExtension; - var isValid = isSupproted && !packageName.IsNullOrEmpty() && this.IsFileInValidFolder(absolutePath, packageName); + var isValid = isSupproted && this.IsFileInValidFolder(absolutePath, packageName); return isValid; } - private void CreateOrUpdateFileData(FileMonitorDataManager dataManager, string fileName, string filePath, string packageName, FileData fileData) + private bool CreateOrUpdateFileData(FileMonitorDataManager dataManager, string fileName, string filePath, string packageName, FileData fileData) { + var changed = false; if (fileData == null) + { fileData = dataManager.CreateFileData(); + fileData.FilePath = filePath; + changed = true; + } + + if (fileData.FileName != fileName) + { + fileData.FileName = fileName; + changed = true; + } + + if (fileData.PackageName != packageName) + { + fileData.PackageName = packageName; + changed = true; + } - fileData.FilePath = filePath; - fileData.FileName = fileName; - fileData.PackageName = packageName; + return changed; } /// diff --git a/Telerik.Sitefinity.Frontend/FilesMonitoring/IFileManager.cs b/Telerik.Sitefinity.Frontend/FilesMonitoring/IFileManager.cs index 8127af86b..8097346ae 100644 --- a/Telerik.Sitefinity.Frontend/FilesMonitoring/IFileManager.cs +++ b/Telerik.Sitefinity.Frontend/FilesMonitoring/IFileManager.cs @@ -1,4 +1,6 @@ -namespace Telerik.Sitefinity.Frontend.FilesMonitoring +using Telerik.Sitefinity.Frontend.FilesMonitoring.Data; + +namespace Telerik.Sitefinity.Frontend.FilesMonitoring { /// /// Classes that implement this interface should define the Sitefinity's behavior when a file is moved over the application folder structure. @@ -10,8 +12,9 @@ internal interface IFileManager /// /// Name of the file. /// The file path. + /// The file data. /// Name of the package. - void FileAdded(string fileName, string filePath, string packageName = ""); + void FileAdded(string fileName, string filePath, FileData fileData, string packageName = ""); /// /// Reacts on file deletion. diff --git a/Telerik.Sitefinity.Frontend/FilesMonitoring/LayoutFileManager.cs b/Telerik.Sitefinity.Frontend/FilesMonitoring/LayoutFileManager.cs index e5c190568..2f2bfc6dc 100644 --- a/Telerik.Sitefinity.Frontend/FilesMonitoring/LayoutFileManager.cs +++ b/Telerik.Sitefinity.Frontend/FilesMonitoring/LayoutFileManager.cs @@ -2,17 +2,23 @@ using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.IO; using System.Linq; using System.Reflection; +using System.Security.Cryptography; +using System.Text; using System.Web.Hosting; using System.Web.Mvc; using Telerik.Sitefinity.Abstractions; using Telerik.Sitefinity.Data; using Telerik.Sitefinity.Frontend.FilesMonitoring.Data; using Telerik.Sitefinity.Frontend.GridSystem; +using Telerik.Sitefinity.Frontend.Resources; using Telerik.Sitefinity.Libraries.Model; using Telerik.Sitefinity.Modules.Libraries; +using Telerik.Sitefinity.Modules.Libraries.ImageProcessing; +using Telerik.Sitefinity.Modules.Libraries.Images; using Telerik.Sitefinity.Modules.Pages; using Telerik.Sitefinity.Multisite; using Telerik.Sitefinity.Pages.Model; @@ -20,7 +26,6 @@ using Telerik.Sitefinity.Services.RelatedData.Messages; using Telerik.Sitefinity.Taxonomies; using Telerik.Sitefinity.Taxonomies.Model; -using Telerik.Sitefinity.Web; using Telerik.Sitefinity.Web.UI; namespace Telerik.Sitefinity.Frontend.FilesMonitoring @@ -62,25 +67,12 @@ public virtual ICollection FolderPathStructure /// The id of the category. public virtual Guid GetOrCreateTemplateCategoryId(string templateCategoryName, bool createIfNotExist = true) { - var taxonomyManager = TaxonomyManager.GetManager(); - - var pageTemplatesTaxonomy = taxonomyManager.GetTaxonomy(SiteInitializer.PageTemplatesTaxonomyId); - var templateCategory = pageTemplatesTaxonomy.Taxa.SingleOrDefault(t => t.Name.Equals(templateCategoryName, StringComparison.OrdinalIgnoreCase)); + if (string.IsNullOrWhiteSpace(templateCategoryName)) + return SiteInitializer.CustomTemplatesCategoryId; - if (templateCategory == null && createIfNotExist) - { - templateCategory = taxonomyManager.CreateTaxon(); - templateCategory.Name = templateCategoryName; - templateCategory.UrlName = templateCategoryName; - templateCategory.RenderAsLink = false; - templateCategory.Title = templateCategoryName; - templateCategory.Description = string.Format("Represents category for {0} page templates.", templateCategoryName); - - pageTemplatesTaxonomy.Taxa.Add(templateCategory); - taxonomyManager.SaveChanges(); - } + var createdIds = this.CreateTemplateCategories(new HashSet() { templateCategoryName }, createIfNotExist); - return templateCategory.Id; + return createdIds[0]; } /// @@ -108,14 +100,16 @@ public virtual void AttachImageToTemplate(PageTemplate template, PageManager pag /// /// The virtual file name. /// The virtual file path. + /// The file data /// Name of the package. - public void FileAdded(string fileName, string filePath, string packageName = "") + public void FileAdded(string fileName, string filePath, FileData fileData, string packageName = "") { var fileMonitorDataManager = FileMonitorDataManager.GetManager(); - var fileData = fileMonitorDataManager.GetFilesData().Where(file => file.FilePath.Equals(filePath, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); - - this.CreateTemplateAndFileData(fileName, filePath, packageName, fileMonitorDataManager, fileData); + if (fileData == null && this.CreateTemplateAndFileData(fileName, filePath, packageName, fileMonitorDataManager, ref fileData)) + { + fileMonitorDataManager.SaveChanges(); + } } /// @@ -151,73 +145,175 @@ public void FileRenamed(string newFileName, string oldFileName, string newFilePa var fileData = fileMonitorDataManager.GetFilesData().Where(file => file.FilePath.Equals(oldFilePath, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); if (fileData != null) - this.CreateTemplateAndFileData(newFileName, newFilePath, packageName, fileMonitorDataManager, fileData); + { + fileData.FilePath = newFilePath; + if (this.CreateTemplateAndFileData(newFileName, newFilePath, packageName, fileMonitorDataManager, ref fileData)) + { + fileMonitorDataManager.SaveChanges(); + } + else + { + fileMonitorDataManager.Delete(fileData); + fileMonitorDataManager.SaveChanges(); + } + } else - this.FileAdded(newFileName, newFilePath, packageName); + { + this.FileAdded(newFileName, newFilePath, null, packageName); + } } - internal void CreateDefaultTemplates(string packageName, string layoutFile) + #endregion + + #region Internal methods + + /// + /// The same logic can be found in PageTemplateHelper -> UpdateDefaultTemplateImages(). In need of a change do it in both places + /// + internal static void UpdateDefaultTemplateImages(LibrariesManager librariesManager) { - var header = new LayoutControlDescription() + var imagesToUpgrade = new List + { + LayoutFileManager.TemplateImage1Column, + LayoutFileManager.TemplateImage2Columns, + LayoutFileManager.TemplateImage3Columns, + LayoutFileManager.TemplateImage4Columns, + LayoutFileManager.TemplateImageLeftSidebar, + LayoutFileManager.TemplateImageRightSidebar, + "default" + }; + + var templateThumbsImageLibrary = librariesManager.GetAlbums().FirstOrDefault(lib => lib.Id == LibrariesModule.DefaultTemplateThumbnailsLibraryId); + if (templateThumbsImageLibrary != null) + { + var images = templateThumbsImageLibrary.Images().ToList(); + foreach (var imageToUpgrade in imagesToUpgrade) + { + var image = images.FirstOrDefault(i => i.Title == "MVC_" + imageToUpgrade && i.Status == GenericContent.Model.ContentLifecycleStatus.Master); + + if (image != null) { - Caption = LayoutFileManager.LayoutCaptionHeader, - Description = LayoutFileManager.LayoutDescriptionHeader, - Path = LayoutFileManager.GridTemplatePathContainer - }; + var iconResource = string.Format(CultureInfo.InvariantCulture, LayoutFileManager.PageTemplateIconPathFormat, imageToUpgrade); + if (Assembly.GetExecutingAssembly().GetManifestResourceNames().Any(mrn => mrn.Equals(iconResource, StringComparison.OrdinalIgnoreCase))) + { + using (var imageStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(iconResource)) + { + using (var resourceImage = ImagesHelper.CurrentImageProcessor.GetImageFromStream(imageStream)) + { + var resourceImageStream = new MemoryStream(); + resourceImage.Save(resourceImageStream, System.Drawing.Imaging.ImageFormat.Png); + + librariesManager.Upload(image, resourceImageStream, ".png", true); + librariesManager.Lifecycle.Publish(image); + librariesManager.SaveChanges(); + } + } + } + } + } + } + } + + internal IList CreateTemplateCategories(ISet categoryNames, bool createIfNotExist) + { + var categoryIds = new List(); + var taxonomyManager = TaxonomyManager.GetManager(); + + foreach (var templateCategoryName in categoryNames) + { + var pageTemplatesTaxonomy = taxonomyManager.GetTaxonomy(SiteInitializer.PageTemplatesTaxonomyId); + var templateCategory = pageTemplatesTaxonomy.Taxa.SingleOrDefault(t => t.Name != null && t.Name.Equals(templateCategoryName, StringComparison.OrdinalIgnoreCase)); + + if (templateCategory == null && createIfNotExist) + { + var guid = this.GuidFromString(templateCategoryName); + templateCategory = taxonomyManager.CreateTaxon(guid); + templateCategory.Name = templateCategoryName; + templateCategory.UrlName = templateCategoryName; + templateCategory.RenderAsLink = false; + templateCategory.Title = templateCategoryName; + templateCategory.Description = string.Format(CultureInfo.InvariantCulture, "Represents category for {0} page templates.", templateCategoryName); + + pageTemplatesTaxonomy.Taxa.Add(templateCategory); + categoryIds.Add(guid); + } + else + { + categoryIds.Add(templateCategory.Id); + } + } + + taxonomyManager.SaveChanges(); + + return categoryIds; + } + + internal void CreateDefaultTemplates(string packageName, string layoutFile) + { + var header = new LayoutControlDescription() + { + Caption = LayoutFileManager.LayoutCaptionHeader, + Description = LayoutFileManager.LayoutDescriptionHeader, + Path = LayoutFileManager.GridTemplatePathContainer + }; var content = new LayoutControlDescription() - { - Caption = LayoutFileManager.LayoutCaptionContent, - Description = LayoutFileManager.LayoutDescriptionContent, - Path = LayoutFileManager.GridTemplatePathContainer - }; + { + Caption = LayoutFileManager.LayoutCaptionContent, + Description = LayoutFileManager.LayoutDescriptionContent, + Path = LayoutFileManager.GridTemplatePathContainer + }; var footer = new LayoutControlDescription() - { - Caption = LayoutFileManager.LayoutCaptionFooter, - Description = LayoutFileManager.LayoutDescriptionFooter, - Path = LayoutFileManager.GridTemplatePathContainer - }; + { + Caption = LayoutFileManager.LayoutCaptionFooter, + Description = LayoutFileManager.LayoutDescriptionFooter, + Path = LayoutFileManager.GridTemplatePathContainer + }; var twoColumns = new LayoutControlDescription() - { - Caption = LayoutFileManager.LayoutCaption2Columns, - Description = LayoutFileManager.LayoutDescriptionContentColumns, - Path = LayoutFileManager.GridTemplatePath2Columns - }; + { + Caption = LayoutFileManager.LayoutCaption2Columns, + Description = LayoutFileManager.LayoutDescriptionContentColumns, + Path = LayoutFileManager.GridTemplatePath2Columns + }; var threeColumns = new LayoutControlDescription() - { - Caption = LayoutFileManager.LayoutCaption3Columns, - Description = LayoutFileManager.LayoutDescriptionContentColumns, - Path = LayoutFileManager.GridTemplatePath3Columns - }; + { + Caption = LayoutFileManager.LayoutCaption3Columns, + Description = LayoutFileManager.LayoutDescriptionContentColumns, + Path = LayoutFileManager.GridTemplatePath3Columns + }; var fourColumns = new LayoutControlDescription() - { - Caption = LayoutFileManager.LayoutCaption4Columns, - Description = LayoutFileManager.LayoutDescriptionContentColumns, - Path = LayoutFileManager.GridTemplatePath4Columns - }; + { + Caption = LayoutFileManager.LayoutCaption4Columns, + Description = LayoutFileManager.LayoutDescriptionContentColumns, + Path = LayoutFileManager.GridTemplatePath4Columns + }; var leftSidebar = new LayoutControlDescription() - { - Caption = LayoutFileManager.LayoutCaptionContentSidebar, - Description = LayoutFileManager.LayoutDescriptionContentSidebar, - Path = LayoutFileManager.GridTemplatePathLeftSidebar - }; + { + Caption = LayoutFileManager.LayoutCaptionContentSidebar, + Description = LayoutFileManager.LayoutDescriptionContentSidebar, + Path = LayoutFileManager.GridTemplatePathLeftSidebar + }; var rightSidebar = new LayoutControlDescription() - { - Caption = LayoutFileManager.LayoutCaptionContentSidebar, - Description = LayoutFileManager.LayoutDescriptionContentSidebar, - Path = LayoutFileManager.GridTemplatePathRightSidebar - }; + { + Caption = LayoutFileManager.LayoutCaptionContentSidebar, + Description = LayoutFileManager.LayoutDescriptionContentSidebar, + Path = LayoutFileManager.GridTemplatePathRightSidebar + }; + + if (!packageName.StartsWith("Bootstrap4") && !packageName.StartsWith("Bootstrap5")) + { + this.CreateTemplate(packageName, layoutFile, LayoutFileManager.TemplateCaption2Columns, LayoutFileManager.TemplateImage2Columns, new LayoutControlDescription[] { header, twoColumns, footer }); + this.CreateTemplate(packageName, layoutFile, LayoutFileManager.TemplateCaption3Columns, LayoutFileManager.TemplateImage3Columns, new LayoutControlDescription[] { header, threeColumns, footer }); + this.CreateTemplate(packageName, layoutFile, LayoutFileManager.TemplateCaption4Columns, LayoutFileManager.TemplateImage4Columns, new LayoutControlDescription[] { header, fourColumns, footer }); + } this.CreateTemplate(packageName, layoutFile, LayoutFileManager.TemplateCaption1Column, LayoutFileManager.TemplateImage1Column, new LayoutControlDescription[] { header, content, footer }); - this.CreateTemplate(packageName, layoutFile, LayoutFileManager.TemplateCaption2Columns, LayoutFileManager.TemplateImage2Columns, new LayoutControlDescription[] { header, twoColumns, footer }); - this.CreateTemplate(packageName, layoutFile, LayoutFileManager.TemplateCaption3Columns, LayoutFileManager.TemplateImage3Columns, new LayoutControlDescription[] { header, threeColumns, footer }); - this.CreateTemplate(packageName, layoutFile, LayoutFileManager.TemplateCaption4Columns, LayoutFileManager.TemplateImage4Columns, new LayoutControlDescription[] { header, fourColumns, footer }); this.CreateTemplate(packageName, layoutFile, LayoutFileManager.TemplateCaptionLeftSidebar, LayoutFileManager.TemplateImageLeftSidebar, new LayoutControlDescription[] { header, leftSidebar, footer }); this.CreateTemplate(packageName, layoutFile, LayoutFileManager.TemplateCaptionRightSidebar, LayoutFileManager.TemplateImageRightSidebar, new LayoutControlDescription[] { header, rightSidebar, footer }); } @@ -252,7 +348,13 @@ private bool IsFileInValidFolder(string filePath, string packageName = "") if (!string.IsNullOrEmpty(packageName)) expectedLayoutFolderStructure = packageName + Path.DirectorySeparatorChar + expectedLayoutFolderStructure; - if (directory.FullName.EndsWith(expectedLayoutFolderStructure, StringComparison.OrdinalIgnoreCase) && directory.FullName.StartsWith(HostingEnvironment.ApplicationPhysicalPath, StringComparison.OrdinalIgnoreCase)) + var resourcePackagesPath = FrontendManager.VirtualPathBuilder.MapPath(string.Concat("~/", PackageManager.PackagesFolder)); + if (directory.FullName.EndsWith(expectedLayoutFolderStructure, StringComparison.OrdinalIgnoreCase) && + ( + directory.FullName.StartsWith(HostingEnvironment.ApplicationPhysicalPath, StringComparison.OrdinalIgnoreCase) || + directory.FullName.StartsWith(resourcePackagesPath, StringComparison.OrdinalIgnoreCase) + ) + ) isFileInValidFolder = true; return isFileInValidFolder; @@ -266,31 +368,59 @@ private bool IsFileInValidFolder(string filePath, string packageName = "") /// Name of the package. /// The file monitor data manager. /// The file data. - private void CreateTemplateAndFileData(string fileName, string filePath, string packageName, FileMonitorDataManager fileMonitorDataManager, FileData fileData) + /// + private bool CreateTemplateAndFileData(string fileName, string filePath, string packageName, FileMonitorDataManager fileMonitorDataManager, ref FileData fileData) { var absolutePath = FrontendManager.VirtualPathBuilder.MapPath(filePath); - if (!this.IsFileInValidFolder(absolutePath, packageName)) - return; - - var extension = fileName.Split('.').LastOrDefault(); - var fileNameWithoutExtension = fileName.Substring(0, fileName.Length - (extension.Length + 1)); + if (this.IsFileInValidFolder(absolutePath, packageName)) + { + var extension = fileName.Split('.').LastOrDefault(); + var fileNameWithoutExtension = fileName.Substring(0, fileName.Length - (extension.Length + 1)); - var viewFileExtensions = this.GetViewExtensions(); + var viewFileExtensions = this.GetViewExtensions(); - if (viewFileExtensions.Contains(extension, StringComparer.Ordinal)) - { - if (fileData == null) - fileData = fileMonitorDataManager.CreateFileData(); + if (viewFileExtensions.Contains(extension, StringComparer.Ordinal)) + { + var changed = false; + if (fileData == null) + { + fileData = fileMonitorDataManager.CreateFileData(); + fileData.FilePath = filePath; + fileData.FileName = fileName; + fileData.PackageName = packageName; + changed = true; + } + else + { + if (fileData.FilePath != filePath) + { + fileData.FilePath = filePath; + changed = true; + } - fileData.FilePath = filePath; - fileData.FileName = fileName; - fileData.PackageName = packageName; + if (fileData.FileName != fileName) + { + fileData.FileName = fileName; + changed = true; + } - fileMonitorDataManager.SaveChanges(); + if (fileData.PackageName != packageName) + { + fileData.PackageName = packageName; + changed = true; + } + } - this.CreateTemplate(packageName, fileNameWithoutExtension); + if (changed) + { + this.CreateTemplate(packageName, fileNameWithoutExtension); + return true; + } + } } + + return false; } /// @@ -327,26 +457,29 @@ private void CreateTemplate(string packageName, string fileNameWithoutExtension, if (!pageManager.GetTemplates().Any(pt => (string.Compare(pt.Name, fullTemplateName, true) == 0 && string.Compare(pt.Title, title, true) == 0) || string.Compare(pt.Title, fullTemplateName, true) == 0)) { - var template = pageManager.CreateTemplate(); + var templateGuid = this.GuidFromString(string.Concat(fullTemplateName, title)); + var template = pageManager.CreateTemplate(templateGuid); template.Category = this.GetOrCreateTemplateCategoryId(packageName); template.Name = fullTemplateName; template.Title = title; template.Framework = Pages.Model.PageTemplateFramework.Mvc; template.Theme = ThemeController.NoThemeName; - var languageData = pageManager.CreatePublishedInvarianLanguageData(); + var languageData = pageManager.CreatePublishedLanguageData(); template.LanguageData.Add(languageData); this.AttachImageToTemplate(template, pageManager, image ?? "default"); this.AddLayoutControls(pageManager, template, layoutControls); - pageManager.SaveChanges(); - var master = pageManager.TemplatesLifecycle.Edit(template); pageManager.TemplatesLifecycle.Publish(master); pageManager.SaveChanges(); + if (string.Equals(LayoutFileManager.Bootstrap5DefaultTemplateName, fullTemplateName, StringComparison.OrdinalIgnoreCase)) + this.CreateDefaultTemplates("Bootstrap5", "default"); + if (string.Equals(LayoutFileManager.Bootstrap4DefaultTemplateName, fullTemplateName, StringComparison.OrdinalIgnoreCase)) + this.CreateDefaultTemplates("Bootstrap4", "default"); if (string.Equals(LayoutFileManager.BootstrapDefaultTemplateName, fullTemplateName, StringComparison.OrdinalIgnoreCase)) this.CreateDefaultTemplates("Bootstrap", "default"); else if (string.Equals(LayoutFileManager.FoundationDefaultTemplateName, fullTemplateName, StringComparison.OrdinalIgnoreCase)) @@ -356,6 +489,10 @@ private void CreateTemplate(string packageName, string fileNameWithoutExtension, } } } + catch (Exception) + { + Log.Write("Automatic template generation failed. To disable the automatic generation, set 'sf:featherFileSystemWatcherBehaviour' to false in the AppSettings in web.config.", ConfigurationPolicy.ErrorLog); + } finally { if (multisiteContext != null) @@ -406,7 +543,7 @@ private Image GetTemplateImage(PageTemplate template, string imageName) if (templateThumbsImageLibrary != null) { // Try get image from library - image = templateThumbsImageLibrary.Images().FirstOrDefault(i => i.Title.Equals("MVC_" + imageName, StringComparison.OrdinalIgnoreCase)); + image = templateThumbsImageLibrary.Images().FirstOrDefault(i => i.Title == "MVC_" + imageName && i.Status == GenericContent.Model.ContentLifecycleStatus.Master); if (image == null) { // Check if image is in the resources and upload it @@ -443,7 +580,7 @@ private Image UploadTemplateImage(LibrariesManager libraryManager, Album templat using (var imageStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(iconResource)) { - using (var resourceImage = System.Drawing.Image.FromStream(imageStream)) + using (var resourceImage = ImagesHelper.CurrentImageProcessor.GetImageFromStream(imageStream)) { var resourceImageStream = new MemoryStream(); resourceImage.Save(resourceImageStream, System.Drawing.Imaging.ImageFormat.Png); @@ -474,8 +611,8 @@ private void AddTemplatePresentation(PageTemplate template, PageManager pageMana private void AddTemplateRelatedData(PageTemplate template, Image image, PageManager pageManager) { - var changedRelations = new ContentLinkChange[] - { + var changedRelations = new ContentLinkChange[] + { new ContentLinkChange() { ChildItemId = image.Id, @@ -492,6 +629,15 @@ private void AddTemplateRelatedData(PageTemplate template, Image image, PageMana method.Invoke(null, new object[] { pageManager, template, changedRelations, false }); } + private Guid GuidFromString(string key) + { + using (var md5 = MD5.Create()) + { + byte[] hash = md5.ComputeHash(Encoding.Default.GetBytes(key)); + return new Guid(hash); + } + } + #endregion #region Private fileds @@ -523,15 +669,19 @@ private class LayoutControlDescription /// public const string PageTemplateIconPathFormat = "Telerik.Sitefinity.Frontend.Resources.PageTemplateImages.{0}.gif"; + public const string Bootstrap5DefaultTemplateName = "Bootstrap5.default"; + public const string Bootstrap4DefaultTemplateName = "Bootstrap4.default"; public const string BootstrapDefaultTemplateName = "Bootstrap.default"; public const string SemanticUIDefaultTemplateName = "SemanticUI.default"; public const string FoundationDefaultTemplateName = "Foundation.default"; - public static readonly string[] DefaultTemplateNames = new string[] - { - LayoutFileManager.BootstrapDefaultTemplateName, - LayoutFileManager.SemanticUIDefaultTemplateName, - LayoutFileManager.FoundationDefaultTemplateName + public static readonly string[] DefaultTemplateNames = new string[] + { + LayoutFileManager.Bootstrap5DefaultTemplateName, + LayoutFileManager.Bootstrap4DefaultTemplateName, + LayoutFileManager.BootstrapDefaultTemplateName, + LayoutFileManager.SemanticUIDefaultTemplateName, + LayoutFileManager.FoundationDefaultTemplateName }; private const string TemplateCaption1Column = "1 Column, Header, Footer"; diff --git a/Telerik.Sitefinity.Frontend/FilesMonitoring/MonitoredDirectory.cs b/Telerik.Sitefinity.Frontend/FilesMonitoring/MonitoredDirectory.cs index 0d4223e6a..57415c2f1 100644 --- a/Telerik.Sitefinity.Frontend/FilesMonitoring/MonitoredDirectory.cs +++ b/Telerik.Sitefinity.Frontend/FilesMonitoring/MonitoredDirectory.cs @@ -36,7 +36,7 @@ public MonitoredDirectory(string path, bool isPackage) public bool IsPackage { get; set; } /// - /// Determines whether the specified , is equal to this instance. /// /// The to compare with this instance. /// diff --git a/Telerik.Sitefinity.Frontend/FrontendModule.cs b/Telerik.Sitefinity.Frontend/FrontendModule.cs index e433eff2a..4ba38c3ed 100644 --- a/Telerik.Sitefinity.Frontend/FrontendModule.cs +++ b/Telerik.Sitefinity.Frontend/FrontendModule.cs @@ -2,13 +2,24 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using System.Web.Mvc; using Ninject; -using Telerik.OpenAccess; +using Ninject.Modules; using Telerik.Sitefinity.Abstractions; using Telerik.Sitefinity.Configuration; using Telerik.Sitefinity.Data; +using Telerik.Sitefinity.Frontend; +using Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Controllers; using Telerik.Sitefinity.Services; +[assembly: SitefinityModule(FrontendModule.ModuleName, + typeof(FrontendModule), + "Feather", + "Modern, intuitive, convention based, mobile-first UI for Progress Sitefinity CMS.", + StartupType.OnApplicationStart)] + namespace Telerik.Sitefinity.Frontend { /// @@ -27,7 +38,7 @@ public static FrontendModule Current { get { - return (FrontendModule)SystemManager.GetModule("Feather"); + return (FrontendModule)SystemManager.GetModule(FrontendModule.ModuleName); } } @@ -46,7 +57,7 @@ public override Guid LandingPageId /// An array of objects. public override Type[] Managers { - get { return new Type[0]; } + get { return ManagerTypes; } } /// @@ -59,7 +70,7 @@ public IKernel DependencyResolver { get { - return this.ninjectDependencyResolver; + return ninjectDependencyResolver; } } @@ -73,24 +84,34 @@ public override void Install(SiteInitializer initializer) } /// - /// Initializes the service with specified settings. + /// Initializes the module with specified settings. /// /// The settings. public override void Initialize(ModuleSettings settings) { base.Initialize(settings); - Bootstrapper.Initialized -= this.Bootstrapper_Initialized; - Bootstrapper.Initialized += this.Bootstrapper_Initialized; - - this.ninjectDependencyResolver = this.CreateKernel(); - - FrontendModuleInstaller.Initialize(this.DependencyResolver); - App.WorkWith() .Module(settings.Name) .Initialize() .Configuration(); + + this.PreloadControllerTypeCacheAsync(); + } + + /// + /// Integrate the module into the system. + /// + public override void Load() + { + base.Load(); + + this.InitializeDependencyResolver(); + + FrontendModuleInstaller.Initialize(); + + Bootstrapper.Initialized -= this.Bootstrapper_Initialized; + Bootstrapper.Initialized += this.Bootstrapper_Initialized; } /// @@ -123,7 +144,7 @@ public override void Uninstall(SiteInitializer initializer) public override void Upgrade(SiteInitializer initializer, Version upgradeFrom) { base.Upgrade(initializer, upgradeFrom); - FrontendModuleUpgrader.Upgrade(upgradeFrom); + FrontendModuleUpgrader.Upgrade(upgradeFrom, initializer); } /// @@ -134,6 +155,17 @@ protected override ConfigSection GetModuleConfig() return Config.Get(); } + /// + /// Gets the meta data aggregation mode of the module persistence. + /// + protected override ManagersInitializationMode ManagersInitializationMode + { + get + { + return ManagersInitializationMode.OnStartup; + } + } + /// /// Creates Ninject kernel. /// @@ -141,11 +173,17 @@ protected override ConfigSection GetModuleConfig() protected virtual IKernel CreateKernel() { var bootstrapper = new Ninject.Web.Common.Bootstrapper(); - + IKernel kernel; if (bootstrapper.Kernel != null) - return bootstrapper.Kernel; + kernel = bootstrapper.Kernel; + else + { + var ninjectSettings = new NinjectSettings(); + ninjectSettings.LoadExtensions = Config.Get().NinjectLoadExtensions; + kernel = new SitefinityKernel(ninjectSettings); + } - return new StandardKernel(); + return kernel; } /// @@ -156,18 +194,84 @@ protected virtual IKernel CreateKernel() protected virtual void Bootstrapper_Initialized(object sender, ExecutedEventArgs e) { if (e.CommandName == "Bootstrapped") - FrontendModuleInstaller.Bootstrapper_Initialized(this.initializers.Value); + { + using (new HealthMonitoring.MethodPerformanceRegion("Feather")) + { + FrontendModuleInstaller.Bootstrapper_Initialized(this.initializers.Value); + } + } } - + // Called both by Unload and Uninstall private void Uninitialize() { - if (this.ninjectDependencyResolver != null && !this.ninjectDependencyResolver.IsDisposed) - this.ninjectDependencyResolver.Dispose(); + this.UninitializeDependencyResolver(); Bootstrapper.Initialized -= this.Bootstrapper_Initialized; } + /// + /// Triggers the initialization of the controller type cache for the default controller factory asynchroniously. + /// + private void PreloadControllerTypeCacheAsync() + { + Task.Run(() => this.InitializeControllerTypeCache()); + } + + /// + /// Initializes the controller type cache of the default controller factory. + /// + private void InitializeControllerTypeCache() + { + DefaultControllerFactory defaultControllerFactory = + System.Web.Mvc.DependencyResolver.Current.GetService() as DefaultControllerFactory ?? + (ControllerBuilder.Current.GetControllerFactory() as DefaultControllerFactory ?? new DefaultControllerFactory()); + + MethodInfo getControllerTypesMethod = defaultControllerFactory.GetType().GetMethod("GetControllerTypes", BindingFlags.Instance | BindingFlags.NonPublic); + getControllerTypesMethod.Invoke(defaultControllerFactory, null); + } + + private void InitializeDependencyResolver() + { + if (ninjectDependencyResolver != null) + return; + + ninjectDependencyResolver = this.CreateKernel(); + var assemblies = new ControllerContainerInitializer().ControllerContainerAssemblies; + var loadedModules = ninjectDependencyResolver.GetModules(); + + foreach (var assembly in assemblies) + { + var assemblyModules = this.GetNinjectModules(assembly); + + // check assembly for already registered ninject modules + var registeredAssemblyModules = assemblyModules.Where(module => loadedModules.Where(loadedModule => loadedModule.Name.Equals(module.Name, StringComparison.OrdinalIgnoreCase)).Any()); + if (registeredAssemblyModules.Any()) + { + foreach (var module in assemblyModules) + { + if (!registeredAssemblyModules.Any(registeredModule => registeredModule.Name.Equals(module.Name, StringComparison.OrdinalIgnoreCase))) + { + ninjectDependencyResolver.Load(module); + } + } + } + else + { + ninjectDependencyResolver.Load(assembly); + } + } + } + + private void UninitializeDependencyResolver() + { + if (ninjectDependencyResolver != null && !ninjectDependencyResolver.IsDisposed && ninjectDependencyResolver is SitefinityKernel) + { + ninjectDependencyResolver.Dispose(); + ninjectDependencyResolver = null; + } + } + private Lazy> initializers = new Lazy>(() => { try @@ -185,6 +289,40 @@ private void Uninitialize() } }); - private IKernel ninjectDependencyResolver; + private IEnumerable GetNinjectModules(Assembly assembly) + { + return assembly.GetExportedTypes().Where(new Func(this.IsLoadableModule)).Select((Type type) => Activator.CreateInstance(type) as INinjectModule); + } + + private bool IsLoadableModule(Type type) + { + if (!typeof(INinjectModule).IsAssignableFrom(type) || type.IsAbstract || type.IsInterface) + { + return false; + } + + return type.GetConstructor(Type.EmptyTypes) != null; + } + + /// + /// The name. + /// + public const string ModuleName = "Feather"; + + private static IKernel ninjectDependencyResolver; + private static readonly Type[] ManagerTypes = new Type[] { typeof(FilesMonitoring.Data.FileMonitorDataManager) }; + + private class SitefinityKernel : StandardKernel + { + public SitefinityKernel() + : base() + { + } + + public SitefinityKernel(INinjectSettings settings) + : base(settings) + { + } + } } } diff --git a/Telerik.Sitefinity.Frontend/FrontendModuleInstaller.cs b/Telerik.Sitefinity.Frontend/FrontendModuleInstaller.cs index 113037207..3fb0520a4 100644 --- a/Telerik.Sitefinity.Frontend/FrontendModuleInstaller.cs +++ b/Telerik.Sitefinity.Frontend/FrontendModuleInstaller.cs @@ -36,14 +36,15 @@ public static void Install(SiteInitializer initializer) /// /// Initializes the specified settings. /// - /// The ninject dependency resolver. - public static void Initialize(IKernel ninjectDependencyResolver) + public static void Initialize() { SystemManager.RegisterServiceStackPlugin(new ListsServiceStackPlugin()); SystemManager.RegisterServiceStackPlugin(new FilesServiceStackPlugin()); - SystemManager.RegisterServiceStackPlugin(new ReviewsServiceStackPlugin()); - - ninjectDependencyResolver.Load(new ControllerContainerInitializer().ControllerContainerAssemblies); + IModule module = SystemManager.GetApplicationModule("Comments"); + if (module != null && !(module is InactiveModule)) + { + SystemManager.RegisterServiceStackPlugin(new ReviewsServiceStackPlugin()); + } } /// @@ -53,7 +54,12 @@ public static void Initialize(IKernel ninjectDependencyResolver) public static void Bootstrapper_Initialized(IEnumerable initializers) { foreach (var initializer in initializers) - initializer.Initialize(); + { + using (new HealthMonitoring.MethodPerformanceRegion("Initializing '{0}'.".Arrange(initializer.GetType().Name))) + { + initializer.Initialize(); + } + } ObjectFactory.Container.RegisterType(new ContainerControlledLifetimeManager()); } @@ -96,8 +102,6 @@ private static void RenameControllers(SiteInitializer initializer) property.Value = "Telerik.Sitefinity.Frontend.ContentBlock.Mvc.Controllers.ContentBlockController"; else if (property.Value == "Navigation.Mvc.Controllers.NavigationController") property.Value = "Telerik.Sitefinity.Frontend.Navigation.Mvc.Controllers.NavigationController"; - else if (property.Value == "SocialShare.Mvc.Controllers.SocialShareController") - property.Value = "Telerik.Sitefinity.Frontend.SocialShare.Mvc.Controllers.SocialShareController"; else if (property.Value == "DynamicContent.Mvc.Controllers.DynamicContentController") property.Value = "Telerik.Sitefinity.Frontend.DynamicContent.Mvc.Controllers.DynamicContentController"; } @@ -126,7 +130,7 @@ private static void ClearToolboxItems() private static void RemoveToolboxItemIfExists(ToolboxSection mvcWidgetsSection, string toolName) { var tool = mvcWidgetsSection.Tools.FirstOrDefault(t => t.Name == toolName); - if (tool != null) + if (tool != null && tool.Source == ConfigSource.Database) mvcWidgetsSection.Tools.Remove(tool); } diff --git a/Telerik.Sitefinity.Frontend/FrontendModuleUninstaller.cs b/Telerik.Sitefinity.Frontend/FrontendModuleUninstaller.cs index 1c6ebf7c3..033701f64 100644 --- a/Telerik.Sitefinity.Frontend/FrontendModuleUninstaller.cs +++ b/Telerik.Sitefinity.Frontend/FrontendModuleUninstaller.cs @@ -32,39 +32,6 @@ public static void Unload(IEnumerable initializers) public static void Uninstall(IEnumerable initializers) { FrontendModuleUninstaller.Uninitialize(initializers); - - var featherWidgetTypes = new List(); - var configManager = ConfigManager.GetManager(); - var toolboxesConfig = configManager.GetSection(); - - foreach (var toolbox in toolboxesConfig.Toolboxes.Values) - { - ICollection emptySections = new List(); - foreach (var section in toolbox.Sections) - { - var featherWidgets = ((ICollection)section.Tools) - .Where(i => - i.ControlType.StartsWith("Telerik.Sitefinity.Frontend", StringComparison.Ordinal) || - (!i.ControllerType.IsNullOrEmpty() && i.ControllerType.StartsWith("Telerik.Sitefinity.Frontend", StringComparison.Ordinal))); - featherWidgetTypes.AddRange(featherWidgets.Select(t => t.ControllerType)); - - var mvcToolsToDelete = featherWidgets.Select(i => i.GetKey()); - foreach (var key in mvcToolsToDelete) - { - section.Tools.Remove(section.Tools.Elements.SingleOrDefault(e => e.GetKey() == key)); - } - - if (section.Tools.Count == 0) - emptySections.Add(section); - } - - foreach (var emptySection in emptySections) - { - toolbox.Sections.Remove(emptySection); - } - } - - configManager.SaveSection(toolboxesConfig); } // Called both by Unload and Uninstall diff --git a/Telerik.Sitefinity.Frontend/FrontendModuleUpgrader.cs b/Telerik.Sitefinity.Frontend/FrontendModuleUpgrader.cs index 51ee0db12..307418959 100644 --- a/Telerik.Sitefinity.Frontend/FrontendModuleUpgrader.cs +++ b/Telerik.Sitefinity.Frontend/FrontendModuleUpgrader.cs @@ -7,6 +7,7 @@ using Telerik.Sitefinity.Frontend.FilesMonitoring; using Telerik.Sitefinity.Frontend.GridSystem; using Telerik.Sitefinity.Frontend.Resources; +using Telerik.Sitefinity.Modules.Libraries; using Telerik.Sitefinity.Modules.Pages; using Telerik.Sitefinity.Modules.Pages.Configuration; using Telerik.Sitefinity.Pages.Model; @@ -22,7 +23,8 @@ internal static class FrontendModuleUpgrader /// Upgrades the specified upgrade from. /// /// The upgrade from. - public static void Upgrade(Version upgradeFrom) + /// The site initializer. + public static void Upgrade(Version upgradeFrom, SiteInitializer initializer) { if (upgradeFrom < new Version(1, 2, 140, 0)) { @@ -56,6 +58,21 @@ public static void Upgrade(Version upgradeFrom) FrontendModuleUpgrader.UpdateGridWidgetsToolbox(); FrontendModuleUpgrader.UpdateGridWidgetPaths(); } + + if (upgradeFrom <= new Version(1, 7, 600, 0)) + { + FrontendModuleUpgrader.UpgradeLimitCountProperty(initializer); + } + + if (upgradeFrom < new Version(12, 0)) + { + FrontendModuleUpgrader.UpdateDefaultTemplateImages(); + } + + if (upgradeFrom < new Version(14, 1)) + { + FrontendModuleUpgrader.CreateBootstrap5Templates(); + } } // 1, 2, 140, 0 @@ -69,7 +86,7 @@ private static void DeleteOldGridSection() if (layoutsToolbox != null) { var htmlLayoutsSection = layoutsToolbox.Sections.FirstOrDefault(s => s.Name == "HtmlLayouts"); - if (htmlLayoutsSection != null) + if (htmlLayoutsSection != null && htmlLayoutsSection.Source == ConfigSource.Database) { layoutsToolbox.Sections.Remove(htmlLayoutsSection); configManager.SaveSection(toolboxConfig); @@ -129,12 +146,13 @@ private static void RemoveMvcWidgetToolboxItems() var widgets = section.Tools.Where(t => t.ControllerType.StartsWith("Telerik.Sitefinity.Frontend.", StringComparison.Ordinal) && !t.ControllerType.StartsWith("Telerik.Sitefinity.Frontend.DynamicContent", StringComparison.Ordinal)).ToArray(); foreach (ToolboxItem tool in widgets) { - section.Tools.Remove(tool); + if (tool.Source == ConfigSource.Database) + section.Tools.Remove(tool); } } var mvcWidgetsSection = pageControls.Sections.FirstOrDefault(s => s.Name == "MvcWidgets"); - if (mvcWidgetsSection != null) + if (mvcWidgetsSection != null && mvcWidgetsSection.Source == ConfigSource.Database) { pageControls.Sections.Remove(mvcWidgetsSection); } @@ -227,6 +245,24 @@ private static void CreateDefaultTemplates() } } + private static void UpdateDefaultTemplateImages() + { + var librariesManager = LibrariesManager.GetManager("SystemLibrariesProvider"); + if (librariesManager != null) + { + var suppressSecurityChecks = librariesManager.Provider.SuppressSecurityChecks; + try + { + librariesManager.Provider.SuppressSecurityChecks = true; + LayoutFileManager.UpdateDefaultTemplateImages(librariesManager); + } + finally + { + librariesManager.Provider.SuppressSecurityChecks = suppressSecurityChecks; + } + } + } + // 1, 3, 320, 0 private static void UpdateGridWidgetsToolbox() { @@ -257,7 +293,8 @@ private static void TransferGridWidgetSectionToDefault(string sectionName) var pageControls = toolboxesConfig.Toolboxes["PageLayouts"]; var sectionToDelete = pageControls.Sections.FirstOrDefault(e => e.Name == sectionName); - pageControls.Sections.Remove(sectionToDelete); + if (sectionToDelete != null && sectionToDelete.Source == ConfigSource.Database) + pageControls.Sections.Remove(sectionToDelete); configurationManager.SaveSection(toolboxesConfig); } @@ -305,5 +342,64 @@ private static void UpdateGridWidgetPaths() currentControl += BATCH; } } + + // 1, 7, 600, 0 + private static void UpgradeLimitCountProperty(SiteInitializer initializer) + { + var pageMan = initializer.PageManager; + string[] controllersList = + { + "Telerik.Sitefinity.Frontend.Blogs.Mvc.Controllers.BlogController", + "Telerik.Sitefinity.Frontend.Blogs.Mvc.Controllers.BlogPostController", + "Telerik.Sitefinity.Frontend.News.Mvc.Controllers.NewsController", + "Telerik.Sitefinity.Frontend.Events.Mvc.Controllers.EventController", + "Telerik.Sitefinity.Frontend.Media.Mvc.Controllers.ImageGalleryController", + "Telerik.Sitefinity.Frontend.Media.Mvc.Controllers.VideoGalleryController", + "Telerik.Sitefinity.Frontend.Media.Mvc.Controllers.DocumentsListController", + "Telerik.Sitefinity.Frontend.DynamicContent.Mvc.Controllers.DynamicContentController", + "Telerik.Sitefinity.Frontend.Identity.Mvc.Controllers.UsersListController", + "Telerik.Sitefinity.Frontend.Search.Mvc.Controllers.SearchResultsController" + }; + + foreach (var controller in controllersList) + { + var controlIds = pageMan.GetProperties() + .Where(x => x.Name == "ControllerName" && x.Value == controller) + .Select(x => x.Control.Id).ToList(); + + foreach (var controlId in controlIds) + { + var settingids = pageMan.GetProperties().Where(x => x.Name == "Settings" && x.Control.Id == controlId).Select(x => x.Id).ToList(); + foreach (var settingId in settingids) + { + var models = pageMan.GetProperties().Where(x => x.Name == "Model" && x.ParentProperty.Id == settingId).ToList(); + foreach (var model in models) + { + var itemsPerPage = pageMan.GetProperties().Where(x => x.Name == "ItemsPerPage" && x.ParentProperty.Id == model.Id).FirstOrDefault(); + if (itemsPerPage != null) + { + var limitCount = pageMan.GetProperties().Where(x => x.Name == "LimitCount" && x.ParentProperty.Id == model.Id).FirstOrDefault(); + if (limitCount == null) + { + limitCount = pageMan.CreateProperty(); + pageMan.CopyProperty(itemsPerPage, limitCount); + limitCount.Name = "LimitCount"; + limitCount.Language = itemsPerPage.Language; + limitCount.ParentProperty = model; + } + } + } + } + } + } + } + + // 14.1 + private static void CreateBootstrap5Templates() + { + var layoutManager = new LayoutFileManager(); + + layoutManager.CreateDefaultTemplates("Bootstrap5", "default"); + } } } diff --git a/Telerik.Sitefinity.Frontend/GlobalSuppressions.cs b/Telerik.Sitefinity.Frontend/GlobalSuppressions.cs index 79a6161e8..eec0e2562 100644 Binary files a/Telerik.Sitefinity.Frontend/GlobalSuppressions.cs and b/Telerik.Sitefinity.Frontend/GlobalSuppressions.cs differ diff --git a/Telerik.Sitefinity.Frontend/GridSystem/GridControl.cs b/Telerik.Sitefinity.Frontend/GridSystem/GridControl.cs index 951167512..03cb18e0b 100644 --- a/Telerik.Sitefinity.Frontend/GridSystem/GridControl.cs +++ b/Telerik.Sitefinity.Frontend/GridSystem/GridControl.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.IO; using System.Linq; @@ -6,15 +6,17 @@ using System.Web.Hosting; using System.Web.UI; using Telerik.Sitefinity.Configuration; +using Telerik.Sitefinity.Frontend.Mvc.Infrastructure; using Telerik.Sitefinity.Frontend.Mvc.StringResources; using Telerik.Sitefinity.Localization; using Telerik.Sitefinity.Modules.Newsletters; +using Telerik.Sitefinity.Modules.Pages; using Telerik.Sitefinity.Services; using Telerik.Sitefinity.Utilities.HtmlParsing; using Telerik.Sitefinity.Utilities.TypeConverters; using Telerik.Sitefinity.Web; -using Telerik.Sitefinity.Web.Configuration; -using Telerik.Sitefinity.Web.UI; +using Telerik.Sitefinity.Web.Configuration; +using Telerik.Sitefinity.Web.UI; namespace Telerik.Sitefinity.Frontend.GridSystem { @@ -55,7 +57,6 @@ protected virtual string ProcessLayoutString(string targetTemplate, bool ensureS parser.AutoExtractBetweenTagsOnly = false; parser.CompressWhiteSpaceBeforeTag = false; parser.KeepRawHTML = true; - bool hasSfCols = false; var output = new StringBuilder(); HtmlChunk chunk; while ((chunk = parser.ParseNext()) != null) @@ -67,9 +68,7 @@ protected virtual string ProcessLayoutString(string targetTemplate, bool ensureS if (cssClass != null) { var classes = cssClass.Split(new char[] { ' ' }); - var chunkHasSfCols = classes.Contains("sf_cols", StringComparer.Ordinal); - hasSfCols = hasSfCols || chunkHasSfCols; - if (chunkHasSfCols || + if (classes.Contains("sf_cols", StringComparer.Ordinal) || classes.Contains("sf_colsIn", StringComparer.Ordinal) || classes.Contains("sf_colsOut", StringComparer.Ordinal)) { @@ -82,7 +81,7 @@ protected virtual string ProcessLayoutString(string targetTemplate, bool ensureS output.Append(modified ? chunk.GenerateHtml() : chunk.Html); } - if (!hasSfCols && ensureSfColsWrapper) + if (ensureSfColsWrapper) { return "" + output.ToString() + ""; } @@ -103,8 +102,8 @@ protected virtual ITemplate GetTemplate(bool isVirtualPath, bool isHtmlTemplate, if (isVirtualPath && isHtmlTemplate) { if (!HostingEnvironment.VirtualPathProvider.FileExists(layout)) - { - throw new ArgumentException(Res.Get("CannotFindTemplate", layout)); + { + throw new ArgumentException(Res.Get("CannotFindTemplateMvcForm", layout)); } using (var reader = new StreamReader(HostingEnvironment.VirtualPathProvider.GetFile(layout).Open())) @@ -129,10 +128,10 @@ protected virtual ITemplate GetTemplate(bool isVirtualPath, bool isHtmlTemplate, } // Add sf_cols wrapper for back end pages and email campaigns. - var currentNode = SiteMapBase.GetActualCurrentNode(); - var rootNode = currentNode != null ? currentNode.RootNode as PageSiteNode : null; - var ensureSfColsWrapper = this.IsBackend() || rootNode == null || rootNode.Id == NewslettersModule.standardCampaignRootNodeId || - System.Web.HttpContext.Current.Items[SiteMapBase.CurrentNodeKey] == null; + var ensureSfColsWrapper = (this.IsBackend() && !SystemManager.IsPreviewMode) + || SiteMapBase.GetActualCurrentNode()?.RootNodeId == NewslettersModule.standardCampaignRootNodeId + || System.Web.HttpContext.Current.Items[SiteMapBase.CurrentNodeKey] == null; + layout = this.ProcessLayoutString(layout, ensureSfColsWrapper); return ControlUtilities.GetTemplate(null, layout.GetHashCode().ToString(System.Globalization.CultureInfo.InvariantCulture), null, layout); diff --git a/Telerik.Sitefinity.Frontend/GridSystem/GridControlToolboxFilter.cs b/Telerik.Sitefinity.Frontend/GridSystem/GridControlToolboxFilter.cs index 29a46ce08..ce019848b 100644 --- a/Telerik.Sitefinity.Frontend/GridSystem/GridControlToolboxFilter.cs +++ b/Telerik.Sitefinity.Frontend/GridSystem/GridControlToolboxFilter.cs @@ -1,5 +1,11 @@ using System; +using Telerik.Sitefinity.Configuration; using Telerik.Sitefinity.DesignerToolbox; +using Telerik.Sitefinity.Frontend.Mvc.Helpers; +using Telerik.Sitefinity.Modules.Newsletters; +using Telerik.Sitefinity.Modules.Newsletters.Configuration; +using Telerik.Sitefinity.Modules.Newsletters.Web; +using Telerik.Sitefinity.Newsletters.Model; using Telerik.Sitefinity.Pages.Model; using Telerik.Sitefinity.Services; using Telerik.Sitefinity.Utilities.TypeConverters; @@ -25,11 +31,12 @@ public GridControlToolboxFilter(Func frameworkExtractor) /// Determines whether a toolbox section should be visible in the . /// /// The section in question. + /// The filter context passed externally. /// true if it should be visible. - public bool IsSectionVisible(IToolboxSection section) + public bool IsSectionVisible(IToolboxSection section, IToolboxFilterContext context) { - if (section != null && section.Name == GridWidgetRegistrator.GridSectionName && section.Title == GridWidgetRegistrator.GridSectionTitle) - return SystemManager.GetModule("Feather") != null; + if (section != null && context != null && section.Name == GridWidgetRegistrator.GridSectionName && section.Title == GridWidgetRegistrator.GridSectionTitle) + return SystemManager.GetModule("Feather") != null && context.ContainerId == "LayoutToolboxContainer"; return true; } @@ -46,9 +53,21 @@ public bool IsToolVisible(IToolboxItem tool) var currentFramework = this.frameworkExtractor(); - // Everything works for Hybrid mode. No need to reflect on the specific item. + // Pages: Everything works for Hybrid mode. No need to reflect on the specific item. + // Newsletters: Always run in Hybrid mode. Message body type needs check. if (currentFramework == PageTemplateFramework.Hybrid) - return true; + { + var currentHttpContext = SystemManager.CurrentHttpContext; + + if (NewslettersHelper.IsMvcNewsletter()) + { + currentFramework = PageTemplateFramework.Mvc; + } + else + { + return true; + } + } if (!tool.ControlType.IsNullOrEmpty()) { @@ -69,6 +88,28 @@ public bool IsToolVisible(IToolboxItem tool) return true; } + private bool IsNewslettersModuleEnabled + { + get + { + if (this.isNewslettersModuleEnabled == null) + { + try + { + Config.Get(); + this.isNewslettersModuleEnabled = true; + } + catch (Exception) + { + this.isNewslettersModuleEnabled = false; + } + } + + return this.isNewslettersModuleEnabled.Value; + } + } + + private bool? isNewslettersModuleEnabled; private readonly Func frameworkExtractor; } } diff --git a/Telerik.Sitefinity.Frontend/GridSystem/GridWidgetRegistrator.cs b/Telerik.Sitefinity.Frontend/GridSystem/GridWidgetRegistrator.cs index 66a43e6a1..c39967fff 100644 --- a/Telerik.Sitefinity.Frontend/GridSystem/GridWidgetRegistrator.cs +++ b/Telerik.Sitefinity.Frontend/GridSystem/GridWidgetRegistrator.cs @@ -23,29 +23,59 @@ internal class GridWidgetRegistrator /// Old name of the file. public void RegisterToolboxItem(string fileName, string oldFileName = "") { - var configManager = ConfigManager.GetManager(); - using (new ElevatedConfigModeRegion()) + ConfigManager configManager = null; + var toolboxConfig = Config.Get(); + var layoutsToolbox = toolboxConfig.Toolboxes[ToolboxesConfig.LayoutsToolboxName]; + var htmlLayoutsSection = layoutsToolbox.Sections.FirstOrDefault(s => s.Name == GridWidgetRegistrator.GridSectionName); + if (htmlLayoutsSection == null) + { + configManager = ConfigManager.GetManager(); + toolboxConfig = configManager.GetSection(); + layoutsToolbox = toolboxConfig.Toolboxes[ToolboxesConfig.LayoutsToolboxName]; + htmlLayoutsSection = this.CreateToolBoxSection(layoutsToolbox, GridWidgetRegistrator.GridSectionName, GridWidgetRegistrator.GridSectionTitle); + } + + var layoutControl = this.CreateGridControlsData(fileName); + + string toolboxItemName; + if (oldFileName.IsNullOrEmpty()) + { + toolboxItemName = layoutControl.Name; + } + else { - var toolboxConfig = configManager.GetSection(); - var sectionName = GridWidgetRegistrator.GridSectionName; - var sectionTitle = GridWidgetRegistrator.GridSectionTitle; - bool needsSaveSection = false; - var htmlLayoutsSection = this.GetOrCreateToolBoxSection(toolboxConfig, sectionName, sectionTitle, ref needsSaveSection); + var oldFileNameWithoutExtension = this.GetFileNameWithoutExtension(oldFileName); + toolboxItemName = oldFileNameWithoutExtension; + } - var layoutControl = this.CreateGridControlsData(fileName); - this.AddOrRenameGridControl(htmlLayoutsSection.Tools, layoutControl, ref needsSaveSection, oldFileName); + ToolboxItem toolboxItem = null; + if (configManager == null) + toolboxItem = htmlLayoutsSection.Tools.FirstOrDefault(t => t.Name == toolboxItemName); - if (needsSaveSection) + if (toolboxItem == null || (toolboxItem.Name != layoutControl.Name || toolboxItem.Title != layoutControl.Title || toolboxItem.LayoutTemplate != layoutControl.LayoutTemplatePath)) + { + if (configManager == null) { - configManager.SaveSection(toolboxConfig); + configManager = ConfigManager.GetManager(); + toolboxConfig = configManager.GetSection(); + layoutsToolbox = toolboxConfig.Toolboxes[ToolboxesConfig.LayoutsToolboxName]; + htmlLayoutsSection = layoutsToolbox.Sections.FirstOrDefault(s => s.Name == GridWidgetRegistrator.GridSectionName); } + + this.AddOrRenameGridControl(htmlLayoutsSection, layoutControl, toolboxItemName); + } + + if (configManager != null) + { + using (new ElevatedConfigModeRegion()) + configManager.SaveSection(toolboxConfig); } } /// /// Unregisters the toolbox item. /// - /// Name of the content type. + /// Name of the content type. public virtual void UnregisterToolboxItem(string fileName) { var configurationManager = ConfigManager.GetManager(); @@ -59,12 +89,12 @@ public virtual void UnregisterToolboxItem(string fileName) var fileNameWithoutExtension = this.GetFileNameWithoutExtension(fileName); var itemToDelete = section.Tools.FirstOrDefault(e => e.Name == fileNameWithoutExtension); - if (itemToDelete != null) + if (itemToDelete != null && itemToDelete.Source == ConfigSource.Database) { section.Tools.Remove(itemToDelete); } - if (!section.Tools.Any()) + if (!section.Tools.Any() && section.Source == ConfigSource.Database) { pageControls.Sections.Remove(section); } @@ -85,7 +115,7 @@ public void UpdateControlData(string newFileName, string oldFileName) var baseTemplatePath = string.Format( CultureInfo.InvariantCulture, GridWidgetRegistrator.GridFolderPathStringTemplate, - FrontendManager.VirtualPathBuilder.GetVirtualPath(typeof(FrontendService).Assembly)); + FrontendManager.VirtualPathBuilder.GetVirtualPath(typeof(FrontendModule).Assembly)); var properties = pageManger.GetProperties().Where(prop => prop.Name == "Layout" && prop.Value == baseTemplatePath + oldFileName).ToList(); var newCaption = this.GetFileNameWithoutExtension(newFileName); @@ -105,26 +135,19 @@ public void UpdateControlData(string newFileName, string oldFileName) /// /// Gets existing or create a tool box section for the grid controls. /// - /// The toolbox configuration. + /// The toolbox configuration. /// Name of the section. /// The section title. - /// - protected virtual ToolboxSection GetOrCreateToolBoxSection(ToolboxesConfig toolboxConfig, string sectionName, string sectionTitle, ref bool needsSaveSection) + /// The section. + protected virtual ToolboxSection CreateToolBoxSection(Toolbox toolbox, string sectionName, string sectionTitle) { - var layoutsToolbox = toolboxConfig.Toolboxes["PageLayouts"]; + var htmlLayoutsSection = new ToolboxSection(toolbox.Sections); + htmlLayoutsSection.Name = sectionName; - var htmlLayoutsSection = layoutsToolbox.Sections.FirstOrDefault(s => s.Name == sectionName); - if (htmlLayoutsSection == null) - { - htmlLayoutsSection = new ToolboxSection(layoutsToolbox.Sections); - htmlLayoutsSection.Name = sectionName; + /// TODO: Set resource class id and use resource keys for Title and Description. + htmlLayoutsSection.Title = sectionTitle; - /// TODO: Set resource class id and use resource keys for Title and Description. - htmlLayoutsSection.Title = sectionTitle; - - layoutsToolbox.Sections.Add(htmlLayoutsSection); - needsSaveSection = true; - } + toolbox.Sections.Add(htmlLayoutsSection); return htmlLayoutsSection; } @@ -132,41 +155,25 @@ protected virtual ToolboxSection GetOrCreateToolBoxSection(ToolboxesConfig toolb /// /// Adds or renames the grid control. /// - /// The parent. + /// The parent toolbox section. /// The data. - /// Old name of the file. + /// Old name of the file. /// data - protected virtual void AddOrRenameGridControl(ConfigElementList parent, GridControlData data, ref bool needsSaveSection, string oldFileName = "") + protected virtual void AddOrRenameGridControl(ToolboxSection section, GridControlData data, string toolboxItemName = default(string)) { if (data == null) throw new ArgumentNullException("data"); - string toolboxItemName; - if (oldFileName.IsNullOrEmpty()) - { - toolboxItemName = data.Name; - } - else - { - var oldFileNameWithoutExtension = this.GetFileNameWithoutExtension(oldFileName); - toolboxItemName = oldFileNameWithoutExtension; - } + var tools = section.Tools; - var control = parent.FirstOrDefault(t => t.Name == toolboxItemName); + var control = tools.FirstOrDefault(t => t.Name == toolboxItemName); if (control == null) { - control = new ToolboxItem(parent); + control = new ToolboxItem(tools); control.ControlType = typeof(GridControl).AssemblyQualifiedName; control.CssClass = data.CssClass; - parent.Add(control); - - needsSaveSection = true; - } - - if (!needsSaveSection) - { - needsSaveSection = control.Name != data.Name || control.Title != data.Title || control.LayoutTemplate != data.LayoutTemplatePath; + tools.Add(control); } control.Name = data.Name; @@ -184,7 +191,7 @@ protected GridControlData CreateGridControlsData(string fileName) var baseTemplatePath = string.Format( System.Globalization.CultureInfo.InvariantCulture, GridWidgetRegistrator.GridFolderPathStringTemplate, - FrontendManager.VirtualPathBuilder.GetVirtualPath(typeof(FrontendService).Assembly)); + FrontendManager.VirtualPathBuilder.GetVirtualPath(typeof(FrontendModule).Assembly)); var fileNameWithoutExtension = this.GetFileNameWithoutExtension(fileName); var cssClass = this.GetInferredCssClass(fileNameWithoutExtension); diff --git a/Telerik.Sitefinity.Frontend/Modules/Comments/ReviewNotificationStrategy.cs b/Telerik.Sitefinity.Frontend/Modules/Comments/ReviewNotificationStrategy.cs index ef5f4139e..8c537930a 100644 --- a/Telerik.Sitefinity.Frontend/Modules/Comments/ReviewNotificationStrategy.cs +++ b/Telerik.Sitefinity.Frontend/Modules/Comments/ReviewNotificationStrategy.cs @@ -4,6 +4,7 @@ using Telerik.Sitefinity.Services.Comments; using Telerik.Sitefinity.Services.Comments.Impl.Notifications; using Telerik.Sitefinity.Services.Comments.Notifications; +using Telerik.Sitefinity.Services.Notifications; namespace Telerik.Sitefinity.Frontend.Modules.Comments { @@ -16,42 +17,33 @@ internal class ReviewNotificationStrategy : CommentNotificationsStrategy, IComme /// Gets the message template unique identifier for the specified event. /// /// The event. - protected override Guid GetMessageTemplateId(ICommentEvent @event) + protected override IMessageTemplateRequest GetMessageTemplate(ICommentEvent @event) { - IComment comment = @event.Item; + if (@event == null || @event.Item == null) + return null; + IMessageTemplateRequest messageTemplate; + IComment comment = @event.Item; ICommentService cs = SystemManager.GetCommentsService(); IThread thread = cs.GetThread(comment.ThreadKey); - var ns = SystemManager.GetNotificationService(); - Guid messageTemplateId; - if (this.IsReviewThread(thread)) { - messageTemplateId = ns.GetMessageTemplates(this.ServiceContext, null) - .Where(mt => mt.Subject == "A new review was posted") - .Select(m => m.Id).FirstOrDefault(); + messageTemplate = this.GetNewReviewMessageTemplate(); } else { - messageTemplateId = ns.GetMessageTemplates(this.ServiceContext, null) - .Where(mt => mt.Subject == "A new comment was posted") - .Select(m => m.Id).FirstOrDefault(); - } - - if (messageTemplateId == Guid.Empty) - { - messageTemplateId = base.GetMessageTemplateId(@event); + messageTemplate = this.GetNewCommentMessageTemplate(); } - return messageTemplateId; + return messageTemplate; } private bool IsReviewThread(IThread thread) { var isReview = false; - if (thread.Behavior == null) + if (thread.Behavior.IsNullOrEmpty()) { isReview = thread.Key.EndsWith("_review", StringComparison.Ordinal); } diff --git a/Telerik.Sitefinity.Frontend/Modules/ControlTemplates/Web/Scripts/MvcControlTemplateEditor.js b/Telerik.Sitefinity.Frontend/Modules/ControlTemplates/Web/Scripts/MvcControlTemplateEditor.js index d711734e7..a167d157b 100644 --- a/Telerik.Sitefinity.Frontend/Modules/ControlTemplates/Web/Scripts/MvcControlTemplateEditor.js +++ b/Telerik.Sitefinity.Frontend/Modules/ControlTemplates/Web/Scripts/MvcControlTemplateEditor.js @@ -13,11 +13,12 @@ Telerik.Sitefinity.Frontend.Modules.ControlTemplates.Web.UI.MvcControlTemplateEd initialize: function () { Telerik.Sitefinity.Frontend.Modules.ControlTemplates.Web.UI.MvcControlTemplateEditor.callBaseMethod(this, "initialize"); - this._containerToHide = jQuery(this._otherPropertiesContainer).parent(); + this._containerToHide = this.getInsertSectionContainer(); + this._containerToHide.hide(); }, dispose: function () { - Telerik.Sitefinity.Modules.ControlTemplates.Web.UI.MvcControlTemplateEditor.callBaseMethod(this, "dispose"); + Telerik.Sitefinity.Frontend.Modules.ControlTemplates.Web.UI.MvcControlTemplateEditor.callBaseMethod(this, "dispose"); }, _valueChangedHandler: function (sender, args) { diff --git a/Telerik.Sitefinity.Frontend/Mvc/Controllers/CompositePrecompiledMvcEngineWrapper.cs b/Telerik.Sitefinity.Frontend/Mvc/Controllers/CompositePrecompiledMvcEngineWrapper.cs index 8209d958e..6432268f4 100644 --- a/Telerik.Sitefinity.Frontend/Mvc/Controllers/CompositePrecompiledMvcEngineWrapper.cs +++ b/Telerik.Sitefinity.Frontend/Mvc/Controllers/CompositePrecompiledMvcEngineWrapper.cs @@ -81,12 +81,33 @@ public CompositePrecompiledMvcEngineWrapper Clone() /// Base64 string of the MD5 hash of the data in the stream. internal static string ComputeHash(Stream stream) { - using (var md5 = MD5.Create()) + using (var algorithm = SHA256.Create()) { - return Convert.ToBase64String(md5.ComputeHash(stream)); + return Convert.ToBase64String(algorithm.ComputeHash(stream)); } } + internal IEnumerable GetViews(IEnumerable viewLocations) + { + var views = new List(); + if (Config.Get().DisablePrecompilation) + return views; + + foreach (var viewLocation in viewLocations) + { + foreach (var asm in this.precompiledAssemblies) + { + var precompiledViews = asm.GetViews(viewLocation); + if (precompiledViews != null) + { + views.AddRange(precompiledViews); + } + } + } + + return views; + } + /// /// Files the exists. /// @@ -95,7 +116,7 @@ internal static string ComputeHash(Stream stream) /// protected override bool FileExists(ControllerContext controllerContext, string virtualPath) { - return !Config.Get().DisablePrecompilation && base.FileExists(controllerContext, virtualPath) && (Config.Get().AlwaysUsePrecompiledVersion || this.ShouldServe(virtualPath)); + return !Config.Get().DisablePrecompilation && base.FileExists(controllerContext, virtualPath) && (Config.Get().AlwaysUsePrecompiledVersion || this.ShouldServe(controllerContext, virtualPath)); } /// @@ -138,8 +159,14 @@ private void LogPrecompiledViewUsage(ControllerContext controllerContext, string } } - private bool ShouldServe(string virtualPath) + // If for any of the location we find newer version of the file outside the precompiled assebly we add "servePrecompiled" flag in the RotueData. + // This way we ensure that view with bigger prority will be served instead of the first view found from the precompiled view engine. + // See #299642 + private bool ShouldServe(ControllerContext controllerContext, string virtualPath) { + if (controllerContext.RouteData.Values.ContainsKey("servePrecompiled") && controllerContext.RouteData.Values["servePrecompiled"].ToString() == "false") + return false; + string precompiledFileHash = null; foreach (var asm in this.precompiledAssemblies) { @@ -157,7 +184,14 @@ private bool ShouldServe(string virtualPath) virtualResourceHash = this.VirtualResourceHash(virtualPath); } - return virtualResourceHash == null || virtualResourceHash == precompiledFileHash; + var shouldServe = virtualResourceHash == null || virtualResourceHash == precompiledFileHash; + + if (!shouldServe) + { + controllerContext.RouteData.Values.Add("servePrecompiled", "false"); + } + + return shouldServe; } private string VirtualResourceHash(string virtualPath) diff --git a/Telerik.Sitefinity.Frontend/Mvc/Controllers/ContentPagerController.cs b/Telerik.Sitefinity.Frontend/Mvc/Controllers/ContentPagerController.cs index 6d7c62be1..c3941a4e4 100644 --- a/Telerik.Sitefinity.Frontend/Mvc/Controllers/ContentPagerController.cs +++ b/Telerik.Sitefinity.Frontend/Mvc/Controllers/ContentPagerController.cs @@ -1,10 +1,11 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Reflection; using System.Web.Mvc; using Telerik.Sitefinity.Frontend.Mvc.Helpers.ViewModels; +using Telerik.Sitefinity.Frontend.Mvc.Infrastructure; using Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Controllers.Attributes; +using Telerik.Sitefinity.Frontend.Mvc.StringResources; +using Telerik.Sitefinity.Web; using Telerik.Sitefinity.Web.UI; namespace Telerik.Sitefinity.Frontend.Mvc.Controllers @@ -12,7 +13,8 @@ namespace Telerik.Sitefinity.Frontend.Mvc.Controllers /// /// This class represents controller for pager widget. /// - [ControllerMetadataAttribute(IsTemplatableControl = false)] + [Localization(typeof(ContentPagerResources))] + [ControllerMetadata(IsTemplatableControl = false)] public class ContentPagerController : Controller { /// @@ -21,11 +23,12 @@ public class ContentPagerController : Controller /// The current page. /// The total pages count. /// The template of the URL used for redirecting. + /// The display count of the pages. /// [OutputCache(Duration = 1)] - public PartialViewResult Index(int currentPage, int totalPagesCount, string redirectUrlTemplate) + public PartialViewResult Index(int currentPage, int totalPagesCount, string redirectUrlTemplate, int displayCount = 10) { - var model = new PagerViewModel(currentPage, totalPagesCount, redirectUrlTemplate); + var model = new PagerViewModel(currentPage, totalPagesCount, redirectUrlTemplate, displayCount); // Build the pager int startIndex = 1; @@ -66,7 +69,66 @@ public PartialViewResult Index(int currentPage, int totalPagesCount, string redi model.NextNode = null; } + this.TryStorePaginationUrls(model); + return this.PartialView("Pager", model); } + + internal static object GetPaginationUrls(string nextUrl, string previousUrl) + { + var paginationUrlsType = Type.GetType("Telerik.Sitefinity.ContentLocations.PaginationUrls, Telerik.Sitefinity"); + var instance = Activator.CreateInstance(paginationUrlsType, nonPublic: true); + paginationUrlsType.GetProperty("PreviousUrl").SetValue(instance, previousUrl); + paginationUrlsType.GetProperty("NextUrl").SetValue(instance, nextUrl); + + return instance; + } + + internal static MethodInfo GetTryStorePaginationUrlsMethod() + { + var canonicalUrlExtensions = Type.GetType("Telerik.Sitefinity.ContentLocations.CanonicalUrlPageExtensions, Telerik.Sitefinity"); + return canonicalUrlExtensions.GetMethod("TryStorePaginationUrls", BindingFlags.Static | BindingFlags.NonPublic); + } + + private static string PageNodeUrl(Pager.PagerNumericItem node, string template) + { + if (node == null) + return null; + else + return RouteHelper.ResolveUrl(string.Format(template, node.PageNumber), UrlResolveOptions.Absolute); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "paginationUrls")] + private void TryStorePaginationUrls(PagerViewModel model) + { + string nextUrl; + var highlightedIndex = model.CurrentPage % model.DisplayCount; + if (highlightedIndex == 0) + { + highlightedIndex = model.DisplayCount; + } + + if (model.CurrentPage > 0 && model.CurrentPage < model.TotalPagesCount) + if (highlightedIndex == model.DisplayCount) + nextUrl = ContentPagerController.PageNodeUrl(model.NextNode, model.RedirectUrlTemplate); + else + nextUrl = ContentPagerController.PageNodeUrl(model.PagerNodes[highlightedIndex], model.RedirectUrlTemplate); + else + nextUrl = null; + + string previousUrl; + if (model.CurrentPage > 1 && model.CurrentPage <= model.TotalPagesCount) + if (highlightedIndex > 1) + previousUrl = ContentPagerController.PageNodeUrl(model.PagerNodes[highlightedIndex - 2], model.RedirectUrlTemplate); + else + previousUrl = ContentPagerController.PageNodeUrl(model.PreviousNode, model.RedirectUrlTemplate); + else + previousUrl = null; + + var paginationUrls = ContentPagerController.GetPaginationUrls(nextUrl, previousUrl); + + var page = this.HttpContext.Handler.GetPageHandler(); + ContentPagerController.GetTryStorePaginationUrlsMethod().Invoke(null, new object[] { page, paginationUrls }); + } } } diff --git a/Telerik.Sitefinity.Frontend/Mvc/Controllers/DesignerController.cs b/Telerik.Sitefinity.Frontend/Mvc/Controllers/DesignerController.cs index b98b4409b..67be1f5a7 100644 --- a/Telerik.Sitefinity.Frontend/Mvc/Controllers/DesignerController.cs +++ b/Telerik.Sitefinity.Frontend/Mvc/Controllers/DesignerController.cs @@ -8,6 +8,8 @@ using Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Controllers.Attributes; using Telerik.Sitefinity.Frontend.Mvc.Models; using Telerik.Sitefinity.Frontend.Mvc.StringResources; +using Telerik.Sitefinity.Modules; +using Telerik.Sitefinity.Modules.Forms; using Telerik.Sitefinity.Modules.Pages; using Telerik.Sitefinity.Pages.Model; using Telerik.Sitefinity.Services; @@ -21,23 +23,26 @@ namespace Telerik.Sitefinity.Frontend.Mvc.Controllers [RequestBackendUserAuthentication] [ControllerMetadataAttribute(IsTemplatableControl = false)] public class DesignerController : Controller - { + { /// /// Returns the designer view which handles the property editing for a particular widget. /// If there is custom designer for the particular widget it will be retrieved, otherwise it will fallback to the default designer. /// The default designer is located under . /// /// The name of the widget. - public virtual ActionResult Master(string widgetName) + /// The name of the widget dynamic module (if any). Default value is null. + public virtual ActionResult Master(string widgetName, string moduleName = null) { this.GetHttpContext().Items[SystemManager.IsBackendRequestKey] = true; - var controlId = this.Request != null ? this.Request["controlId"] ?? Guid.Empty.ToString() : Guid.Empty.ToString(); + var controlId = this.Request != null ? this.Request["controlId"] ?? Guid.Empty.ToString() : Guid.Empty.ToString(); + var mediaType = this.Request != null ? this.Request["mediaType"] ?? DesignMediaType.Page.ToString() : DesignMediaType.Page.ToString(); this.ViewBag.ControlName = widgetName; this.ViewBag.ControlId = controlId; + this.ViewBag.MediaType = mediaType; - var model = this.GetModel(widgetName, Guid.Parse(controlId)); + var model = this.GetModel(widgetName, Guid.Parse(controlId), moduleName, (DesignMediaType)Enum.Parse(typeof(DesignMediaType), mediaType)); return this.View(DesignerController.DefaultView, model); } @@ -54,15 +59,17 @@ public virtual ActionResult Master(string widgetName) { this.GetHttpContext().Items[SystemManager.IsBackendRequestKey] = true; - var viewName = DesignerController.DesignerViewTemplate.Arrange(viewType); + var viewName = DesignerController.DesignerViewTemplate.Arrange(viewType); + var mediaType = this.Request != null ? this.Request["mediaType"] ?? DesignMediaType.Page.ToString() : DesignMediaType.Page.ToString(); + var designMediaType = (DesignMediaType)Enum.Parse(typeof(DesignMediaType), mediaType); - var model = this.GetViewModel(); + var model = this.GetViewModel(designMediaType); // Passing the DesignerModel to the view model var controlIdParam = this.GetControlIdParam(); if (controlIdParam.HasValue) { - ViewBag.DesignerModel = this.GetModel(widgetName, controlIdParam.Value); + ViewBag.DesignerModel = this.GetModel(widgetName, controlIdParam.Value, designMediaType: designMediaType); } return this.PartialView(viewName, model); @@ -97,24 +104,25 @@ protected virtual HttpContextBase GetHttpContext() /// /// Gets the model of the designer. /// - private IDesignerModel GetModel(string widgetName, Guid controlId) + private IDesignerModel GetModel(string widgetName, Guid controlId, string moduleName = null, DesignMediaType designMediaType = DesignMediaType.Page) { var viewFilesMappgings = new Dictionary(); var constructorParameters = new Dictionary { - { "views", this.GetPartialViews(ref viewFilesMappgings) }, + { "views", this.GetPartialViews(ref viewFilesMappgings, moduleName) }, { "viewLocations", this.GetPartialViewLocations() }, { "widgetName", widgetName }, { "controlId", controlId }, { "preselectedView", this.Request != null ? this.Request["view"] : null }, - { "viewFilesMappings", viewFilesMappgings } + { "viewFilesMappings", viewFilesMappgings }, + { "mediaType", designMediaType } }; return ControllerModelFactory.GetModel(typeof(DesignerController), constructorParameters); } - private Control GetViewModel() + private Control GetViewModel(DesignMediaType designMediaType) { var controlIdParam = this.GetControlIdParam(); @@ -122,7 +130,7 @@ private Control GetViewModel() return null; var controlId = controlIdParam.Value; - var manager = PageManager.GetManager(); + var manager = this.GetControlManager(designMediaType); var viewModel = manager.LoadControl(manager.GetControl(controlId)); var widgetProxy = viewModel as MvcWidgetProxy; @@ -137,6 +145,18 @@ private Control GetViewModel() return viewModel; } + private IControlManager GetControlManager(DesignMediaType designMediaType) + { + if (designMediaType == DesignMediaType.Form) + { + return FormsManager.GetManager(); + } + else + { + return PageManager.GetManager(); + } + } + private const string DefaultView = "Designer"; private const string DesignerViewTemplate = "DesignerView.{0}"; } diff --git a/Telerik.Sitefinity.Frontend/Mvc/Controllers/PrecompiledViewAssemblyWrapper.cs b/Telerik.Sitefinity.Frontend/Mvc/Controllers/PrecompiledViewAssemblyWrapper.cs index 3ea133417..522949152 100644 --- a/Telerik.Sitefinity.Frontend/Mvc/Controllers/PrecompiledViewAssemblyWrapper.cs +++ b/Telerik.Sitefinity.Frontend/Mvc/Controllers/PrecompiledViewAssemblyWrapper.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using RazorGenerator.Mvc; @@ -56,18 +58,42 @@ public string GetFileHash(string virtualPath) if (virtualPath.StartsWith(this.basePath, StringComparison.OrdinalIgnoreCase)) { var relativePath = virtualPath.Right(virtualPath.Length - this.basePath.Length); - relativePath = Regex.Replace(relativePath, @"[ \-]", "_").Replace('/', '.'); - var resourcePath = this.assembly.GetName().Name + "." + relativePath; + var relativePathWithoutSpaces = Regex.Replace(relativePath, @"[ \-]", "_").Replace('/', '.'); + var resourcePathWithoutSpaces = this.assembly.GetName().Name + "." + relativePathWithoutSpaces; + var relativePathWithSpaces = Regex.Replace(relativePath, @"[\-]", "_").Replace('/', '.'); + var resourcePathWithSpaces = this.assembly.GetName().Name + "." + relativePathWithSpaces; - if (this.embeddedResourceHashes.ContainsKey(resourcePath)) + if (this.embeddedResourceHashes.ContainsKey(resourcePathWithoutSpaces)) { - return this.embeddedResourceHashes[resourcePath]; + return this.embeddedResourceHashes[resourcePathWithoutSpaces]; + } + else if (this.embeddedResourceHashes.ContainsKey(resourcePathWithSpaces)) + { + return this.embeddedResourceHashes[resourcePathWithSpaces]; } } return null; } + public IEnumerable GetViews(string virtualFolderPath) + { + if (virtualFolderPath.StartsWith(this.basePath, StringComparison.OrdinalIgnoreCase)) + { + var relativePath = virtualFolderPath.Right(virtualFolderPath.Length - this.basePath.Length); + var relativePathWithSpaces = Regex.Replace(relativePath, @"[\-]", "_").Replace('/', '.'); + var resourcePathWithSpaces = this.assembly.GetName().Name + "." + relativePathWithSpaces; + + var resources = this.embeddedResourceHashes.Keys + .Where(resourceKey => resourceKey.ToUpperInvariant().Contains(resourcePathWithSpaces.ToUpperInvariant())) + .Select(key => Path.GetFileNameWithoutExtension(Regex.Replace(key, resourcePathWithSpaces, string.Empty, RegexOptions.IgnoreCase))); + + return resources; + } + + return new List(); + } + private string ComputeHashForResource(string resourceName) { using (var stream = this.assembly.GetManifestResourceStream(resourceName)) diff --git a/Telerik.Sitefinity.Frontend/Mvc/Helpers/CommentsHelpers.cs b/Telerik.Sitefinity.Frontend/Mvc/Helpers/CommentsHelpers.cs index 059ef207b..f3eacb5a7 100644 --- a/Telerik.Sitefinity.Frontend/Mvc/Helpers/CommentsHelpers.cs +++ b/Telerik.Sitefinity.Frontend/Mvc/Helpers/CommentsHelpers.cs @@ -34,6 +34,12 @@ public static MvcHtmlString CommentsCount(this HtmlHelper helper, string navigat var contentItem = item as Content; bool? allowComments = contentItem != null ? contentItem.AllowComments : null; + + if (NewslettersHelper.IsNewsletter()) + { + allowComments = false; + } + var threadKey = ControlUtilities.GetLocalizedKey(item.Id, null, CommentsBehaviorUtilities.GetLocalizedKeySuffix(item.GetType().FullName)); var itemTypeFullName = item.GetType().FullName; @@ -151,13 +157,10 @@ private static string GetDataSourceName(IDataItem item) if (item != null && item is DynamicContent) { - var moduleProvider = ModuleBuilderManager.GetManager().Provider; - var itemType = item.GetType().FullName; + var itemType = item.GetType(); - var dynamicContentType = moduleProvider.GetDynamicModules() - .Where(m => m.Status == DynamicModuleStatus.Active) - .Join(moduleProvider.GetDynamicModuleTypes().Where(t => string.Concat(t.TypeNamespace, ".", t.TypeName) == itemType), m => m.Id, t => t.ParentModuleId, (m, t) => t) - .FirstOrDefault(); + var dynamicContentType = ModuleBuilderManager.GetActiveTypes() + .FirstOrDefault(t => t.TypeNamespace == itemType.Namespace && t.TypeName == itemType.Name); if (dynamicContentType != null) { @@ -205,6 +208,11 @@ private static string GetTitle(IDataItem item) } } + if (NewslettersHelper.IsNewsletter()) + { + allowComments = false; + } + return allowComments; } diff --git a/Telerik.Sitefinity.Frontend/Mvc/Helpers/ContentDataItemResolver.cs b/Telerik.Sitefinity.Frontend/Mvc/Helpers/ContentDataItemResolver.cs new file mode 100644 index 000000000..b99740b59 --- /dev/null +++ b/Telerik.Sitefinity.Frontend/Mvc/Helpers/ContentDataItemResolver.cs @@ -0,0 +1,289 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Web.Mvc; +using Telerik.Sitefinity.Abstractions; +using Telerik.Sitefinity.ContentLocations; +using Telerik.Sitefinity.Data; +using Telerik.Sitefinity.DynamicModules; +using Telerik.Sitefinity.DynamicModules.Model; +using Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Controllers; +using Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Routing; +using Telerik.Sitefinity.GenericContent.Model; +using Telerik.Sitefinity.Lifecycle; +using Telerik.Sitefinity.Model; +using Telerik.Sitefinity.Modules.GenericContent; +using Telerik.Sitefinity.Mvc.Proxy.Security; +using Telerik.Sitefinity.Services; +using Telerik.Sitefinity.Utilities.TypeConverters; +using Telerik.Sitefinity.Web; + +namespace Telerik.Sitefinity.Frontend.Mvc.Helpers +{ + /// + /// The class contains helper methods related to resolving detail content items operations. + /// + public class ContentDataItemResolver + { + /// + /// Gets the detail content item. + /// + /// The controller. + /// Contains information about the route parameters. + public IDataItem GetItemByController(ControllerBase controller, string[] routeParams) + { + string redirectUrl = null; + IDataItem item = null; + + var url = RouteHelper.GetUrlParameterString(routeParams); + + var itemType = this.GetItemType(controller); + if (itemType != null) + { + var providerNames = this.GetProviderNames(controller, itemType); + foreach (var providerName in providerNames) + { + item = this.GetItemByUrl(url, itemType, providerName, out redirectUrl); + if (item != null) + { + return item; + } + } + } + + return item; + } + + /// + /// Gets the content item by URL. + /// + /// The URL. + /// The content type of the item. + /// The provider of the item + /// The redirect URL. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "3#"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#")] + public IDataItem GetItemByUrl(string url, Type itemType, string provider, out string redirectUrl) + { + var manager = this.ResolveManager(itemType, provider); + if (manager == null) + { + redirectUrl = null; + return null; + } + + var itemIdFromQueryParam = (SystemManager.CurrentHttpContext.Request.ParamsGet("sf-itemId") ?? SystemManager.CurrentHttpContext.Items["sf-itemId"]) as string; + if (!itemIdFromQueryParam.IsNullOrEmpty()) + { + Guid itemIdGuid; + if (Guid.TryParse(itemIdFromQueryParam, out itemIdGuid)) + { + redirectUrl = null; + return manager.GetItem(itemType, itemIdGuid) as IDataItem; + } + } + + IDataItem item = null; + if (manager is IContentManager || manager is DynamicModuleManager) + { + try + { + item = this.GetItemFromUrl(manager, itemType, url, out redirectUrl); + } + catch (System.UnauthorizedAccessException e) + { + redirectUrl = null; + ItemAccessException access = new ItemAccessException(e.Message, e.InnerException); + access.Data["ItemType"] = itemType.Name; + + throw access; + } + + if (item != null) + { + var type = item.GetType(); + if (!type.Equals(itemType) && !(type.Module != null && type.Module.ScopeName == "OpenAccessDynamic" && type.BaseType != null && type.BaseType.Equals(itemType))) + { + item = null; + } + } + } + else + { + item = ((IUrlProvider)manager.Provider).GetItemFromUrl(itemType, url, out redirectUrl); + } + + var lifecycleItem = item as ILifecycleDataItem; + var lifecycleManager = manager as ILifecycleManager; + if (lifecycleItem != null && lifecycleManager != null) + { + ContentLifecycleStatus requestedStatus = ContentLocatableViewExtensions.GetRequestedItemStatus(); + if (lifecycleItem.Status != requestedStatus) + { + item = lifecycleManager.Lifecycle.GetLive(lifecycleItem); + } + + object requestedItem; + if (ContentLocatableViewExtensions.TryGetItemWithRequestedStatus(lifecycleItem, lifecycleManager, out requestedItem)) + { + item = requestedItem as IDataItem; + } + } + + return item; + } + + private IDataItem GetItemFromUrl(IManager manager, Type itemType, string url, out string redirectUrl) + { + IDataItem item = null; + var isPublished = !this.IsPreviewRequested() || this.ResolveRequestedItemStatus() == ContentLifecycleStatus.Live; + + if (manager is IContentManager) + { + var contentManager = manager as IContentManager; + item = contentManager.GetItemFromUrl(itemType, url, isPublished, out redirectUrl); + } + else + { + var dynamicManager = manager as DynamicModuleManager; + item = dynamicManager.Provider.GetItemFromUrl(itemType, url, isPublished, out redirectUrl); + } + + return item; + } + + /// + /// Gets the type of the content item for specified controller. + /// + /// The controller. + private Type GetItemType(ControllerBase controller) + { + var controllerType = controller.GetType(); + Type contentType = null; + var detailsAction = new ReflectedControllerDescriptor(controllerType).FindAction(controller.ControllerContext, DetailActionParamsMapper.DefaultActionName); + if (detailsAction != null) + { + var contentParam = detailsAction.GetParameters().FirstOrDefault(); + if (contentParam != null && contentParam.ParameterType.ImplementsInterface(typeof(IDataItem))) + { + if (typeof(DynamicContent) == contentParam.ParameterType) + { + var controllerName = this.GetControllerName(controller); + var dynamicContentType = controller.GetDynamicContentType(controllerName); + contentType = dynamicContentType != null ? TypeResolutionService.ResolveType(dynamicContentType.GetFullTypeName(), throwOnError: false) : null; + } + else + { + contentType = contentParam.ParameterType; + } + } + } + + return contentType; + } + + private string GetControllerName(ControllerBase controller) + { + var widgetName = (string)controller.ViewBag.WidgetName; + + if (!string.IsNullOrEmpty(widgetName)) + { + return widgetName; + } + + var controllerName = controller.GetType().Name.Replace("Controller", ""); + return controllerName; + } + + /// + /// This method returns the requested item status based on content location url parameters. + /// + /// Copied from IContentLocatableView in Sitefinity. Should use it instead when it goes public. + /// Requested item status. + private ContentLifecycleStatus ResolveRequestedItemStatus() + { + var itemStatusParam = SystemManager.CurrentHttpContext.Request.ParamsGet("sf-lc-status") ?? SystemManager.CurrentHttpContext.Items["sf-lc-status"]; + ContentLifecycleStatus status = ContentLifecycleStatus.Live; + if (itemStatusParam != null) + { + if (!Enum.TryParse(itemStatusParam as string, out status)) + status = ContentLifecycleStatus.Live; + } + + return status; + } + + /// + /// Determines whether preview of the item is requested. + /// + /// Copied from IContentLocatableView in Sitefinity. Should use it instead when it goes public. + /// Whether preview is requested. + private bool IsPreviewRequested() + { + var actionParam = SystemManager.CurrentHttpContext.Request.ParamsGet("sf-content-action"); + bool isPreview = actionParam != null && actionParam == "preview"; + return isPreview; + } + + /// + /// Resolves the manager. + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private IManager ResolveManager(Type itemType, string providerName) + { + IManager manager; + + try + { + ManagerBase.TryGetMappedManager(itemType, providerName, out manager); + } + catch (Exception ex) + { + Log.Write(string.Format(System.Globalization.CultureInfo.InvariantCulture, "Exception occurred in the routing functionality, details: {0}", ex)); + manager = null; + } + + return manager; + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private IEnumerable GetProviderNames(ControllerBase controller, Type contentType) + { + var providerNameProperty = controller.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).FirstOrDefault(p => p.Name == "ProviderName" && p.PropertyType == typeof(string)); + + if (providerNameProperty != null) + { + return new string[1] { providerNameProperty.GetValue(controller, null) as string }; + } + else + { + IManager manager; + + try + { + ManagerBase.TryGetMappedManager(contentType, string.Empty, out manager); + } + catch (Exception ex) + { + Log.Write(string.Format(System.Globalization.CultureInfo.InvariantCulture, "Exception occurred in the routing functionality, details: {0}", ex)); + manager = null; + } + + if (manager != null) + { + IList providers = manager.GetContextProviders().Select(p => p.Name).ToList(); + + if (!providers.Contains(manager.Provider.Name)) + providers.Add(manager.Provider.Name); + + return providers; + } + else + { + return new string[0]; + } + } + } + } +} diff --git a/Telerik.Sitefinity.Frontend/Mvc/Helpers/ControllerHelper.cs b/Telerik.Sitefinity.Frontend/Mvc/Helpers/ControllerHelper.cs new file mode 100644 index 000000000..09880e752 --- /dev/null +++ b/Telerik.Sitefinity.Frontend/Mvc/Helpers/ControllerHelper.cs @@ -0,0 +1,277 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Web; +using System.Web.Mvc; +using System.Web.Routing; +using Telerik.Sitefinity.Modules.Pages; +using Telerik.Sitefinity.Mvc.Proxy; +using Telerik.Sitefinity.Pages.Model; +using Telerik.Sitefinity.Security.Claims; +using Telerik.Sitefinity.Services; +using Telerik.Sitefinity.Web; + +namespace Telerik.Sitefinity.Frontend.Mvc.Helpers +{ + /// + /// This class contains helpers for working with controller related operations. + /// + public static class ControllerHelper + { + /// + /// Load controller model + /// + /// The widget id + /// The culture + /// Controller model + public static object LoadControllerModel(Guid widgetId, CultureInfo culture) + { + return LoadControllerModel(widgetId, culture, new Guid()); + } + + /// + /// Load controller model + /// + /// The widget id + /// The culture + /// The page id + /// Controller model + public static object LoadControllerModel(Guid widgetId, CultureInfo culture, Guid pageId) + { + if (widgetId == null || widgetId == Guid.Empty) + { + return null; + } + + var pageManager = PageManager.GetManager(); + ObjectData objectData = null; + + if (pageId != Guid.Empty) + { + objectData = GetOverridingControlForPage(widgetId, pageId); + } + + if (objectData == null) + { + objectData = pageManager.GetControls().SingleOrDefault(p => p.Id == widgetId); + } + + if (objectData is PageDraftControl && ClaimsManager.IsBackendUser() == false) + { + return null; + } + + object model = null; + + if (objectData != null) + { + var mvcProxy = pageManager.LoadControl(objectData, culture) as MvcControllerProxy; + if (mvcProxy != null) + { + var controller = mvcProxy.Controller; + var controllerType = controller.GetType(); + var modelProperty = controllerType.GetProperty(Model); + if (modelProperty != null) + { + model = modelProperty.GetValue(controller); + } + } + } + + return model; + } + + /// + /// Get controller widget id + /// + /// Widget Id + public static Guid GetWidgetId(Controller controller) + { + if (controller == null) + { + throw new ArgumentNullException("controller"); + } + + var pageManager = PageManager.GetManager(); + var viewBagControlId = controller.ViewData[ControllerKey]; + if (viewBagControlId == null) + return Guid.Empty; + + var widgetId = Guid.Empty; + string controlId = (string)viewBagControlId; + + // templates + if (controller.HttpContext.Items[IsTemplate] != null && (bool)controller.HttpContext.Items[IsTemplate] == true) + { + widgetId = GetTemplateWidgetId(pageManager, controller, controlId); + } + else + { + widgetId = GetPageWidgetId(pageManager, controlId); + } + + return widgetId; + } + + private static Guid GetTemplateWidgetId(PageManager pageManager, Controller controller, string controlId) + { + // check if action is after save or cancel in template + if (controller.HttpContext.Items[FormControlId] != null) + return (Guid)controller.HttpContext.Items[FormControlId]; + + var templateId = GetTemplateIdKey(controller.ControllerContext.HttpContext); + var versionId = GetVersionNumberKey(controller.ControllerContext.HttpContext); + + if (templateId != null && versionId != null) + { + var template = pageManager.GetTemplate(new Guid(templateId)); + if (template != null) + { + var versionManager = Telerik.Sitefinity.Versioning.VersionManager.GetManager(); + TemplateDraft draft = new TemplateDraft(); + versionManager.GetSpecificVersionByChangeId(draft, new Guid(versionId)); + + var control = GetControl(draft.Controls, controlId); + if (control != null) + return control.OriginalControlId; + } + } + + // check if loaded in template + var templateData = controller.HttpContext.Items[TemplateDraftProxy] as TemplateDraftProxy; + if (templateData != null) + { + var template = pageManager.GetTemplate(templateData.ParentItemId); + if (template != null) + { + if (SystemManager.IsDesignMode || SystemManager.IsPreviewMode) + { + var control = GetControl(template.Drafts.FirstOrDefault(p => p.IsTempDraft).Controls, controlId); + if (control != null) + return control.Id; + } + else + { + var control = GetControl(template.Controls, controlId); + if (control != null) + return control.Id; + } + } + } + + return Guid.Empty; + } + + private static Guid GetPageWidgetId(PageManager pageManager, string controlId) + { + // pages + var pageId = SiteMapBase.GetCurrentNode().PageId; + var page = pageManager.GetPageData(pageId); + + if (page.Template != null) + { + var templateControl = GetControl(page.Template.Controls, controlId); + if (templateControl != null) + return templateControl.Id; + } + + if (SystemManager.IsDesignMode || SystemManager.IsPreviewMode) + { + var pageDraft = page.Drafts.FirstOrDefault(p => p.IsTempDraft); + + // Draft, if page is created page template is null, only draft is available + if (page.Template == null && pageDraft.TemplateId != Guid.Empty) + { + var template = pageManager.GetTemplate(pageDraft.TemplateId); + if (template != null) + { + var templateControl = GetControl(template.Controls, controlId); + if (templateControl != null) + return templateControl.Id; + } + } + + var control = GetControl(pageDraft.Controls, controlId); + if (control != null) + return control.Id; + } + else + { + var control = GetControl(page.Controls, controlId); + if (control != null) + return control.Id; + } + + return Guid.Empty; + } + + private static ControlData GetOverridingControlForPage(Guid controlId, Guid pageId) + { + var pageManager = PageManager.GetManager(); + ControlData overridingControl = pageManager.GetControls().Where(c => c.Page.Id == pageId && (c.Id == controlId || c.BaseControlId == controlId)).FirstOrDefault(); + + if (overridingControl == null) + overridingControl = pageManager.GetControls().Where(c => c.Page.Id == pageId && (c.Id == controlId || c.BaseControlId == controlId)).FirstOrDefault(); + + if (overridingControl == null) + { + var pageDraft = pageManager.GetDrafts().Where(a => a.Id == pageId).FirstOrDefault(); + if (pageDraft != null) + { + var iter = pageManager.GetTemplates().Where(c => c.Id == pageDraft.TemplateId).FirstOrDefault(); + + while (iter != null) + { + overridingControl = pageManager.GetControls().Where(c => c.Page.Id == iter.Id && (c.Id == controlId || c.BaseControlId == controlId)).FirstOrDefault(); + if (overridingControl != null) + break; + iter = iter.ParentTemplate; + } + } + } + + return overridingControl; + } + + private static ControlData GetControl(IEnumerable controls, string controlId) + { + return controls.FirstOrDefault(p => p.Properties.FirstOrDefault(t => t.Name == IDParameter && controlId.EndsWith(t.Value)) != null); + } + + private static string GetTemplateIdKey(HttpContextBase context) + { + var requestContext = context.Items[RouteHandler.RequestContextKey] as RequestContext ?? context.Request.RequestContext; + if (requestContext.RouteData.Values.ContainsKey(ItemId)) + { + return requestContext.RouteData.Values[ItemId] as string; + } + else + { + return null; + } + } + + private static string GetVersionNumberKey(HttpContextBase context) + { + var requestContext = context.Items[RouteHandler.RequestContextKey] as RequestContext ?? context.Request.RequestContext; + if (requestContext.RouteData.Values.ContainsKey(VersionNumber)) + { + return requestContext.RouteData.Values[VersionNumber] as string; + } + else + { + return null; + } + } + + private const string TemplateDraftProxy = "TemplateDraftProxy"; + private const string IsTemplate = "IsTemplate"; + private const string FormControlId = "FormControlId"; + private const string ControllerKey = "sf_cntrl_id"; + private const string VersionNumber = "VersionNumber"; + private const string ItemId = "itemId"; + private const string IDParameter = "ID"; + private const string Model = "Model"; + } +} \ No newline at end of file diff --git a/Telerik.Sitefinity.Frontend/Mvc/Helpers/HtmlHelperExtensions.cs b/Telerik.Sitefinity.Frontend/Mvc/Helpers/HtmlHelperExtensions.cs index d23763ac1..6bcf78465 100644 --- a/Telerik.Sitefinity.Frontend/Mvc/Helpers/HtmlHelperExtensions.cs +++ b/Telerik.Sitefinity.Frontend/Mvc/Helpers/HtmlHelperExtensions.cs @@ -2,10 +2,17 @@ { using System.Collections.Generic; using System.Web.Mvc; + using Sitefinity.Security.Sanitizers; + using Telerik.Sitefinity.Abstractions; + using Telerik.Sitefinity.Frontend.Mvc.Infrastructure; + using Telerik.Sitefinity.Mvc.Rendering; + using Telerik.Sitefinity.Security.CSRF; using Telerik.Sitefinity.Services; + using Telerik.Sitefinity.Utilities.TypeConverters; + using Telerik.Sitefinity.Web.UI; /// - /// Helepr method for HTML markup generation. + /// Helper method for HTML markup generation. /// public static class HtmlHelperExtensions { @@ -45,6 +52,65 @@ public static MvcHtmlString UniqueId(this HtmlHelper htmlHelper, string prefix) return MvcHtmlString.Create(uniqueId); } + /// + /// Sanitizes html string using the registered . This method should be used when you want to handle text that is supposed to be HTML, but comes from untrusted source. + /// + /// The HTML helper which this method is extending. + /// The HTML string to be sanitized. + /// + public static MvcHtmlString HtmlSanitize(this HtmlHelper htmlHelper, string html) + { + var sanitizedHtml = ControlUtilities.Sanitize(html); + + return MvcHtmlString.Create(sanitizedHtml); + } + + /// + /// Sanitizes url string using the registered . It will also attribute encode the string. + /// + /// The HTML helper which this method is extending. + /// The url string to be sanitized. + /// + public static MvcHtmlString UrlSanitize(this HtmlHelper htmlHelper, string url) + { + var sanitizedUrl = ControlUtilities.SanitizeUrl(url); + + return MvcHtmlString.Create(sanitizedUrl); + } + + /// + /// Adds an antiforgery markup and scripts to the view. + /// + /// The HTML helper which this method is extending. + /// The path to the token retrieval script. + /// + public static MvcHtmlString AddSitefinityAntiforgeryToken(this HtmlHelper htmlHelper, string retrievalScriptPath = null) + { + var antiForgery = ObjectFactory.IsTypeRegistered() ? ObjectFactory.Resolve() : null; + var antiForgeryEnabled = antiForgery != null && antiForgery.Enabled; + if (antiForgeryEnabled) + { + if (retrievalScriptPath == null) + { + var page = htmlHelper.ViewContext.HttpContext.Handler.GetPageHandler() ?? new PageProxy(null); + retrievalScriptPath = page.ClientScript.GetWebResourceUrl(TypeResolutionService.ResolveType("Telerik.Sitefinity.WebSecurity.WebSecurityModule"), + "Telerik.Sitefinity.WebSecurity.CSRF.TokenRetrieval.js"); + } + + htmlHelper.Script(retrievalScriptPath, "bottom", false); + var result = + $@" + +"; + + return MvcHtmlString.Create(result); + } + else + { + return MvcHtmlString.Empty; + } + } + private const string ElementIdsKey = "sf-element-ids"; private const string IdCountersKey = "sf-element-id-counters"; } diff --git a/Telerik.Sitefinity.Frontend/Mvc/Helpers/HyperLinkHelpers.cs b/Telerik.Sitefinity.Frontend/Mvc/Helpers/HyperLinkHelpers.cs index ebd6a63d4..f715d3919 100644 --- a/Telerik.Sitefinity.Frontend/Mvc/Helpers/HyperLinkHelpers.cs +++ b/Telerik.Sitefinity.Frontend/Mvc/Helpers/HyperLinkHelpers.cs @@ -1,19 +1,21 @@ using System; -using System.Linq; -using System.Threading; using System.Web; -using System.Web.Mvc; +using System.Linq; using Telerik.Sitefinity.Abstractions; -using Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Controllers; +using Telerik.Sitefinity.Data.Linq.Dynamic; using Telerik.Sitefinity.Frontend.Mvc.Models; -using Telerik.Sitefinity.Localization.UrlLocalizationStrategies; using Telerik.Sitefinity.Model; +using Telerik.Sitefinity.Modules.Libraries; using Telerik.Sitefinity.Modules.Pages; +using Telerik.Sitefinity.Multisite; using Telerik.Sitefinity.Pages.Model; +using Telerik.Sitefinity.Services; using Telerik.Sitefinity.Taxonomies.Model; using Telerik.Sitefinity.Web; using Telerik.Sitefinity.Web.DataResolving; using Telerik.Sitefinity.Web.UrlEvaluation; +using System.Runtime.CompilerServices; +using ServiceStack; namespace Telerik.Sitefinity.Frontend.Mvc.Helpers { @@ -106,7 +108,7 @@ public static string GetDetailPageUrl(ItemViewModel item, Guid detailsPageId, bo url = DataResolver.Resolve(item.DataItem, "URL", null, detailsPageId.ToString()); } - url = url.Replace("//", "/"); + url = RemoveDoubleSlash(url); return UrlPath.ResolveUrl(url, true); } @@ -134,8 +136,7 @@ public static string GetDetailPageUrl(ItemViewModel item, Guid detailsPageId, bo } url = url + "?itemIndex=" + itemIndex; - - url = url.Replace("//", "/"); + url = RemoveDoubleSlash(url); return UrlPath.ResolveUrl(url, true); } @@ -167,6 +168,30 @@ public static string GetFullPageUrl(Guid pageId) { return UrlPath.ResolveUrl(node.Url, true); } + else + { + // try to redirect to a page from a different site + var redirectPageNode = PageManager.GetManager().GetPageNode(pageId); + if (SystemManager.CurrentContext.CurrentSite.SiteMapRootNodeId != redirectPageNode.RootNodeId) + { + using (SiteRegion.FromSiteMapRoot(redirectPageNode.RootNodeId, SiteContextResolutionTypes.ByParam)) + { + if (sitefinitySiteMap != null) + { + node = sitefinitySiteMap.FindSiteMapNodeFromKey(pageId.ToString(), false); + } + else + { + node = siteMap.FindSiteMapNodeFromKey(pageId.ToString()); + } + + if (node != null) + { + return UrlPath.ResolveUrl(node.Url, true); + } + } + } + } } return null; @@ -199,5 +224,46 @@ public static string BuildTaxonQueryStringParams(ITaxon taxon, string urlKeyPref var taxonQueryStringParams = evaluator.BuildUrl(taxon.Taxonomy.Name, taxonRelativeUrl, taxon.Taxonomy.Name, taxonBuildOptions, UrlEvaluationMode.QueryString, urlKeyPrefix); return taxonQueryStringParams; } + + /// + /// Retrieves the thumbnail url for the video + /// + /// The librarires manager + /// The video id + /// + public static string GetVideoThumbnailUrl(LibrariesManager librariesManager, Guid videoId) + { + try + { + var video = librariesManager.GetVideo(videoId); + + return video.ResolveThumbnailUrl(); + } + catch (UnauthorizedAccessException ex) + { + Exceptions.HandleException(ex, ExceptionPolicyName.IgnoreExceptions); + + return string.Empty; + } + } + + private static string RemoveDoubleSlash(string url) + { + // If the url is absolute we should not replace first // with /. Ex: http://domain... + Uri result = null; + if (Uri.TryCreate(url, UriKind.Absolute, out result)) + { + UriBuilder builder = new UriBuilder(url); + builder.Path = builder.Path.Replace("//", "/"); + + url = builder.Uri.ToString(); + } + else + { + url = url.Replace("//", "/"); + } + + return url; + } } } diff --git a/Telerik.Sitefinity.Frontend/Mvc/Helpers/InlineEditingHtmlHelpers.cs b/Telerik.Sitefinity.Frontend/Mvc/Helpers/InlineEditingHtmlHelpers.cs index 2a7998c39..73aace273 100644 --- a/Telerik.Sitefinity.Frontend/Mvc/Helpers/InlineEditingHtmlHelpers.cs +++ b/Telerik.Sitefinity.Frontend/Mvc/Helpers/InlineEditingHtmlHelpers.cs @@ -2,8 +2,10 @@ using System.Web; using System.Web.Mvc; using System.Web.UI; +using Telerik.Sitefinity.Data; using Telerik.Sitefinity.Frontend.InlineEditing; using Telerik.Sitefinity.Services; +using Telerik.Sitefinity.Utilities.TypeConverters; using Telerik.Sitefinity.Web; namespace Telerik.Sitefinity.Frontend.Mvc.Helpers @@ -47,10 +49,10 @@ public static IHtmlString TextField(this HtmlHelper helper, string propName, str { htmlString = string.Format(HtmlProcessor.InlineEditingHtmlWrapper, propName, fieldType, propValue); } - + return new System.Web.Mvc.MvcHtmlString(htmlString); } - + /// /// HTML helper which adds an InlineEditing region. This should be added once at the top of the page, and the whole region will support InlineEditing. /// @@ -62,14 +64,14 @@ public static IHtmlString TextField(this HtmlHelper helper, string propName, str public static HtmlRegion InlineEditingRegion( this HtmlHelper htmlHelper, string providerName, - string type, + string type, Guid id) { var htmlProcessor = new HtmlProcessor(); return htmlProcessor.CreateInlineEditingRegion( htmlHelper.ViewContext.Writer, - providerName, - type, + providerName, + type, id); } @@ -85,6 +87,11 @@ public static IHtmlString InlineEditingAttributes(this HtmlHelper htmlHelper, st if (!SystemManager.IsInlineEditingMode) return htmlHelper.Raw(string.Empty); + if (string.IsNullOrEmpty(providerName)) + { + providerName = GetProviderName(type); + } + var providerNameEncoded = providerName != null ? htmlHelper.Encode(providerName) : providerName; var typeEncoded = htmlHelper.Encode(type); @@ -122,7 +129,7 @@ public static IHtmlString InlineEditingFieldAttributes(this HtmlHelper htmlHelpe } /// - /// Returns if the inline editin section should be rendered. + /// Returns if the inline editing section should be rendered. /// /// The HTML helper. /// @@ -150,5 +157,31 @@ public static bool ShouldRenderInlineEditing(this HtmlHelper htmlHelper) return shouldRender; } + + private static string GetProviderName(string type) + { + string providerName = null; + + if (type != null) + { + var itemType = TypeResolutionService.ResolveType(type); + if (itemType != null) + { + Type managerType; + ManagerBase.TryGetMappedManagerType(itemType, out managerType); + if (managerType != null) + { + var currentSite = SystemManager.CurrentContext.CurrentSite; + var provider = currentSite.GetDefaultProvider(managerType.FullName); + if (provider != null) + { + providerName = provider.ProviderName; + } + } + } + } + + return providerName; + } } } \ No newline at end of file diff --git a/Telerik.Sitefinity.Frontend/Mvc/Helpers/NewslettersHelper.cs b/Telerik.Sitefinity.Frontend/Mvc/Helpers/NewslettersHelper.cs new file mode 100644 index 000000000..8947cb693 --- /dev/null +++ b/Telerik.Sitefinity.Frontend/Mvc/Helpers/NewslettersHelper.cs @@ -0,0 +1,47 @@ +using Telerik.Sitefinity.Configuration; +using Telerik.Sitefinity.Modules.Newsletters; +using Telerik.Sitefinity.Modules.Newsletters.Configuration; +using Telerik.Sitefinity.Modules.Newsletters.Web; +using Telerik.Sitefinity.Newsletters.Model; +using Telerik.Sitefinity.Services; + +namespace Telerik.Sitefinity.Frontend.Mvc.Helpers +{ + /// + /// Helper methods for email campaigns. + /// + public static class NewslettersHelper + { + /// + /// Indicates whether the current request is for newsletter. + /// + /// True if the current request is for newsletter. + public static bool IsNewsletter() + { + var httpContext = SystemManager.CurrentHttpContext; + + var newsLetterItem = httpContext.Items.Contains(NewslettersModule.IsNewsletter) ? (bool)httpContext.Items[NewslettersModule.IsNewsletter] : false; + + var isNewsletter = newsLetterItem + || (httpContext.Request.UrlReferrer != null && httpContext.Request.UrlReferrer.AbsolutePath.ToLower().Contains("/sitefinity/sfnwslttrs/")) + || (httpContext.Request.Url != null && httpContext.Request.Url.AbsolutePath.ToLower().Contains("/sitefinity/sfnwslttrs/")); + + return isNewsletter; + } + + /// + /// Indicates whether the current request is for pure mvc newsletter. + /// + /// True if the current request is for mvc newsletter. + internal static bool IsMvcNewsletter() + { + var currentHttpContext = SystemManager.CurrentHttpContext; + + return SystemManager.GetApplicationModule(NewslettersModule.ModuleName) != null + && !Config.Get().AllowHybridWidgets + && currentHttpContext != null + && currentHttpContext.Items.Contains(NewslettersRouteHandler.NewslettersMessageBodyType) + && (MessageBodyType)currentHttpContext.Items[NewslettersRouteHandler.NewslettersMessageBodyType] == MessageBodyType.InternalMvcPage; + } + } +} diff --git a/Telerik.Sitefinity.Frontend/Mvc/Helpers/PagingHelpers.cs b/Telerik.Sitefinity.Frontend/Mvc/Helpers/PagingHelpers.cs new file mode 100644 index 000000000..89f2ccfd9 --- /dev/null +++ b/Telerik.Sitefinity.Frontend/Mvc/Helpers/PagingHelpers.cs @@ -0,0 +1,72 @@ +using System; +using System.Web; +using Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Controllers; +using Telerik.Sitefinity.Services; +using Telerik.Sitefinity.Web; + +namespace Telerik.Sitefinity.Frontend.Mvc.Helpers +{ + /// + /// Helper methods for widget paging. + /// + public static class PagingHelpers + { + /// + /// Generate url template that suports paging. + /// + /// Target controller. + /// Base url for paging. + /// Query parameter sufix. + /// Url suffix. + public static string GeneratePagingTemplate(this ContentBaseController controller, string pageUrl, string urlKeyPrefix) + { + if (urlKeyPrefix.IsNullOrWhitespace()) + { + return string.Concat(pageUrl, UrlHelpers.GetRedirectPagingUrl(), SystemManager.CurrentHttpContext.Request.QueryString.ToQueryString(true)); + } + + var key = string.Format(KeyFormat, urlKeyPrefix); + var queryParams = HttpUtility.ParseQueryString(SystemManager.CurrentHttpContext.Request.Url.Query); + queryParams[key] = "{0}"; + + var queryParamsRaw = HttpUtility.UrlDecode(queryParams.ToQueryString(true)); + return string.Concat(SystemManager.CurrentHttpContext.Request.Url.LocalPath, queryParamsRaw); + } + + /// + /// Check query params and update page if applicable. + /// + /// Target controller. + /// The page. + /// Prefix of page query parameter. + public static void UpdatePageFromQuery(this ContentBaseController controller, ref int? page, string urlKeyPrefix) + { + if (urlKeyPrefix.IsNullOrWhitespace()) return; + + var key = string.Format(KeyFormat, urlKeyPrefix); + var pageString = SystemManager.CurrentHttpContext.Request.QueryStringGet(key); + + int queryPage; + if (!int.TryParse(pageString, out queryPage)) + { + queryPage = 1; + } + page = queryPage; + } + + /// + /// Extract a valid page number. + /// + /// Target controller. + /// Nullable page number. + /// One (1) if page is null or less than one, otherwise page value. + public static int ExtractValidPage(this ContentBaseController controller, int? page) + { + if (!page.HasValue || page.Value < 1) return 1; + + return page.Value; + } + + private const string KeyFormat = "{0}page"; + } +} diff --git a/Telerik.Sitefinity.Frontend/Mvc/Helpers/ResourceHelper.cs b/Telerik.Sitefinity.Frontend/Mvc/Helpers/ResourceHelper.cs index 03ff4c720..8008231e8 100644 --- a/Telerik.Sitefinity.Frontend/Mvc/Helpers/ResourceHelper.cs +++ b/Telerik.Sitefinity.Frontend/Mvc/Helpers/ResourceHelper.cs @@ -3,8 +3,12 @@ using System.Linq; using System.Text; using System.Web; +using System.Web.Configuration; using System.Web.Mvc; using System.Web.UI; +using System.Collections.Generic; +using Telerik.Sitefinity.Abstractions; +using Telerik.Sitefinity.Abstractions.VirtualPath; using Telerik.Sitefinity.Configuration; using Telerik.Sitefinity.Frontend.Mvc.Infrastructure; using Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Layouts; @@ -12,6 +16,7 @@ using Telerik.Sitefinity.Modules.Pages; using Telerik.Sitefinity.Modules.Pages.Configuration; using Telerik.Sitefinity.Mvc.Rendering; +using Telerik.Sitefinity.Services; using Telerik.Sitefinity.Utilities.TypeConverters; namespace Telerik.Sitefinity.Frontend.Mvc.Helpers @@ -21,11 +26,71 @@ namespace Telerik.Sitefinity.Frontend.Mvc.Helpers /// public static class ResourceHelper { + /// + /// Gets a value indicating whether section name of the scripts should be rendered. + /// This value is used client side for placing scripts tag within personalized widgets. + /// + /// + /// true if section name of the scripts should be rendered; otherwise, false. + /// + internal static bool RenderScriptSection + { + get + { + return SystemManager.CurrentHttpContext != null && + SystemManager.CurrentHttpContext.Items.Contains("RenderScriptSection") && + (bool)SystemManager.CurrentHttpContext.Items["RenderScriptSection"]; + } + } + + /// + /// Gets a value indicating whether the site is in debug mode acording to the web.config + /// + /// + /// true if the site is in debug mode; otherwise, false. + /// + internal static bool IsDebugMode + { + get + { + if (ResourceHelper.isDebugMode.HasValue) + return ResourceHelper.isDebugMode.Value; + + CompilationSection compilationSection = (CompilationSection)System.Configuration.ConfigurationManager.GetSection(@"system.web/compilation"); + + return compilationSection.Debug; + } + set + { + ResourceHelper.isDebugMode = value; + } + } + + /// + /// Registers JavaScript reference and ensures that it loads maximum once for a page. + /// + /// The helper. + /// The path to the JavaScript file. + /// Indicates whether to use script manager(if exists) when register JavaScript reference. + /// If it is used the script will always be loaded on the top section of the page. + /// + /// MvcHtmlString + /// + /// + /// This method uses directly the resource from the . + /// In case you want to use embedded scripts from Sitefinity check . + /// + public static MvcHtmlString Script(this HtmlHelper helper, string scriptPath, bool tryUseScriptManager) + { + return ResourceHelper.Script(helper, scriptPath, null, false, tryUseScriptManager); + } + /// /// Registers JavaScript reference and ensures that it loads maximum once for a page. /// /// The helper. /// The path to the JavaScript file. + /// If it is used the script will always be loaded on the top section of the page. /// /// MvcHtmlString /// @@ -35,7 +100,26 @@ public static class ResourceHelper /// public static MvcHtmlString Script(this HtmlHelper helper, string scriptPath) { - return ResourceHelper.Script(helper, scriptPath, null, false); + return ResourceHelper.Script(helper, scriptPath, null, false, true); + } + + /// + /// Registers JavaScript reference and ensures that it loads maximum once for a page. + /// + /// The helper. + /// The path to the JavaScript file. + /// The path to the JavaScript file. + /// If it is used the script will always be loaded on the top section of the page. + /// + /// MvcHtmlString + /// + /// + /// This method uses directly the resource from the . + /// In case you want to use embedded scripts from Sitefinity check . + /// + public static MvcHtmlString Script(this HtmlHelper helper, string scriptPath, List> attributes) + { + return ResourceHelper.Script(helper, scriptPath, null, false, true, attributes); } /// @@ -44,6 +128,7 @@ public static MvcHtmlString Script(this HtmlHelper helper, string scriptPath) /// The helper. /// The path to the JavaScript file. /// The name of the section that will render this script. If null it will render on the same place of the page + /// If it is used the script will always be loaded on the top section of the page. /// /// MvcHtmlString /// @@ -63,6 +148,7 @@ public static MvcHtmlString Script(this HtmlHelper helper, string scriptPath, st /// The path to the JavaScript file. /// The name of the section that will render this script. If null it will render on the same place of the page /// Indicates whether to throw an exception if the specified section does not exist. + /// If it is used the script will always be loaded on the top section of the page. /// /// MvcHtmlString /// @@ -72,10 +158,95 @@ public static MvcHtmlString Script(this HtmlHelper helper, string scriptPath, st /// public static MvcHtmlString Script(this HtmlHelper helper, string scriptPath, string sectionName, bool throwException) { - if (ResourceHelper.TryConfigureScriptManager(scriptPath, helper.ViewContext.HttpContext.CurrentHandler)) + return ResourceHelper.Script(helper, scriptPath, sectionName, throwException, true); + } + + /// + /// Registers JavaScript reference and ensures that it loads maximum once for a page. + /// + /// The helper. + /// The path to the JavaScript file. + /// The name of the section that will render this script. If null it will render on the same place of the page + /// Indicates whether to throw an exception if the specified section does not exist. + /// The path to the JavaScript file. + /// If it is used the script will always be loaded on the top section of the page. + /// + /// MvcHtmlString + /// + /// + /// This method uses directly the resource from the . + /// In case you want to use embedded scripts from Sitefinity check . + /// + public static MvcHtmlString Script(this HtmlHelper helper, string scriptPath, string sectionName, bool throwException, List> attributes) + { + return ResourceHelper.Script(helper, scriptPath, sectionName, throwException, true, attributes); + } + + /// + /// Registers JavaScript reference and ensures that it loads maximum once for a page. + /// + /// The helper. + /// The path to the JavaScript file. + /// The name of the section that will render this script. If null it will render on the same place of the page + /// Indicates whether to throw an exception if the specified section does not exist. + /// Indicates whether to use script manager(if exists) when register JavaScript reference. + /// If it is used the script will always be loaded on the top section of the page. + /// + /// MvcHtmlString + /// + /// + /// This method uses directly the resource from the . + /// In case you want to use embedded scripts from Sitefinity check . + /// + public static MvcHtmlString Script(this HtmlHelper helper, string scriptPath, string sectionName, bool throwException, bool tryUseScriptManager) + { + if (tryUseScriptManager && ResourceHelper.TryConfigureScriptManager(scriptPath, helper.ViewContext.HttpContext.CurrentHandler)) + return MvcHtmlString.Empty; + + return ResourceHelper.RegisterResource(helper.ViewContext.HttpContext, scriptPath, ResourceType.Js, sectionName, throwException, null); + } + + /// + /// Registers JavaScript reference and ensures that it loads maximum once for a page. + /// + /// The helper. + /// The path to the JavaScript file. + /// The name of the section that will render this script. If null it will render on the same place of the page + /// Indicates whether to throw an exception if the specified section does not exist. + /// Indicates whether to use script manager(if exists) when register JavaScript reference. + /// A list of attribute key value pairs to be added to the script + /// If it is used the script will always be loaded on the top section of the page. + /// + /// MvcHtmlString + /// + /// + /// This method uses directly the resource from the . + /// In case you want to use embedded scripts from Sitefinity check . + /// + public static MvcHtmlString Script(this HtmlHelper helper, string scriptPath, string sectionName, bool throwException, bool tryUseScriptManager, List> attributes) + { + if (tryUseScriptManager && ResourceHelper.TryConfigureScriptManager(scriptPath, helper.ViewContext.HttpContext.CurrentHandler)) return MvcHtmlString.Empty; - return ResourceHelper.RegisterResource(helper.ViewContext.HttpContext, scriptPath, ResourceType.Js, sectionName, throwException); + return ResourceHelper.RegisterResource(helper.ViewContext.HttpContext, scriptPath, ResourceType.Js, sectionName, throwException, attributes); + } + + /// + /// Registers JavaScript reference and ensures that it loads maximum once for a page. + /// + /// + /// This helper references the same resource existing in Sitefinity. + /// + /// The helper. + /// The script reference. + /// Indicates whether to use script manager(if exists) when register JavaScript reference. + /// If it is used the script will always be loaded on the top section of the page. + /// + /// MvcHtmlString + /// + public static MvcHtmlString Script(this HtmlHelper helper, ScriptRef scriptReference, bool tryUseScriptManager) + { + return ResourceHelper.Script(helper, scriptReference, null, false, tryUseScriptManager); } /// @@ -86,12 +257,32 @@ public static MvcHtmlString Script(this HtmlHelper helper, string scriptPath, st /// /// The helper. /// The script reference. + /// If it is used the script will always be loaded on the top section of the page. /// /// MvcHtmlString /// public static MvcHtmlString Script(this HtmlHelper helper, ScriptRef scriptReference) { - return ResourceHelper.Script(helper, scriptReference, null, false); + return ResourceHelper.Script(helper, scriptReference, null, false, true); + } + + + /// + /// Registers JavaScript reference and ensures that it loads maximum once for a page. + /// + /// + /// This helper references the same resource existing in Sitefinity. + /// + /// The helper. + /// The script reference. + /// The path to the JavaScript file. + /// If it is used the script will always be loaded on the top section of the page. + /// + /// MvcHtmlString + /// + public static MvcHtmlString Script(this HtmlHelper helper, ScriptRef scriptReference, List> attributes) + { + return ResourceHelper.Script(helper, scriptReference, null, false, true, attributes); } /// @@ -103,6 +294,7 @@ public static MvcHtmlString Script(this HtmlHelper helper, ScriptRef scriptRefer /// The helper. /// The script reference. /// The name of the section that will render this script. If null it will render on the same place of the page + /// If it is used the script will always be loaded on the top section of the page. /// /// MvcHtmlString /// @@ -111,6 +303,25 @@ public static MvcHtmlString Script(this HtmlHelper helper, ScriptRef scriptRefer return ResourceHelper.Script(helper, scriptReference, sectionName, true); } + /// + /// Registers JavaScript reference and ensures that it loads maximum once for a page. + /// + /// + /// This helper references the same resource existing in Sitefinity. + /// + /// The helper. + /// The script reference. + /// The name of the section that will render this script. If null it will render on the same place of the page + /// The path to the JavaScript file. + /// If it is used the script will always be loaded on the top section of the page. + /// + /// MvcHtmlString + /// + public static MvcHtmlString Script(this HtmlHelper helper, ScriptRef scriptReference, string sectionName, List> attributes) + { + return ResourceHelper.Script(helper, scriptReference, sectionName, true, true, attributes); + } + /// /// Registers JavaScript reference and ensures that it loads maximum once for a page. /// @@ -121,12 +332,48 @@ public static MvcHtmlString Script(this HtmlHelper helper, ScriptRef scriptRefer /// The script reference. /// The name of the section that will render this script. If null it will render on the same place of the page /// Indicates whether to throw an exception if the specified section does not exist. + /// Indicates whether to use script manager (if exists) when register JavaScript reference. + /// If it is used the script will always be loaded on the top section of the page. /// /// MvcHtmlString /// - public static MvcHtmlString Script(this HtmlHelper helper, ScriptRef scriptReference, string sectionName, bool throwException) + public static MvcHtmlString Script(this HtmlHelper helper, ScriptRef scriptReference, string sectionName, bool throwException, bool tryUseScriptManager) + { + if (tryUseScriptManager && ResourceHelper.TryConfigureScriptManager(scriptReference, helper.ViewContext.HttpContext.CurrentHandler)) + return System.Web.Mvc.MvcHtmlString.Empty; + + var references = PageManager.GetScriptReferences(scriptReference).Select(r => new MvcScriptReference(r)); + + StringBuilder outputMarkup = new StringBuilder(); + + foreach (var script in references) + { + var resourceUrl = script.GetResourceUrl(); + outputMarkup.Append(ResourceHelper.RegisterResource(helper.ViewContext.HttpContext, resourceUrl, ResourceType.Js, sectionName, throwException, null)); + } + + return MvcHtmlString.Create(outputMarkup.ToString()); + } + + /// + /// Registers JavaScript reference and ensures that it loads maximum once for a page. + /// + /// + /// This helper references the same resource existing in Sitefinity. + /// + /// The helper. + /// The script reference. + /// The name of the section that will render this script. If null it will render on the same place of the page + /// Indicates whether to throw an exception if the specified section does not exist. + /// Indicates whether to use script manager (if exists) when register JavaScript reference. + /// A list of attribute key value pairs to be added to the script + /// If it is used the script will always be loaded on the top section of the page. + /// + /// MvcHtmlString + /// + public static MvcHtmlString Script(this HtmlHelper helper, ScriptRef scriptReference, string sectionName, bool throwException, bool tryUseScriptManager, List> attributes) { - if (ResourceHelper.TryConfigureScriptManager(scriptReference, helper.ViewContext.HttpContext.CurrentHandler)) + if (tryUseScriptManager && ResourceHelper.TryConfigureScriptManager(scriptReference, helper.ViewContext.HttpContext.CurrentHandler)) return System.Web.Mvc.MvcHtmlString.Empty; var references = PageManager.GetScriptReferences(scriptReference).Select(r => new MvcScriptReference(r)); @@ -136,12 +383,32 @@ public static MvcHtmlString Script(this HtmlHelper helper, ScriptRef scriptRefer foreach (var script in references) { var resourceUrl = script.GetResourceUrl(); - outputMarkup.Append(ResourceHelper.RegisterResource(helper.ViewContext.HttpContext, resourceUrl, ResourceType.Js, sectionName, throwException)); + outputMarkup.Append(ResourceHelper.RegisterResource(helper.ViewContext.HttpContext, resourceUrl, ResourceType.Js, sectionName, throwException, attributes)); } return MvcHtmlString.Create(outputMarkup.ToString()); } + /// + /// Registers JavaScript reference and ensures that it loads maximum once for a page. + /// + /// + /// This helper references the same resource existing in Sitefinity. + /// + /// The helper. + /// The script reference. + /// The name of the section that will render this script. If null it will render on the same place of the page + /// Indicates whether to throw an exception if the specified section does not exist. + /// A list of attribute key value pairs to be added to the script + /// If it is used the script will always be loaded on the top section of the page. + /// + /// MvcHtmlString + /// + public static MvcHtmlString Script(this HtmlHelper helper, ScriptRef scriptReference, string sectionName, bool throwException) + { + return ResourceHelper.Script(helper, scriptReference, sectionName, throwException, true); + } + /// /// Registers style sheet reference and ensures that it loads maximum once for a page. /// @@ -155,6 +422,20 @@ public static MvcHtmlString StyleSheet(this HtmlHelper helper, string resourcePa return ResourceHelper.StyleSheet(helper, resourcePath, null, false); } + /// + /// Registers style sheet reference and ensures that it loads maximum once for a page. + /// + /// The helper. + /// The path to the CSS file. + /// A list of attribute key value pairs to be added to the stylesheet + /// + /// MvcHtmlString + /// + public static MvcHtmlString StyleSheet(this HtmlHelper helper, string resourcePath, List> attributes) + { + return ResourceHelper.StyleSheet(helper, resourcePath, null, false, attributes); + } + /// /// Registers style sheet reference and ensures that it loads maximum once for a page. /// @@ -181,7 +462,23 @@ public static MvcHtmlString StyleSheet(this HtmlHelper helper, string resourcePa /// public static MvcHtmlString StyleSheet(this HtmlHelper helper, string resourcePath, string sectionName, bool throwException) { - return ResourceHelper.RegisterResource(helper.ViewContext.HttpContext, resourcePath, ResourceType.Css, sectionName, throwException); + return ResourceHelper.RegisterResource(helper.ViewContext.HttpContext, resourcePath, ResourceType.Css, sectionName, throwException, null); + } + + /// + /// Registers style sheet reference and ensures that it loads maximum once for a page. + /// + /// The helper. + /// The path to the CSS file. + /// The name of the section that will render this script. If null it will render on the same place of the page + /// Indicates whether to throw an exception if the specified section does not exist. + /// A list of attribute key value pairs to be added to the stylesheet + /// + /// MvcHtmlString + /// + public static MvcHtmlString StyleSheet(this HtmlHelper helper, string resourcePath, string sectionName, bool throwException, List> attributes) + { + return ResourceHelper.RegisterResource(helper.ViewContext.HttpContext, resourcePath, ResourceType.Css, sectionName, throwException, attributes); } /// @@ -226,6 +523,21 @@ public static MvcHtmlString CodeMirrorScriptReferences(this HtmlHelper helper) return MvcHtmlString.Create(result.ToString()); } + /// + /// Adds script references for Js Beautify library. + /// + /// The helper. + /// + /// MvcHtmlString + /// + public static MvcHtmlString JsBeautifierScriptReference(this HtmlHelper helper) + { + var result = new StringBuilder(); + var urlHelper = new UrlHelper(helper.ViewContext.HttpContext.Request.RequestContext); + result.Append(ResourceHelper.Script(helper, urlHelper.EmbeddedResource("Telerik.Sitefinity.Frontend.Startup", "Telerik.Sitefinity.Frontend.Mvc.Scripts.JSBeautifier.beautify-html.js")).ToHtmlString()); + return MvcHtmlString.Create(result.ToString()); + } + /// /// Renders the lang attribute. /// @@ -233,7 +545,7 @@ public static MvcHtmlString CodeMirrorScriptReferences(this HtmlHelper helper) /// public static MvcHtmlString RenderLangAttribute(this HtmlHelper helper) { - return RenderLangAttribute(helper, CultureInfo.CurrentUICulture.Name); + return RenderLangAttribute(helper, Telerik.Sitefinity.Services.SystemManager.CurrentContext.Culture.Name); } /// @@ -244,7 +556,31 @@ public static MvcHtmlString RenderLangAttribute(this HtmlHelper helper) /// public static MvcHtmlString RenderLangAttribute(this HtmlHelper helper, string culture) { - string attributeString = helper.FormatValue(culture, "lang=\"{0}\""); + string attributeString = helper.FormatValue(HttpUtility.HtmlAttributeEncode(culture), "lang=\"{0}\""); + + return new MvcHtmlString(attributeString); + } + + /// + /// Renders the dir attribute when language is right to left. + /// + /// The helper. + /// + public static MvcHtmlString RenderDirAttribute(this HtmlHelper helper) + { + return RenderDirAttribute(helper, Telerik.Sitefinity.Services.SystemManager.CurrentContext.Culture.Name); + } + + /// + /// Renders the dir attribute when language is right to left. + /// + /// The helper. + /// The culture. + /// + public static MvcHtmlString RenderDirAttribute(this HtmlHelper helper, string culture) + { + var isRtl = (Array.Find(rtlLanguages, s => culture.ToString().StartsWith(s)) != null) || culture.ToString().ToLower().Contains("arab"); + string attributeString = isRtl && (!SystemManager.IsDesignMode || SystemManager.IsPreviewMode) ? "dir=\"rtl\"" : ""; return new MvcHtmlString(attributeString); } @@ -277,6 +613,76 @@ public static string GetWebResourceUrl(ScriptRef scriptReference) return resourceUrl; } + /// + /// Places the script before the end of the body. + /// + /// The page. + /// The script. + public static void PlaceScriptBeforeBodyEnd(this Page page, string script) + { + /* There is a literal control in the FrontendMVC.aspx which is used as a mark where scripts should be placed.*/ + int insertAt = page.Controls.Count - 1; + MasterPage master = null; + bool hasLiteral = false; + for (int i = page.Controls.Count - 1; i >= 0; i--) + { + if (page.Controls[i] is LiteralControl) + { + insertAt = i; + hasLiteral = true; + break; + } + else if (page.Controls[i] is MasterPage) + { + master = page.Controls[i] as MasterPage; + break; + } + } + + if (master != null) + { + for (int i = master.Controls.Count - 1; i >= 0; i--) + { + if (master.Controls[i] is LiteralControl) + { + insertAt = i; + hasLiteral = true; + break; + } + } + } + + if (hasLiteral) + { + if (master != null) + { + master.Controls.AddAt(insertAt, new LiteralControl(script)); + } + else + { + if (page.Form != null) + { + page.Form.Controls.Add(new LiteralControl(script)); + } + else + { + page.Controls.AddAt(insertAt, new LiteralControl(script)); + } + } + } + else + { + if (page.Form != null) + { + page.Form.Controls.Add(new LiteralControl(script)); + } + else + { + page.Controls.Add(new LiteralControl(script)); + } + } + } + /// /// Renders all scripts. /// @@ -305,7 +711,7 @@ internal static string RenderAllStylesheets(HttpContextBase context, string sect return stylesheetMarkup; } - private static MvcHtmlString RegisterResource(HttpContextBase httpContext, string resourcePath, ResourceType resourceType, string sectionName, bool throwException) + private static MvcHtmlString RegisterResource(HttpContextBase httpContext, string resourcePath, ResourceType resourceType, string sectionName, bool throwException, List> attributes = null) { throwException = throwException && httpContext.CurrentHandler != null; @@ -319,17 +725,37 @@ private static MvcHtmlString RegisterResource(HttpContextBase httpContext, strin MvcHtmlString result = MvcHtmlString.Empty; + if (!string.IsNullOrWhiteSpace(sectionName)) + { + var pageHandler = httpContext.Handler.GetPageHandler(); + if (pageHandler != null && pageHandler.Master is MvcMasterPage) + { + if (!throwException && !SectionRenderer.IsAvailable(pageHandler, sectionName)) + sectionName = null; + } + else + { + if (!SectionRenderer.IsAvailable(pageHandler, sectionName)) + sectionName = null; + } + } + else + { + sectionName = null; + } + // No section name renders the script inline if it hasn't been rendered - if (string.IsNullOrEmpty(sectionName) || !SectionRenderer.IsAvailable(httpContext.Handler.GetPageHandler(), sectionName)) + if (sectionName == null || + ResourceHelper.RenderScriptSection) { - if (!register.IsRegistered(resourcePath, sectionName: null)) + if (!register.IsRegistered(resourcePath)) { - result = MvcHtmlString.Create(ResourceHelper.BuildSingleResourceMarkup(resourcePath, resourceType)); + result = MvcHtmlString.Create(ResourceHelper.BuildSingleResourceMarkup(resourcePath, resourceType, sectionName, attributes)); } } // Register the resource even if it had to be rendered inline (avoid repetitions). - register.Register(resourcePath, sectionName, throwException); + register.Register(resourcePath, sectionName, throwException, attributes); return result; } @@ -373,7 +799,8 @@ private static bool TryConfigureScriptManager(string scriptReference, IHttpHandl if (scriptManager != null) { - scriptManager.Scripts.Add(new ScriptReference(scriptReference)); + var updatedScriptReference = GetResourceOrMinified(scriptReference); + scriptManager.Scripts.Add(new ScriptReference(updatedScriptReference)); return true; } } @@ -405,43 +832,78 @@ private static string BuildHtmlResourcesMarkup(ResourceRegister resourceRegister { StringBuilder output = new StringBuilder(); - foreach (var resource in resourceRegister.GetResourcesForSection(sectionName)) + foreach (var resource in resourceRegister.GetResourceItemsForSection(sectionName)) { - if (!resourceRegister.IsRendered(resource)) + if (!resourceRegister.IsRendered(resource.ResourceKey)) { - output.Append(ResourceHelper.BuildSingleResourceMarkup(resource, resourceType)); - resourceRegister.MarkAsRendered(resource); + output.Append(ResourceHelper.BuildSingleResourceMarkup(resource.ResourceKey, resourceType, sectionName, resource.Attributes)); + resourceRegister.MarkAsRendered(resource.ResourceKey); } } return output.ToString(); } - private static string BuildSingleResourceMarkup(string resourceKey, ResourceType resourceType) + private static string BuildSingleResourceMarkup(string resourceKey, ResourceType resourceType, string sectionName, List> attributes = null) { string result; if (resourceType == ResourceType.Js) - result = ResourceHelper.BuildScriptMarkup(resourceKey); + result = ResourceHelper.BuildScriptMarkup(resourceKey, sectionName, attributes); else if (resourceType == ResourceType.Css) - result = ResourceHelper.BuildStyleSheetMarkup(resourceKey); + result = ResourceHelper.BuildStyleSheetMarkup(resourceKey, attributes); else result = string.Empty; return result; } - private static string BuildScriptMarkup(string resourceKey) + private static string BuildScriptMarkup(string resourceKey, string sectionName, List> attributes) { var tag = new TagBuilder("script"); + resourceKey = GetResourceOrMinified(resourceKey); + tag.Attributes["src"] = resourceKey; tag.Attributes["type"] = "text/javascript"; + if (attributes != null) + { + foreach (var attr in attributes) + { + tag.Attributes[attr.Key] = attr.Value; + } + } + + if (ResourceHelper.RenderScriptSection && !string.IsNullOrWhiteSpace(sectionName)) + { + tag.Attributes["data-sf-section"] = sectionName; + } + return tag.ToString(TagRenderMode.Normal); } - private static string BuildStyleSheetMarkup(string resourceKey) + private static string GetResourceOrMinified(string resourceKey) + { + if (!ResourceHelper.IsDebugMode) + { + var extensionIndex = resourceKey.LastIndexOf(".js"); + if (extensionIndex > 0 && !resourceKey.Contains(".min.js")) + { + var minFilePath = resourceKey.Insert(extensionIndex, ".min"); + var minPathWithoutParams = resourceKey.Substring(0, extensionIndex) + ".min.js"; + + if (VirtualPathManager.FileExists(minPathWithoutParams) || VirtualPathManager.FileExists(minFilePath)) + { + resourceKey = minFilePath; + } + } + } + + return resourceKey; + } + + private static string BuildStyleSheetMarkup(string resourceKey, List> attributes) { var tag = new TagBuilder("link"); @@ -449,13 +911,21 @@ private static string BuildStyleSheetMarkup(string resourceKey) tag.Attributes["rel"] = "stylesheet"; tag.Attributes["type"] = "text/css"; + if (attributes != null) + { + foreach (var attr in attributes) + { + tag.Attributes[attr.Key] = attr.Value; + } + } + return tag.ToString(TagRenderMode.SelfClosing); } - private const string JsRegisterName = "JsRegister"; - private const string CssRegisterName = "CssRegister"; + internal const string JsRegisterName = "JsRegister"; + internal const string CssRegisterName = "CssRegister"; - private class MvcScriptReference : ScriptReference + internal class MvcScriptReference : ScriptReference { public MvcScriptReference(ScriptReference reference) { @@ -496,10 +966,14 @@ private bool IsGetUrlFromPathException(Exception ex) /// /// This enum represents supported resource types. /// - private enum ResourceType + internal enum ResourceType { Js, Css } + + private static bool? isDebugMode; + + private static string[] rtlLanguages = { "ar", "he", "fa", "ku", "ur", "dv", "ps", "ha", "ks", "yi" }; } } \ No newline at end of file diff --git a/Telerik.Sitefinity.Frontend/Mvc/Helpers/SettingsHelpers.cs b/Telerik.Sitefinity.Frontend/Mvc/Helpers/SettingsHelpers.cs new file mode 100644 index 000000000..baa0b4271 --- /dev/null +++ b/Telerik.Sitefinity.Frontend/Mvc/Helpers/SettingsHelpers.cs @@ -0,0 +1,67 @@ +using System.Linq; +using System.Web.Script.Serialization; +using Telerik.Sitefinity.Configuration; +using Telerik.Sitefinity.Libraries.Model; +using Telerik.Sitefinity.Modules.Libraries.Configuration; +using Telerik.Sitefinity.Modules.Libraries.Images; +using Telerik.Sitefinity.Web.UI.ContentUI.Views.Backend.Detail.Definitions; +using Telerik.Sitefinity.Web.UI.Fields.Config; +using Telerik.Sitefinity.Web.UI.Fields.Definitions; + +namespace Telerik.Sitefinity.Frontend.Mvc.Helpers +{ + /// + /// This class contains helpers for working with widget settings. + /// + public static class SettingsHelpers + { + /// + /// Gets the media settings for specific media type + /// + /// Serialized media settings + public static string GetMediaSettings(string mediaType) + { + var libratiesConfig = Config.Get(); + object settings; + + switch (mediaType) + { + case "Image": + var singleImageUploadDefinition = libratiesConfig.ContentViewControls[ImagesDefinitions.BackendImagesDefinitionName]?.Views[ImagesDefinitions.SingleImageUploadDetailsView] as DetailFormViewDefinition; + var altTextRequired = (singleImageUploadDefinition?.Sections.First(s => s.Name == "MainSection")?.Fields.First(f => f.FieldName == nameof(Image.AlternativeText)) as TextFieldDefinition)?.Validation?.Required.GetValueOrDefault(); + + settings = new + { + AllowedExensionsSettings = libratiesConfig.Images.AllowedExensionsSettings, + EnableAllLanguagesSearch = libratiesConfig.EnableAllLanguagesSearch, + EnableSelectedFolderSearch = libratiesConfig.EnableSelectedFolderSearch, + AltTextRequired = altTextRequired + }; + break; + case "Video": + settings = new + { + AllowedExensionsSettings = libratiesConfig.Videos.AllowedExensionsSettings, + EnableAllLanguagesSearch = libratiesConfig.EnableAllLanguagesSearch, + EnableSelectedFolderSearch = libratiesConfig.EnableSelectedFolderSearch + }; + break; + case "Document": + settings = new + { + AllowedExensionsSettings = libratiesConfig.Documents.AllowedExensionsSettings, + EnableAllLanguagesSearch = libratiesConfig.EnableAllLanguagesSearch, + EnableSelectedFolderSearch = libratiesConfig.EnableSelectedFolderSearch, + AllowedExensions = libratiesConfig.Documents.AllowedExensions + }; + break; + default: + settings = null; + break; + } + + var serialzier = new JavaScriptSerializer(); + return serialzier.Serialize(settings); + } + } +} diff --git a/Telerik.Sitefinity.Frontend/Mvc/Helpers/SocialShareHelpers.cs b/Telerik.Sitefinity.Frontend/Mvc/Helpers/SocialShareHelpers.cs index a5fdac1ef..845919ff0 100644 --- a/Telerik.Sitefinity.Frontend/Mvc/Helpers/SocialShareHelpers.cs +++ b/Telerik.Sitefinity.Frontend/Mvc/Helpers/SocialShareHelpers.cs @@ -1,6 +1,6 @@ -using System.Web; +using System; +using System.Diagnostics.CodeAnalysis; using System.Web.Mvc; -using System.Web.Mvc.Html; namespace Telerik.Sitefinity.Frontend.Mvc.Helpers { @@ -10,22 +10,23 @@ public static class SocialShareHelpers /// Socials the share options. Redirect to the SocialShare control if exist else render error message /// /// The HTML helper. - public static System.Web.Mvc.MvcHtmlString SocialShareOptions(this HtmlHelper helper) + [Obsolete("Social sharing module has been removed. This helper will no longer work.")] + [SuppressMessage("Microsoft.Design", "CA1801:ReviewUnusedParameters")] + public static MvcHtmlString SocialShareOptions(this HtmlHelper helper) { - System.Web.Mvc.MvcHtmlString result; - try - { - result = helper.Action(ActionName, ControllerName); - } - catch (HttpException) - { - result = new System.Web.Mvc.MvcHtmlString("The SocialShare widget could not be found."); - } - - return result; + return null; } - private const string ActionName = "Index"; - private const string ControllerName = "SocialShare"; + /// + /// Socials the share options. Redirect to the SocialShare control if exist else render error message + /// + /// The HTML helper. + /// The data item which we will be sharing + [Obsolete("Social sharing module has been removed. This helper will no longer work.")] + [SuppressMessage("Microsoft.Design", "CA1801:ReviewUnusedParameters")] + public static MvcHtmlString SocialShareOptions(this HtmlHelper helper, Telerik.Sitefinity.Model.IHasTitle dataItem) + { + return null; + } } } \ No newline at end of file diff --git a/Telerik.Sitefinity.Frontend/Mvc/Helpers/UrlHelpers.cs b/Telerik.Sitefinity.Frontend/Mvc/Helpers/UrlHelpers.cs index fce4ef374..bea9444b9 100644 --- a/Telerik.Sitefinity.Frontend/Mvc/Helpers/UrlHelpers.cs +++ b/Telerik.Sitefinity.Frontend/Mvc/Helpers/UrlHelpers.cs @@ -1,145 +1,262 @@ -using System; -using System.Globalization; -using System.Web; -using System.Web.Hosting; -using System.Web.Mvc; -using System.Web.UI; -using Telerik.Sitefinity.Frontend.Mvc.Infrastructure; -using Telerik.Sitefinity.Frontend.Resources; -using Telerik.Sitefinity.Modules.Pages; -using Telerik.Sitefinity.Mvc.Rendering; -using Telerik.Sitefinity.Utilities.TypeConverters; - -namespace Telerik.Sitefinity.Frontend.Mvc.Helpers -{ - /// - /// Extension methods for UrlHelper. - /// - public static class UrlHelpers - { - /// - /// Resolves URL based on the current widget. - /// - /// The URL helper. - /// The content path. - /// Resolved URL. - /// contentPath - /// - /// Could not resolve the given URL because RouteData of the current context is null. - /// or - /// Could not resolve the given URL because RouteData does not contain \controller\ key. - /// - public static string WidgetContent(this UrlHelper helper, string contentPath) - { - if (contentPath.IsNullOrEmpty()) - throw new ArgumentNullException("contentPath"); - - var packagesManager = new PackageManager(); - var packageName = packagesManager.GetCurrentPackage(); - - if (contentPath.StartsWith("~", StringComparison.Ordinal) || contentPath.StartsWith("/", StringComparison.Ordinal) || contentPath.Contains("://")) - { - var url = UrlTransformations.AppendParam(contentPath, PackageManager.PackageUrlParameterName, packageName); - return helper.Content(url); - } - - if (helper.RequestContext.RouteData == null) - throw new InvalidOperationException("Could not resolve the given URL because RouteData of the current context is null."); - - var contentResolvedPath = string.Empty; - object controllerName; - - // "widgetName" is a parameter in the route of the Designer. It allows us to have a special fallback logic - // where we first check for the requested resource in the widget assembly and then fallback to the current controller assembly. - if (helper.RequestContext.RouteData.Values.TryGetValue("widgetName", out controllerName)) - contentResolvedPath = UrlHelpers.GetResourcePath((string)controllerName, contentPath, PackageManager.PackageUrlParameterName, packageName); - - if (string.IsNullOrEmpty(contentResolvedPath)) - { - if (helper.RequestContext.RouteData.Values.TryGetValue("controller", out controllerName)) - contentResolvedPath = UrlHelpers.GetResourcePath((string)controllerName, contentPath, PackageManager.PackageUrlParameterName, packageName); - else - throw new InvalidOperationException("Could not resolve the given URL because RouteData does not contain \"controller\" key."); - } - - if (string.IsNullOrEmpty(contentResolvedPath)) - { - var url = "~/" + FrontendManager.VirtualPathBuilder.GetVirtualPath(typeof(UrlHelpers).Assembly) + contentPath; - contentResolvedPath = UrlTransformations.AppendParam(url, PackageManager.PackageUrlParameterName, packageName); - } - - return helper.Content(contentResolvedPath); - } - - /// - /// Resolves URL based on the current widget. - /// - /// The helper. - /// The content path. - /// Name of the assembly. - /// - /// contentPath - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object,System.Object)")] - public static string WidgetContent(this UrlHelper helper, string contentPath, string assemblyName) - { - if (contentPath.IsNullOrEmpty()) - throw new ArgumentNullException("contentPath"); - - if (string.IsNullOrEmpty(assemblyName)) - return UrlHelpers.WidgetContent(helper, contentPath); - - var packagesManager = new PackageManager(); - var packageName = packagesManager.GetCurrentPackage(); - - if (contentPath.StartsWith("~", StringComparison.Ordinal) || contentPath.StartsWith("/", StringComparison.Ordinal) || contentPath.Contains("://")) - { - var url = UrlTransformations.AppendParam(contentPath, PackageManager.PackageUrlParameterName, packageName); - return helper.Content(url); - } - - var resourceUrl = string.Format("~/{0}/{1}", FrontendManager.VirtualPathBuilder.GetVirtualPath(assemblyName), contentPath); - var contentResolvedPath = UrlTransformations.AppendParam(resourceUrl, PackageManager.PackageUrlParameterName, packageName); - - return helper.Content(contentResolvedPath); - } - - /// - /// Gets the URL of an embedded resource. - /// - /// The helper. - /// A type from the assembly that embeds the resource. - /// The resource path. - /// The resource URL. - public static string EmbeddedResource(this UrlHelper helper, string type, string path) +using System; +using System.Linq; +using System.Text; +using System.Web.Hosting; +using System.Web.Mvc; +using Telerik.Sitefinity.Abstractions.VirtualPath; +using Telerik.Sitefinity.Frontend.Mvc.Infrastructure; +using Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Routing; +using Telerik.Sitefinity.Frontend.Resources; +using Telerik.Sitefinity.Mvc.Rendering; +using Telerik.Sitefinity.Taxonomies.Model; +using Telerik.Sitefinity.Utilities.TypeConverters; + +namespace Telerik.Sitefinity.Frontend.Mvc.Helpers +{ + /// + /// Extension methods for UrlHelper. + /// + public static class UrlHelpers + { + /// + /// Resolves URL based on the current widget. + /// + /// The URL helper. + /// The content path. + /// Resolved URL. + /// contentPath + /// + /// Could not resolve the given URL because RouteData of the current context is null. + /// or + /// Could not resolve the given URL because RouteData does not contain \controller\ key. + /// + public static string WidgetContent(this UrlHelper helper, string contentPath) { - var page = helper.RequestContext.HttpContext.Handler.GetPageHandler() ?? new PageProxy(null); - return page.ClientScript.GetWebResourceUrl(TypeResolutionService.ResolveType(type), path); - } - - /// - /// Gets the resource path. - /// - /// Name of the controller. - /// The content path. - /// Name of the paramater. - /// Name of the package. - /// - private static string GetResourcePath(string controllerName, string contentPath, string paramaterName, string packageName) - { - var controllerType = FrontendManager.ControllerFactory.ResolveControllerType(controllerName); - if (controllerType != null) - { - var alternatePath = FrontendManager.VirtualPathBuilder.GetVirtualPath(controllerType); - var baseUrl = "~/" + alternatePath + contentPath; - - if (HostingEnvironment.VirtualPathProvider == null || HostingEnvironment.VirtualPathProvider.FileExists(baseUrl)) - { - alternatePath = UrlTransformations.AppendParam(baseUrl, paramaterName, packageName); - return alternatePath; - } - } - - return string.Empty; - } - } -} + if (contentPath.IsNullOrEmpty()) + throw new ArgumentNullException("contentPath"); + + var packagesManager = new PackageManager(); + var packageName = packagesManager.GetCurrentPackage(); + + if (contentPath.StartsWith("~", StringComparison.Ordinal) || contentPath.StartsWith("/", StringComparison.Ordinal) || contentPath.Contains("://")) + { + var url = UrlTransformations.AppendParam(contentPath, PackageManager.PackageUrlParameterName, packageName); + url = UrlHelpers.AppendVersion(url); + return helper.Content(url); + } + + if (helper.RequestContext.RouteData == null) + throw new InvalidOperationException("Could not resolve the given URL because RouteData of the current context is null."); + + var contentResolvedPath = string.Empty; + object controllerName; + + // "widgetName" is a parameter in the route of the Designer. It allows us to have a special fallback logic + // where we first check for the requested resource in the widget assembly and then fallback to the current controller assembly. + if (helper.RequestContext.RouteData.Values.TryGetValue("widgetName", out controllerName)) + contentResolvedPath = UrlHelpers.GetResourcePath((string)controllerName, contentPath, PackageManager.PackageUrlParameterName, packageName); + + if (string.IsNullOrEmpty(contentResolvedPath)) + { + if (helper.RequestContext.RouteData.Values.TryGetValue("controller", out controllerName)) + contentResolvedPath = UrlHelpers.GetResourcePath((string)controllerName, contentPath, PackageManager.PackageUrlParameterName, packageName); + else + throw new InvalidOperationException("Could not resolve the given URL because RouteData does not contain \"controller\" key."); + } + + if (string.IsNullOrEmpty(contentResolvedPath)) + { + var url = "~/" + FrontendManager.VirtualPathBuilder.GetVirtualPath(typeof(UrlHelpers).Assembly) + contentPath; + contentResolvedPath = UrlTransformations.AppendParam(url, PackageManager.PackageUrlParameterName, packageName); + } + + contentResolvedPath = UrlHelpers.AppendVersion(contentResolvedPath); + + return helper.Content(contentResolvedPath); + } + + /// + /// Resolves URL based on the current widget. + /// + /// The helper. + /// The content path. + /// Name of the assembly. + /// + /// contentPath + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object,System.Object)")] + public static string WidgetContent(this UrlHelper helper, string contentPath, string assemblyName) + { + if (contentPath.IsNullOrEmpty()) + throw new ArgumentNullException("contentPath"); + + if (string.IsNullOrEmpty(assemblyName)) + return UrlHelpers.WidgetContent(helper, contentPath); + + var packagesManager = new PackageManager(); + var packageName = packagesManager.GetCurrentPackage(); + + if (contentPath.StartsWith("~", StringComparison.Ordinal) || contentPath.StartsWith("/", StringComparison.Ordinal) || contentPath.Contains("://")) + { + var url = UrlTransformations.AppendParam(contentPath, PackageManager.PackageUrlParameterName, packageName); + url = UrlHelpers.AppendVersion(url); + return helper.Content(url); + } + + var resourceUrl = string.Format("~/{0}/{1}", FrontendManager.VirtualPathBuilder.GetVirtualPath(assemblyName), contentPath); + var contentResolvedPath = UrlTransformations.AppendParam(resourceUrl, PackageManager.PackageUrlParameterName, packageName); + contentResolvedPath = UrlHelpers.AppendVersion(contentResolvedPath); + + return helper.Content(contentResolvedPath); + } + + /// + /// Gets the URL of an embedded resource. + /// + /// The helper. + /// A type from the assembly that embeds the resource. + /// The resource path. + /// The resource URL. + public static string EmbeddedResource(this UrlHelper helper, string type, string path) + { + var page = helper.RequestContext.HttpContext.Handler.GetPageHandler() ?? new PageProxy(null); + return page.ClientScript.GetWebResourceUrl(TypeResolutionService.ResolveType(type), path); + } + + /// + /// Gets the URL template used for the paging. + /// + /// The URL template. + public static string GetRedirectPagingUrl() + { + string redirectUrl; + if (UrlParamsMapperBase.UseNamedParametersRouting) + { + redirectUrl = "/" + FeatherActionInvoker.PagingNamedParameter + "/{0}"; + } + else + { + redirectUrl = "/{0}"; + } + + return redirectUrl; + } + + /// + /// Gets the URL template used for the paging when filtered by taxonomy. + /// + /// The URL template. + public static string GetRedirectPagingUrl(ITaxon taxonFilter) + { + string redirectUrl; + if (UrlParamsMapperBase.UseNamedParametersRouting) + { + redirectUrl = string.Format("/{0}/{1}/{2}", taxonFilter.Taxonomy.Name, taxonFilter.UrlName, FeatherActionInvoker.PagingNamedParameter) + "/{0}"; + } + else + { + redirectUrl = "/" + taxonFilter.UrlName + "/{0}"; + } + + return redirectUrl; + } + + /// + /// Gets the URL template used for the paging when filtered by taxonomy and url evaluation mode. + /// + /// The URL template. + public static string GetRedirectPagingUrl(ITaxon taxonFilter, string[] urlParams, string queryString) + { + bool addQueryString = !string.IsNullOrEmpty(queryString); + string redirectUrl; + if (UrlParamsMapperBase.UseNamedParametersRouting) + { + redirectUrl = string.Format("/{0}/{1}/{2}", taxonFilter.Taxonomy.Name, taxonFilter.UrlName, FeatherActionInvoker.PagingNamedParameter) + "/{0}"; + } + else + { + // 3 because we have the following structure /-in-tags/tags/tagName + if (urlParams != null && urlParams.Length >= 3) + { + if (taxonFilter is FlatTaxon) + { + redirectUrl = string.Format("/{0}/{1}/{2}", urlParams[0], urlParams[1], urlParams[2]); + } + else + { + if (urlParams[urlParams.Length - 1].Equals(taxonFilter.UrlName)) + { + // url is like /-in-category/categories/cat1/cat2 + string taxonFilterParams = string.Join("/", urlParams); + redirectUrl = "/" + taxonFilterParams; + } + else + { + // url is like /-in-category/categories/cat1/cat2/2 where '2' is the page so we want to exclude it from redirect url + string taxonFilterParams = string.Join("/", urlParams.Take(urlParams.Length - 1)); + redirectUrl = "/" + taxonFilterParams; + } + } + + // add /{0} at the very end for the page number + redirectUrl += "/{0}"; + } + else + { + addQueryString = false; + redirectUrl = "/{0}" + queryString; + } + } + + if (addQueryString) + { + redirectUrl += queryString; + } + + return redirectUrl; + } + + /// + /// Gets the resource path. + /// + /// Name of the controller. + /// The content path. + /// Name of the paramater. + /// Name of the package. + /// + private static string GetResourcePath(string controllerName, string contentPath, string paramaterName, string packageName) + { + var controllerType = FrontendManager.ControllerFactory.ResolveControllerType(controllerName); + if (controllerType != null) + { + var alternatePath = FrontendManager.VirtualPathBuilder.GetVirtualPath(controllerType); + var baseUrl = "~/" + alternatePath + contentPath; + + if (HostingEnvironment.VirtualPathProvider == null || HostingEnvironment.VirtualPathProvider.FileExists(baseUrl)) + { + alternatePath = UrlTransformations.AppendParam(baseUrl, paramaterName, packageName); + return alternatePath; + } + } + + return string.Empty; + } + + internal static string AppendVersion(string contentPath) + { + if (contentPath.EndsWith(".js") || contentPath.Contains(".js?")) + { + var hash = VirtualPathManager.GetFileHash(contentPath, null); + if (hash != null) + { + var bytes = Encoding.UTF8.GetBytes(hash); + var base64version = Convert.ToBase64String(bytes); + contentPath = UrlTransformations.AppendParam(contentPath, VersionQueryParam, base64version); + } + } + + return contentPath; + } + + private const string VersionQueryParam = "v"; + } +} diff --git a/Telerik.Sitefinity.Frontend/Mvc/Helpers/ViewSelectorHelpers.cs b/Telerik.Sitefinity.Frontend/Mvc/Helpers/ViewSelectorHelpers.cs index e96cb5239..3c50bcaab 100644 --- a/Telerik.Sitefinity.Frontend/Mvc/Helpers/ViewSelectorHelpers.cs +++ b/Telerik.Sitefinity.Frontend/Mvc/Helpers/ViewSelectorHelpers.cs @@ -28,6 +28,10 @@ public static IEnumerable GetViewNames(this HtmlHelper helper, string co throw new ArgumentException("Controller cannot be resolved."); } + if (controller.RouteData != null && controller.RouteData.Values["controller"] as string != null) + { + controller.RouteData.Values["controller"] = controllerName; + } var regex = new Regex(templateNamePattern, RegexOptions.IgnoreCase); var views = controller.GetViews().Where(view => Regex.IsMatch(view, templateNamePattern)).Select(view => regex.Match(view).Groups["viewName"].Value); @@ -44,6 +48,21 @@ public static IEnumerable GetViewNames(this HtmlHelper helper, string co /// Controller cannot be resolved. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "helper")] public static IEnumerable GetViewNames(this HtmlHelper helper, Controller controller, string templateNamePattern) + { + return ViewSelectorHelpers.GetViewNames(helper, controller, templateNamePattern, null); + } + + /// + /// Gets a collection with the view names which match the given pattern. + /// + /// The helper. + /// The controller. + /// The template name pattern. + /// The name of dynamic module. + /// Names of the views that match provided pattern. + /// Controller cannot be resolved. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "helper")] + public static IEnumerable GetViewNames(this HtmlHelper helper, Controller controller, string templateNamePattern, string moduleName) { if (controller == null) { @@ -51,7 +70,7 @@ public static IEnumerable GetViewNames(this HtmlHelper helper, Controlle } var regex = new Regex(templateNamePattern, RegexOptions.IgnoreCase); - var views = controller.GetViews().Where(view => Regex.IsMatch(view, templateNamePattern)).Select(view => regex.Match(view).Groups["viewName"].Value); + var views = controller.GetViews(null, moduleName).Where(view => Regex.IsMatch(view, templateNamePattern)).Select(view => regex.Match(view).Groups["viewName"].Value); return views; } diff --git a/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Compilation/CompilationPerformanceRazorView.cs b/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Compilation/CompilationPerformanceRazorView.cs new file mode 100644 index 000000000..b94fcb6e1 --- /dev/null +++ b/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Compilation/CompilationPerformanceRazorView.cs @@ -0,0 +1,306 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Web; +using System.Web.Caching; +using System.Web.Compilation; +using System.Web.Hosting; +using System.Web.Mvc; +using Telerik.Sitefinity.Abstractions; +using Telerik.Sitefinity.DynamicModules.Builder; +using Telerik.Sitefinity.DynamicModules.Builder.Model; +using Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Controllers; +using Telerik.Sitefinity.Frontend.Mvc.Models; +using Telerik.Sitefinity.Frontend.Resources; +using Telerik.Sitefinity.HealthMonitoring; +using Telerik.Sitefinity.Modules.Forms; +using Telerik.Sitefinity.Modules.Pages; +using Telerik.Sitefinity.Mvc.Store; +using Telerik.Sitefinity.Pages.Model; +using Telerik.Sitefinity.Services; +using Telerik.Sitefinity.Web; + +namespace Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Compilation +{ + /// + /// This class represents a which allows measurement of its compilation performance. + /// + /// + internal class CompilationPerformanceRazorView : RazorView + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The controller context. + /// The view path. + /// The layout or master page. + /// A value that indicates whether view start files should be executed before the view. + /// The set of extensions that will be used when looking up view start files. + public CompilationPerformanceRazorView(ControllerContext controllerContext, string viewPath, string layoutPath, bool runViewStartPages, IEnumerable viewStartFileExtensions) : + this(controllerContext, viewPath, layoutPath, runViewStartPages, viewStartFileExtensions, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The controller context. + /// The view path. + /// The layout or master page. + /// A value that indicates whether view start files should be executed before the view. + /// The set of extensions that will be used when looking up view start files. + /// The view page activator. + public CompilationPerformanceRazorView(ControllerContext controllerContext, string viewPath, string layoutPath, bool runViewStartPages, IEnumerable viewStartFileExtensions, IViewPageActivator viewPageActivator) + : base(controllerContext, viewPath, layoutPath, runViewStartPages, viewStartFileExtensions, viewPageActivator) + { + this.controllerContext = controllerContext; + this.viewPageActivator = viewPageActivator; + } + + #endregion + + #region Public Methods + + /// + /// Renders the specified view context by using the specified the writer object. + /// + /// Information related to rendering a view, such as view data, temporary data, and form context. + /// The writer object. + public override void Render(ViewContext viewContext, TextWriter writer) + { + if (!this.ShouldMeasurePerformance(this.ViewPath) || !this.RenderWithPerformanceMeasurement(viewContext, writer)) + base.Render(viewContext, writer); + } + + #endregion + + #region Private Methods + + private bool RenderWithPerformanceMeasurement(ViewContext viewContext, TextWriter writer) + { + if (viewContext == null) + return false; + + var region = this.GetMethodPerformanceRegion(viewContext); + if (region == null) + return false; + + Type compiledType = null; + try + { + compiledType = System.Web.Compilation.BuildManager.GetCompiledType(this.ViewPath); + } + finally + { + region.Dispose(); + } + + if (compiledType == null) + return false; + + var obj = this.viewPageActivator.Create(this.controllerContext, compiledType); + if (obj == null) + return false; + + this.RenderView(viewContext, writer, obj); + + return true; + } + + private bool ShouldMeasurePerformance(string virtualPath) + { + if (virtualPath == null || HttpContext.Current == null) + return false; + + return HostingEnvironment.VirtualPathProvider.FileExists(virtualPath) && BuildManager.GetCachedBuildDependencySet(HttpContext.Current, virtualPath) == null; + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The type of the thrown exception is not of interest in this scenario. We are only interested in marking the start of the performance measurement as failed.")] + private IDisposable GetMethodPerformanceRegion(ViewContext viewContext) + { + if (SystemManager.HttpContextItems == null || !SystemManager.HttpContextItems.Contains(SiteMapBase.CurrentNodeKey) || !(SystemManager.HttpContextItems[SiteMapBase.CurrentNodeKey] is PageSiteNode)) + return null; + + var pageNode = (PageSiteNode)SystemManager.HttpContextItems[SiteMapBase.CurrentNodeKey]; + + try + { + int slashIndex = this.ViewPath.LastIndexOf('/'); + var fullViewName = this.ViewPath.Substring(slashIndex + 1); + + int hashIndex = fullViewName.IndexOf('#'); + var viewName = hashIndex < 0 ? fullViewName : fullViewName.Substring(0, hashIndex); + + var actionName = (string)viewContext.RequestContext.RouteData.Values["action"]; + var virtualPath = hashIndex < 0 ? this.ViewPath : this.ViewPath.Substring(0, slashIndex + hashIndex + 1); + var source = this.GetSource(this.ViewPath); + var packageManager = new PackageManager(); + var resourcePackage = packageManager.GetCurrentPackage(); + var controllerName = viewContext.Controller.GetType().FullName; + var widgetName = this.GetWidgetName(this.controllerContext); + if (this.ViewPath.StartsWith(CompilationPerformanceRazorView.FormsResolverPath, StringComparison.OrdinalIgnoreCase) && viewName.EndsWith(CompilationPerformanceRazorView.RazorViewSuffix, StringComparison.OrdinalIgnoreCase)) + { + Guid formId; + var formIdString = viewName.Left(viewName.Length - CompilationPerformanceRazorView.RazorViewSuffix.Length); + if (Guid.TryParse(formIdString, out formId)) + { + var form = FormsManager.GetManager().GetForms().FirstOrDefault(f => f.Id == formId); + if (form != null) + { + viewName = form.Title + " (" + formIdString + ")"; + } + } + } + + var isBackendRequest = bool.Parse(SystemManager.CurrentHttpContext.Items[SystemManager.IsBackendRequestKey].ToString()); + var rootNodeId = isBackendRequest ? SiteInitializer.BackendRootNodeId : SiteInitializer.CurrentFrontendRootNodeId; + var siteId = SystemManager.CurrentContext.CurrentSite.Id; + var machineName = Environment.MachineName; + + var key = string.Format("Compile view \"{0}\" of controller \"{1}\"", fullViewName, controllerName); + var data = new Dictionary() + { + { CompilationPerformanceRazorView.ViewNameKey, viewName }, + { CompilationPerformanceRazorView.PageIdKey, pageNode.PageId }, + { CompilationPerformanceRazorView.PageTitleKey, pageNode.Title }, + { CompilationPerformanceRazorView.ResourcePackageKey, resourcePackage ?? string.Empty }, + { CompilationPerformanceRazorView.ActionNameKey, actionName }, + { CompilationPerformanceRazorView.ControllerNameKey, controllerName }, + { CompilationPerformanceRazorView.WidgetNameKey, widgetName ?? string.Empty }, + { CompilationPerformanceRazorView.VirtualPathKey, this.ViewPath }, + { CompilationPerformanceRazorView.MachineNameKey, machineName }, + { CompilationPerformanceRazorView.SiteIdKey, siteId }, + { CompilationPerformanceRazorView.RootNodeIdKey, rootNodeId }, + { CompilationPerformanceRazorView.SourceKey, virtualPath }, + { CompilationPerformanceRazorView.ViewSourceKey, source } + }; + + return (IDisposable)Activator.CreateInstance(CompilationPerformanceRazorView.methodPerformanceRegionType, new object[] { key, CompilationPerformanceRazorView.ViewCompilationCategory, data }); + } + catch + { + return null; + } + } + + private string GetSource(string viewPath) + { + var cacheDep = HostingEnvironment.VirtualPathProvider.GetCacheDependency(viewPath, new object[0], DateTime.UtcNow); + + // We are looking for both public and non-public method GetFileDependencies because of differences in the .NET Framework versions + var getFileDependencies = typeof(CacheDependency).GetMethod("GetFileDependencies", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic); + var getDependencyArray = typeof(AggregateCacheDependency).GetMethod("GetDependencyArray", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + + while (cacheDep is AggregateCacheDependency) + { + var deps = getDependencyArray.Invoke(cacheDep, null) as CacheDependency[]; + if (deps == null || deps.Length == 0) + break; + + cacheDep = deps[deps.Length - 1]; + } + + if (cacheDep is Abstractions.VirtualPath.ControlPresentationCacheDependency) + { + var title = this.ExtractTemplateTitle((Abstractions.VirtualPath.ControlPresentationCacheDependency)cacheDep); + if (title != null) + { + return "Widget template: " + title + " (DB)"; + } + } + + var files = getFileDependencies.Invoke(cacheDep, null) as string[]; + if (files != null && files.Length > 0) + return files[files.Length - 1].Replace(HostingEnvironment.ApplicationPhysicalPath, "~\\"); + else + return viewPath; + } + + private string ExtractTemplateTitle(Abstractions.VirtualPath.ControlPresentationCacheDependency cacheDep) + { + var cacheDepId = cacheDep.GetUniqueID().Right(cacheDep.GetUniqueID().Length - cacheDep.UniquePrefix.Length - 1); + Guid id; + if (!Guid.TryParse(cacheDepId, out id)) + return null; + + var manager = PageManager.GetManager(); + string title; + using (new Data.ElevatedModeRegion(manager)) + { + title = manager.GetPresentationItem(id).Name; + } + + return title; + } + + private string GetWidgetName(ControllerContext context) + { + var controllerType = context.Controller.GetType(); + + // Check in controller store + var controllerInfo = ControllerStore.Controllers() + .FirstOrDefault(c => c.ControllerType == controllerType); + + if (controllerInfo != null && !controllerInfo.DefaultToolboxItemTitle.IsNullOrEmpty()) + return controllerInfo.DefaultToolboxItemTitle; + + // Check for dynamic type + var modelProperty = controllerType.GetProperty("Model"); + if (modelProperty != null) + { + var model = modelProperty.GetValue(context.Controller) as ContentModelBase; + if (model != null) + { + var contentType = model.ContentType; + + var dynamicType = ModuleBuilderManager.GetModules().GetTypeByFullName(contentType.FullName); + if (dynamicType != null) + return dynamicType.DisplayName; + } + } + + if (this.ViewPath.StartsWith(CompilationPerformanceRazorView.LayoutVirtualPathBeginning, StringComparison.OrdinalIgnoreCase)) + return CompilationPerformanceRazorView.Layout; + + var controllerName = FrontendManager.ControllerFactory.GetControllerName(controllerType); + + return controllerName; + } + + #endregion + + #region Fields and Constants + + private ControllerContext controllerContext; + private IViewPageActivator viewPageActivator; + + internal const string ViewCompilationCategory = "ViewCompilation"; + + internal const string ViewNameKey = "View"; + internal const string PageIdKey = "PageId"; + internal const string PageTitleKey = "PageTitle"; + internal const string ResourcePackageKey = "ResourcePackage"; + internal const string ActionNameKey = "Action"; + internal const string ControllerNameKey = "Controller"; + internal const string WidgetNameKey = "Widget"; + internal const string VirtualPathKey = "VirtualPath"; + internal const string MachineNameKey = "MachineName"; + internal const string SiteIdKey = "SiteId"; + internal const string RootNodeIdKey = "RootNodeId"; + internal const string SourceKey = "Source"; + internal const string ViewSourceKey = "ViewSource"; + + private const string LayoutVirtualPathBeginning = "~/Frontend-Assembly/Telerik.Sitefinity.Frontend/Mvc/Views/Layouts"; + private const string Layout = "Layout"; + private const string FormsResolverPath = "~/Mvc-Form-View/"; + private const string RazorViewSuffix = ".cshtml"; + + private static Type methodPerformanceRegionType = Assembly.GetAssembly(typeof(SystemManager)).GetType("Telerik.Sitefinity.HealthMonitoring.MethodPerformanceRegion", false); + + #endregion + } +} diff --git a/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Compilation/CompilationPerformanceRazorViewEngine.cs b/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Compilation/CompilationPerformanceRazorViewEngine.cs new file mode 100644 index 000000000..7a1352bc0 --- /dev/null +++ b/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Compilation/CompilationPerformanceRazorViewEngine.cs @@ -0,0 +1,76 @@ +using System.Reflection; +using System.Web.Mvc; + +namespace Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Compilation +{ + /// + /// This class represents a which creates objects allowing measurement of their compilation performance. + /// + /// + internal class CompilationPerformanceRazorViewEngine : RazorViewEngine + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public CompilationPerformanceRazorViewEngine() + : this(null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The page activator. + public CompilationPerformanceRazorViewEngine(IViewPageActivator pageActivator) + : base(pageActivator) + { + } + + #endregion + + #region Protected Methods + + /// + /// Creates a view by using the specified controller context and the paths of the view and master view. + /// + /// The controller context. + /// The path to the view. + /// The path to the master view. + /// + /// The view. + /// + protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath) + { + var view = new CompilationPerformanceRazorView(controllerContext, viewPath, masterPath, true, this.FileExtensions, this.ViewPageActivator); + displayModeProviderProperty.SetValue(view, this.DisplayModeProvider); + + return view; + } + + /// + /// Creates a partial view using the specified controller context and partial path. + /// + /// The controller context. + /// The path to the partial view. + /// + /// The partial view. + /// + protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath) + { + var view = new CompilationPerformanceRazorView(controllerContext, partialPath, null, false, this.FileExtensions, this.ViewPageActivator); + displayModeProviderProperty.SetValue(view, this.DisplayModeProvider); + + return view; + } + + #endregion + + #region Fields + + private static PropertyInfo displayModeProviderProperty = typeof(RazorView).GetProperty("DisplayModeProvider", BindingFlags.Instance | BindingFlags.NonPublic); + + #endregion + } +} diff --git a/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/Attributes/ControllerContainerAttribute.cs b/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/Attributes/ControllerContainerAttribute.cs index 3e6903939..892e95b00 100644 --- a/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/Attributes/ControllerContainerAttribute.cs +++ b/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/Attributes/ControllerContainerAttribute.cs @@ -1,4 +1,5 @@ using System; +using System.Web.Mvc; namespace Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Controllers.Attributes { diff --git a/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/ContentBaseController.cs b/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/ContentBaseController.cs new file mode 100644 index 000000000..7220e04a4 --- /dev/null +++ b/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/ContentBaseController.cs @@ -0,0 +1,252 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Web.Mvc; +using Telerik.Sitefinity.Configuration; +using Telerik.Sitefinity.Data; +using Telerik.Sitefinity.DynamicModules; +using Telerik.Sitefinity.Model; +using Telerik.Sitefinity.Modules.Pages; +using Telerik.Sitefinity.Mvc; +using Telerik.Sitefinity.Services; +using Telerik.Sitefinity.Services.Configuration; +using Telerik.Sitefinity.Web; +using Telerik.Sitefinity.ContentLocations; +using Telerik.Sitefinity.Lifecycle; + +namespace Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Controllers +{ + /// + /// This class represents the base controller of content widgets. + /// + public abstract class ContentBaseController : Controller + { + #region Properties + + /// + /// Gets the metadata container.Title for search engines + /// + /// + /// + /// The metadata container. + /// + [TypeConverter(typeof(ExpandableObjectConverter))] + public virtual MetadataModel MetadataFields + { + get + { + if (this.metadata == null) + { + this.metadata = new MetadataModel(); + this.metadata.OpenGraphType = PageHelper.OpenGraphTypes.Website; + this.metadata.PageTitleMode = Telerik.Sitefinity.Mvc.ControllerActionInvoker.PageTitleModes.Replace; + } + + return this.metadata; + } + set + { + metadata = value; + } + } + + #endregion + + #region Methods + + /// + /// Populates the viewbag with values for metadata properties. + /// + /// The item. + protected void InitializeMetadataDetailsViewBag(IDataItem item) + { + if (this.IsDesignMode && !this.IsPreviewMode) + { + return; + } + + string parentMainValue = string.Empty; + var seoAndOpenGraphConfig = this.GetSeoAndOpenGraphConfig(); + MetadataModel metadataProperties = new MetadataModel(); + + metadataProperties.SEOEnabled = seoAndOpenGraphConfig.EnabledSEO; + metadataProperties.OpenGraphEnabled = seoAndOpenGraphConfig.EnabledOpenGraph; + metadataProperties.SEOEnabledPerWidget = this.MetadataFields.SEOEnabled; + metadataProperties.OpenGraphEnabledPerWidget = this.MetadataFields.OpenGraphEnabled; + bool isSeoEnabled = metadataProperties.SEOEnabled && metadataProperties.SEOEnabledPerWidget; + bool isOpenGraphEnabled = metadataProperties.OpenGraphEnabled && metadataProperties.OpenGraphEnabledPerWidget; + + if (isSeoEnabled) + { + metadataProperties.MetaTitle = this.GetTitleProperty(item, new[] { this.MetadataFields.MetaTitle, PageHelper.MetaDataProperties.MetaTitle }); + metadataProperties.MetaDescription = this.GetDescriptionProperty(item, new[] { this.MetadataFields.MetaDescription, PageHelper.MetaDataProperties.MetaDescription }); + } + + if (isOpenGraphEnabled) + { + metadataProperties.OpenGraphTitle = this.GetTitleProperty(item, new[] { this.MetadataFields.OpenGraphTitle, PageHelper.MetaDataProperties.OpenGraphTitle, this.MetadataFields.MetaTitle, PageHelper.MetaDataProperties.MetaTitle }); + metadataProperties.OpenGraphDescription = this.GetDescriptionProperty(item, new[] { this.MetadataFields.OpenGraphDescription, PageHelper.MetaDataProperties.OpenGraphDescription, this.MetadataFields.MetaDescription, PageHelper.MetaDataProperties.MetaDescription }); + metadataProperties.Url = this.GetDefaultCanonicalUrl(item); + metadataProperties.OpenGraphType = this.MetadataFields.OpenGraphType; + metadataProperties.OpenGraphImage = PageHelper.GetFieldValue(item, new[] { this.MetadataFields.OpenGraphImage, PageHelper.MetaDataProperties.OpenGraphImage }); + metadataProperties.OpenGraphVideo = PageHelper.GetFieldValue(item, new[] { this.MetadataFields.OpenGraphVideo, PageHelper.MetaDataProperties.OpenGraphVideo }); + metadataProperties.SiteName = SystemManager.CurrentContext.CurrentSite.Name; + } + + if (isSeoEnabled || isOpenGraphEnabled) + { + metadataProperties.PageTitleMode = this.MetadataFields.PageTitleMode; + + if (this.MetadataFields.PageTitleMode == Telerik.Sitefinity.Mvc.ControllerActionInvoker.PageTitleModes.Hierarchy && typeof(IDynamicContentWidget).IsAssignableFrom(this.GetType())) + { + parentMainValue = this.GetParentMainFiledValue(item); + if (!string.IsNullOrEmpty(parentMainValue)) + { + this.ViewBag.ParentMainValue = parentMainValue; + } + } + } + + this.ViewBag.Metadata = metadataProperties; + } + + /// + /// Determines whether the page is in design mode. + /// + protected virtual bool IsDesignMode + { + get + { + return SystemManager.IsDesignMode; + } + } + + /// + /// Gets a value indicating whether the current request is for page in preview mode. + /// + protected virtual bool IsPreviewMode + { + get + { + return SystemManager.IsPreviewMode; + } + } + + /// + /// Gets the item default location. + /// + /// The item. + /// + /// The item default location. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "The url is needed here as a string.")] + internal virtual string GetDefaultCanonicalUrl(IDataItem item) + { + IManager manager = null; + if (!ManagerBase.TryGetMappedManager(item.GetType(), item.GetProviderName(), out manager)) + return null; + + var locationsService = SystemManager.GetContentLocationService(); + if (item is ILifecycleDataItemGeneric lifecycleDataItem && + lifecycleDataItem.Status != GenericContent.Model.ContentLifecycleStatus.Master && + manager is ILifecycleManager lifecycleManager) + { + item = lifecycleManager.Lifecycle.GetMaster(lifecycleDataItem); + } + + var location = locationsService.GetItemDefaultLocation(item); + if (location != null) + { + return location.ItemAbsoluteUrl; + } + + var page = this.HttpContext.CurrentHandler.GetPageHandler(); + var pageNode = SiteMapBase.GetActualCurrentNode(); + var canonicalUrl = page.GetCanonicalUrlForPage(pageNode); + + return canonicalUrl; + } + + /// + /// Adds the canonical tag in the page headers HTML tag, like . + /// + /// The page. + /// The item. + protected void AddCanonicalUrlTagIfEnabled(System.Web.UI.Page page, IDataItem item) + { + var contentView = this as IContentLocatableView; + if (contentView != null) + { + var defaultCanonicalUrl = this.GetDefaultCanonicalUrl(item); + contentView.AddCanonicalUrlTagIfEnabled(page, defaultCanonicalUrl); + } + } + + internal virtual SeoAndOpenGraphElement GetSeoAndOpenGraphConfig() + { + return Config.Get().SeoAndOpenGraphConfig; + } + + private string GetParentMainFiledValue(IDataItem item) + { + string parentMainValue = string.Empty; + var property = TypeDescriptor.GetProperties(item)["ParentItem"]; + if (property != null) + { + var parent = property.GetValue(item); + parentMainValue = this.GetParentMainValueFromProperty(parent); + + var parentProperty = TypeDescriptor.GetProperties(parent)["ParentItem"]; + + if (parentProperty != null) + { + var grandParent = property.GetValue(parent); + parentMainValue = parentMainValue + " - " + this.GetParentMainValueFromProperty(grandParent); + } + } + + return parentMainValue; + } + + private string GetParentMainValueFromProperty(object itemValue) + { + var parentMainField = this.GetDynamicContentType().ParentModuleType.MainShortTextFieldName; + string[] field = { parentMainField }; + + return PageHelper.GetFieldValue(itemValue, field); + } + + private string GetTitleProperty(object detailItem, string[] propertyNames) + { + var fields = new List(propertyNames); + + if (typeof(IDynamicContentWidget).IsAssignableFrom(this.GetType())) + { + var dynamicType = this.GetDynamicContentType(); + var title = dynamicType == null ? PageHelper.MetaDataProperties.Title : dynamicType.MainShortTextFieldName; + fields.Add(title); + } + else + { + fields.Add(PageHelper.MetaDataProperties.Title); + } + + return PageHelper.GetFieldValue(detailItem, fields.ToArray()); + } + + private string GetDescriptionProperty(object detailItem, string[] propertyNames) + { + var fields = new List(propertyNames); + fields.Add(PageHelper.MetaDataProperties.Description); + + return PageHelper.GetFieldValue(detailItem, fields.ToArray()); + } + + #endregion + + #region Fields and constants + + private MetadataModel metadata; + + #endregion + } +} \ No newline at end of file diff --git a/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/ControllerContainerInitializer.cs b/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/ControllerContainerInitializer.cs index e3f88459b..f7b33cb2d 100644 --- a/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/ControllerContainerInitializer.cs +++ b/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/ControllerContainerInitializer.cs @@ -4,14 +4,16 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Threading.Tasks; using System.Web.Hosting; using System.Web.Mvc; using System.Web.Routing; -using RazorGenerator.Mvc; +using System.Web.Script.Serialization; using Telerik.Microsoft.Practices.Unity; using Telerik.Sitefinity.Abstractions; using Telerik.Sitefinity.Abstractions.VirtualPath; using Telerik.Sitefinity.Configuration; +using Telerik.Sitefinity.Data; using Telerik.Sitefinity.Frontend.Mvc.Controllers; using Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Controllers.Attributes; using Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Routing; @@ -24,6 +26,8 @@ using Telerik.Sitefinity.Mvc.Store; using Telerik.Sitefinity.Pages; using Telerik.Sitefinity.Services; +using Telerik.Sitefinity.Taxonomies; +using Telerik.Sitefinity.Taxonomies.Model; namespace Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Controllers { @@ -87,7 +91,7 @@ public virtual void Initialize() this.RegisterVirtualPaths(this.ControllerContainerAssemblies); - var controllerTypes = this.GetControllers(this.ControllerContainerAssemblies); + var controllerTypes = this.GetControllers(); this.InitializeControllers(controllerTypes); this.InitializeCustomRouting(); @@ -112,9 +116,19 @@ public virtual void Uninitialize() this.ControllerContainerAssemblies = null; // Clears all controllers - foreach (var ctrl in ControllerStore.Controllers().ToList()) + // ControllerStore.ClearControllers(); + + var sitefinityViewEngines = ViewEngines.Engines.Where(v => v != null && v.GetType() == typeof(CompositePrecompiledMvcEngineWrapper)).ToList(); + foreach (var sitefinityViewEngine in sitefinityViewEngines) + { + ViewEngines.Engines.Remove(sitefinityViewEngine); + } + + var sitefinityViewEngineExists = ViewEngines.Engines.Any(v => v.GetType() == typeof(SitefinityViewEngine)); + if (!sitefinityViewEngineExists) { - ControllerStore.RemoveController(ctrl.ControllerType); + // add Sitefinity view engine + ViewEngines.Engines.Add(new SitefinityViewEngine()); } } @@ -123,30 +137,37 @@ public virtual void Uninitialize() /// public virtual IEnumerable RetrieveAssemblies() { - var assemblyFileNames = this.RetrieveAssembliesFileNames().ToArray(); - var result = new List(); - - foreach (var assemblyFileName in assemblyFileNames) + using (new HealthMonitoring.MethodPerformanceRegion("ControllerContainerInitializer.RetrieveAssemblies")) { - if (this.IsControllerContainer(assemblyFileName)) - { - var assembly = this.LoadAssembly(assemblyFileName); - this.InitializeControllerContainer(assembly); - - result.Add(assembly); - } + IEnumerable assemblyFileNames = this.RetrieveControllerAssembliesFileNames().Distinct().ToArray(); + IList> retrieveAssemblyTasks = new List>(); - if (this.IsMarkedAssembly(assemblyFileName)) + foreach (string assemblyFileName in assemblyFileNames) { - result.Add(this.LoadAssembly(assemblyFileName)); + retrieveAssemblyTasks.Add(this.LoadControllerAssemblyAsync(assemblyFileName)); } - } - return result; + Task.WaitAll(retrieveAssemblyTasks.ToArray()); + + IEnumerable result = retrieveAssemblyTasks + .Select(v => v.Result) + .Where(v => v != null); + + return result; + } } #endregion + + #region Internal + internal IEnumerable GetControllers() + { + using (new HealthMonitoring.MethodPerformanceRegion("ControllerContainerInitializer.GetControllers")) + return this.GetControllers(this.ControllerContainerAssemblies); + } + #endregion + #region Protected members /// @@ -158,9 +179,9 @@ protected virtual void InitializeControllerContainer(Assembly container) if (container == null) throw new ArgumentNullException("container"); - var containerAttribute = container.GetCustomAttributes(false).Single(attr => attr.GetType().AssemblyQualifiedName == typeof(ControllerContainerAttribute).AssemblyQualifiedName) as ControllerContainerAttribute; + var containerAttribute = container.GetCustomAttributes(false).FirstOrDefault(attr => attr.GetType().AssemblyQualifiedName == typeof(ControllerContainerAttribute).AssemblyQualifiedName) as ControllerContainerAttribute; - if (containerAttribute.InitializationType == null || containerAttribute.InitializationMethod.IsNullOrWhitespace()) + if (containerAttribute == null || containerAttribute.InitializationType == null || containerAttribute.InitializationMethod.IsNullOrWhitespace()) return; var initializationMethod = containerAttribute.InitializationType.GetMethod(containerAttribute.InitializationMethod); @@ -229,12 +250,7 @@ protected virtual void InitializeControllers(IEnumerable controllers) this.RegisterControllerFactory(); this.RemoveSitefinityViewEngine(); this.ReplaceControllerFactory(); - - foreach (var controller in controllers) - { - this.RegisterController(controller); - ControllerContainerInitializer.RegisterStringResources(controller); - } + this.RegisterControllers(controllers); } protected virtual void UninitializeGlobalFilters() @@ -268,12 +284,53 @@ protected virtual void RegisterController(Type controller) } /// - /// Gets the assemblies file names that will be inspected for controllers. + /// Gets the assemblies file names from the controller container assembly cache. /// - protected virtual IEnumerable RetrieveAssembliesFileNames() + protected virtual IEnumerable RetrieveControllerAssembliesFileNames() { + var isFeatherenabled = SystemManager.GetApplicationModule(FrontendModule.ModuleName) != null; + bool useCachedControllerContainerAssemblies = false; + if (isFeatherenabled) + { + useCachedControllerContainerAssemblies = Config.Get().UseCachedControllerContainerAssemblies; + } + var controllerAssemblyPath = Path.Combine(HostingEnvironment.ApplicationPhysicalPath, "bin"); - return Directory.EnumerateFiles(controllerAssemblyPath, "*.dll", SearchOption.TopDirectoryOnly); + + if (useCachedControllerContainerAssemblies) + { + var pathToCacheFile = Path.Combine(HostingEnvironment.ApplicationPhysicalPath, "bin", "ControllerContainerAsembliesLocation.json"); + if (File.Exists(pathToCacheFile)) + { + try + { + var file = File.ReadAllText(pathToCacheFile); + var filesFromcache = new JavaScriptSerializer().Deserialize(file); + + // append path to the bin in order to be able to load the assemblies + return filesFromcache.Select(x => Path.Combine(controllerAssemblyPath, x)); + } + catch (Exception ex) + { + Log.Write(string.Format("Attempt to read from ControllerContainerAsembliesLocation.json was unsuccessful: {0}", ex.Message), ConfigurationPolicy.ErrorLog); + } + } + } + + var allAssemblyNames = Directory.EnumerateFiles(controllerAssemblyPath, "*.dll", SearchOption.TopDirectoryOnly); + IList> retrieveAssemblyTasks = new List>(); + + foreach (string assemblyFileName in allAssemblyNames) + { + retrieveAssemblyTasks.Add(this.RetrieveControllerAssemblyAsync(assemblyFileName)); + } + + Task.WaitAll(retrieveAssemblyTasks.ToArray()); + var controllerAssembliesNames = retrieveAssemblyTasks + .Select(x => x.Result) + .Where(x => !string.IsNullOrEmpty(x)); + + return controllerAssembliesNames; } /// @@ -302,8 +359,13 @@ protected virtual void InitializeCustomRouting() { ObjectFactory.Container.RegisterType(); ObjectFactory.Container.RegisterType("int"); - ObjectFactory.Container.RegisterType("category"); - ObjectFactory.Container.RegisterType("tag"); + + Task.Run(() => + { + this.RegisterTaxonomyRoutes(); + }); + + CacheDependency.Subscribe(typeof(Taxonomy), this.OnTaxonomiesUpdated); string mvcControllerProxySettingsPropertyDescriptorName = string.Format("{0}.{1}", typeof(MvcWidgetProxy).FullName, "Settings"); ObjectFactory.Container.RegisterType(mvcControllerProxySettingsPropertyDescriptorName); @@ -311,10 +373,92 @@ protected virtual void InitializeCustomRouting() FrontendManager.AttributeRouting.MapMvcAttributeRoutes(); } + /// + /// Register controllers into the store + /// + /// The controllers to be registered. + protected virtual void RegisterControllers(IEnumerable controllers) + { + controllers.ToList().ForEach(c => ControllerContainerInitializer.RegisterStringResources(c)); + } + #endregion #region Private members - + + private void RegisterTaxonomyRoutes() + { + var taxonomies = TaxonomyManager.GetTaxonomiesCache(); + + if (taxonomies.Count() > 0) + { + foreach (var taxonomy in taxonomies) + { + var taxonomyName = this.GetTaxonomyName(taxonomy.Id, taxonomy.Name); + if (!ObjectFactory.IsTypeRegistered(taxonomyName)) + ObjectFactory.Container.RegisterType(taxonomyName, new InjectionConstructor(taxonomyName)); + } + } + } + + private string GetTaxonomyName(Guid id, string name) + { + var taxonomyName = string.Empty; + + if (id == TaxonomyManager.TagsTaxonomyId) + { + taxonomyName = "tag"; + } + else if (id == TaxonomyManager.CategoriesTaxonomyId) + { + taxonomyName = "category"; + } + else + { + taxonomyName = name.ToLowerInvariant(); + } + + return taxonomyName; + } + + private Task RetrieveControllerAssemblyAsync(string assemblyFileName) + { + return Task.Run(() => this.RetrieveAssembly(assemblyFileName)); + } + + private Task LoadControllerAssemblyAsync(string assemblyFileName) + { + return Task.Run(() => this.LoadControllerAssembly(assemblyFileName)); + } + + private Assembly LoadControllerAssembly(string assemblyFileNameWithPath) + { + try + { + Assembly assembly = this.LoadAssembly(assemblyFileNameWithPath); + this.InitializeControllerContainer(assembly); + return assembly; + } + catch (Exception) + { + var lastIndexOfPathSeperator = assemblyFileNameWithPath.LastIndexOf("\\"); + var fileNameFailedToLoad = assemblyFileNameWithPath.Substring(lastIndexOfPathSeperator >= 0 ? lastIndexOfPathSeperator : 0); + Log.Write(string.Format("Attempt to load {0} failed. Check if the assembly is present in the bin", fileNameFailedToLoad), ConfigurationPolicy.ErrorLog); + } + + return null; + } + + private string RetrieveAssembly(string assemblyFileName) + { + if (this.IsControllerContainer(assemblyFileName) || this.IsMarkedAssembly(assemblyFileName)) + { + return assemblyFileName; + } + + return null; + } + /// /// Registers the controller string resources. /// @@ -328,7 +472,7 @@ private static void RegisterStringResources(Type controller) var resourceClass = localAttr.ResourceClass; var resourceClassId = Res.GetResourceClassId(resourceClass); - if (!ObjectFactory.Container.IsRegistered(resourceClass, resourceClassId)) + if (!ObjectFactory.IsTypeRegistered(resourceClass, null, resourceClassId, false)) { Res.RegisterResource(resourceClass); } @@ -501,6 +645,16 @@ private string AssemblyPackage(Assembly assembly) return attribute.Name; } + private void OnTaxonomiesUpdated(ICacheDependencyHandler caller, Type itemType, string itemKey) + { + // needs to run on a separate thread otherwise messes up the open access cache for som reason + // no time to investigate and I see this method has already been called in the same fashion in earlier checkins + Task.Run(() => + { + RegisterTaxonomyRoutes(); + }); + } + #endregion } } diff --git a/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/ControllerExtensions.cs b/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/ControllerExtensions.cs index dc0d55639..b36a8430f 100644 --- a/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/ControllerExtensions.cs +++ b/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/ControllerExtensions.cs @@ -5,17 +5,22 @@ using System.Reflection; using System.Web; using System.Web.Mvc; +using ServiceStack.Text; using Telerik.Sitefinity.Abstractions; using Telerik.Sitefinity.Abstractions.VirtualPath; using Telerik.Sitefinity.Data; using Telerik.Sitefinity.DynamicModules.Builder; using Telerik.Sitefinity.DynamicModules.Builder.Model; using Telerik.Sitefinity.Frontend.Mvc.Controllers; +using Telerik.Sitefinity.Frontend.Mvc.Models; using Telerik.Sitefinity.Frontend.Resources; using Telerik.Sitefinity.Frontend.Resources.Resolvers; +using Telerik.Sitefinity.Security.Sanitizers; using Telerik.Sitefinity.Services; using Telerik.Sitefinity.Web; +using Telerik.Sitefinity.Web.OutputCache; using Telerik.Sitefinity.Web.UI; +using Telerik.Sitefinity.Web.UI.ContentUI.Enums; namespace Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Controllers { @@ -26,6 +31,26 @@ public static class ControllerExtensions { #region Public methods + /// + /// Determines whether the controller should return the details view. + /// + /// The controller. + /// Display mode of the view. + /// A view model containing list of selected items. + /// A value indicating whether the controller should return the details view. + public static bool ShouldReturnDetails(this Controller controller, ContentViewDisplayMode contentViewDisplayMode, ContentListViewModel viewModel) + { + if (controller == null) + throw new ArgumentNullException("controller"); + + if (contentViewDisplayMode == ContentViewDisplayMode.Detail && viewModel != null) + { + return true; + } + + return false; + } + /// /// Updates the view engines collection of the given by making the engines aware of the controller's container virtual path. /// @@ -81,9 +106,21 @@ public static IEnumerable GetPartialViews(this Controller controller, IL /// The full view paths. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "fullViewPaths")] public static IEnumerable GetViews(this Controller controller, IList fullViewPaths = null) + { + return ControllerExtensions.GetViews(controller, fullViewPaths, null); + } + + /// + /// Gets the views that are available to the controller. + /// + /// The controller. + /// The full view paths. + /// The name of dynamic module (if any). Default is null. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "fullViewPaths")] + public static IEnumerable GetViews(this Controller controller, IList fullViewPaths, string moduleName) { var viewLocations = ControllerExtensions.GetViewLocations(controller); - return ControllerExtensions.GetViews(controller, viewLocations); + return ControllerExtensions.GetViews(controller, viewLocations, moduleName); } /// @@ -99,25 +136,33 @@ public static void AddCacheDependencies(this Controller controller, IEnumerable< if (keys == null) throw new ArgumentNullException("keys"); - if (SystemManager.CurrentHttpContext == null) + var httpContext = SystemManager.CurrentHttpContext; + if (httpContext == null) throw new InvalidOperationException("Current HttpContext is null. Cannot add cache dependencies."); - IList dependencies = null; - if (SystemManager.CurrentHttpContext.Items.Contains(PageCacheDependencyKeys.PageData)) - { - dependencies = SystemManager.CurrentHttpContext.Items[PageCacheDependencyKeys.PageData] as IList; - } + string widgetId = controller.ViewData[Telerik.Sitefinity.Mvc.ControllerActionInvoker.ControlDataIdKey] as string; - if (dependencies == null) - { - dependencies = new List(); - SystemManager.CurrentHttpContext.Items.Add(PageCacheDependencyKeys.PageData, dependencies); - } + httpContext.AddCacheDependencies(keys, widgetId); + } - foreach (var key in keys) - dependencies.Add(key); + /// + /// Adds cache dependencies for the current response. + /// + /// The controller. + /// The type of the content. + /// The name of the provider. + public static void AddCacheVariations(this Controller controller, Type contentType, string providerName = null) + { + if (controller == null) + throw new ArgumentNullException("controller"); + + if (contentType == null) + throw new ArgumentNullException("contentType"); + + PageRouteHandler.RegisterContentListCacheVariation(contentType, providerName); } + /// /// Gets the partial view paths of the given controller. /// @@ -153,7 +198,7 @@ public static string GetCurrentPageUrl(this IController controller) /// /// The controller. /// The dynamic module type. - public static DynamicModuleType GetDynamicContentType(this ControllerBase controller) + public static IDynamicModuleType GetDynamicContentType(this ControllerBase controller) { if (controller == null) throw new ArgumentNullException("controller"); @@ -173,7 +218,7 @@ public static DynamicModuleType GetDynamicContentType(this ControllerBase contro /// /// The dynamic module type. /// - public static DynamicModuleType GetDynamicContentType(this ControllerBase controller, string controllerName) + public static IDynamicModuleType GetDynamicContentType(this ControllerBase controller, string controllerName) { if (controller == null) throw new ArgumentNullException("controller"); @@ -181,7 +226,9 @@ public static DynamicModuleType GetDynamicContentType(this ControllerBase contro if (controllerName == null) throw new ArgumentNullException("controllerName"); - return ControllerExtensions.GetDynamicContentType(controllerName); + var moduleName = controller.ViewBag.ModuleName as string; + + return ControllerExtensions.GetDynamicContentType(controllerName, moduleName); } /// @@ -191,21 +238,22 @@ public static DynamicModuleType GetDynamicContentType(this ControllerBase contro /// /// The dynamic module type. /// - public static DynamicModuleType GetDynamicContentType(string controllerName) + public static IDynamicModuleType GetDynamicContentType(string controllerName) { - if (controllerName == null) - throw new ArgumentNullException("controllerName"); - - if (SystemManager.GetModule("ModuleBuilder") == null) - return null; - - var moduleProvider = ModuleBuilderManager.GetManager().Provider; - var dynamicContentType = moduleProvider.GetDynamicModules() - .Where(m => m.Status == DynamicModuleStatus.Active) - .Join(moduleProvider.GetDynamicModuleTypes().Where(t => t.TypeName == controllerName), m => m.Id, t => t.ParentModuleId, (m, t) => t) - .FirstOrDefault(); + return ControllerExtensions.GetDynamicContentType(controllerName, null); + } - return dynamicContentType; + /// + /// Gets the type of the dynamic content that is inferred for the given controller name. + /// + /// Name of the controller. + /// The name of the module. If empty or null will search all dynamic modules. Default value is null. + /// + /// The dynamic module type. + /// + public static IDynamicModuleType GetDynamicContentType(string controllerName, string moduleName) + { + return ControllerExtensions.FindDynamicContentTypes(controllerName, moduleName).FirstOrDefault(); } /// @@ -230,22 +278,105 @@ public static IndexRenderModes GetIndexRenderMode(this IController controller) return attribute == null ? IndexRenderModes.Normal : attribute.Mode; } + /// + /// Returns the invalid response for the details view. + /// + /// The + public static ActionResult HandleInvalidDetailsAction(this IController controller, string message) + { + if (SystemManager.IsDesignMode || SystemManager.IsPreviewMode) + { + var sanitizer = ObjectFactory.Resolve(); + message = sanitizer.Sanitize(message); + + var response = new ContentResult(); + response.Content = message; + + return response; + } + + return new EmptyResult(); + } + #endregion #region Internal methods + /// + /// Gets the type of the dynamic content that is inferred for the given controller name. + /// + /// Name of the controller. + /// The name of the module. If empty or null will search all dynamic modules. + /// + /// The dynamic module types. + /// + internal static IEnumerable FindDynamicContentTypes(string controllerName, string moduleName) + { + if (controllerName == null) + throw new ArgumentNullException("controllerName"); + + if (SystemManager.GetModule("ModuleBuilder") == null) + return Enumerable.Empty().AsQueryable(); + + var dynamicModuleTypes = ModuleBuilderManager.GetActiveTypes() + .Where(t => string.Equals(t.TypeName, controllerName, StringComparison.OrdinalIgnoreCase)); + + if (!moduleName.IsNullOrWhitespace()) + { + dynamicModuleTypes = dynamicModuleTypes.Where(t => t.ModuleName == moduleName); + } + + return dynamicModuleTypes; + } + /// /// Gets the partial views that are available to the controller. /// /// The controller. /// The view files mappings. - /// The full view paths. + /// The name of dynamic module name. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "fullViewPaths")] - internal static IEnumerable GetPartialViews(this Controller controller, ref Dictionary viewFilesMappings, IList fullViewPaths = null) + internal static IEnumerable GetPartialViews(this Controller controller, ref Dictionary viewFilesMappings, string moduleName) { var viewLocations = ControllerExtensions.GetPartialViewLocations(controller); - return ControllerExtensions.GetViews(controller, viewLocations, ref viewFilesMappings); + return ControllerExtensions.GetViews(controller, viewLocations, ref viewFilesMappings, moduleName); + } + + /// + /// Resolves the widget name of the dynamic widget controller. + /// + /// The controller. + /// + /// The widget name. + /// + internal static string ResolveDynamicControllerWidgetName(this Controller controller) + { + if (controller == null || + controller.Request == null || + controller.Request.QueryString == null || + controller.RouteData == null || + !controller.RouteData.Values.ContainsKey("widgetName") || + (string)controller.RouteData.Values["widgetName"] != "DynamicContent") + { + return null; + } + + var controlId = controller.Request.QueryStringGet("controlId") as string; + Guid controlIdGuid; + if (string.IsNullOrEmpty(controlId) || !Guid.TryParse(controlId, out controlIdGuid)) + return null; + + var pageManager = Telerik.Sitefinity.Modules.Pages.PageManager.GetManager(); + var controlObjectData = pageManager.GetControl(controlIdGuid); + if (controlObjectData == null || controlObjectData.Properties == null) + return null; + + var controllerWidgetProperty = controlObjectData.Properties.FirstOrDefault(x => x.Name == "WidgetName"); + if (controllerWidgetProperty == null) + return null; + + return controllerWidgetProperty.Value; } #endregion @@ -290,6 +421,10 @@ private static string GetKey(Controller controller) { var widgetName = (string)controller.RouteData.Values[WidgetNameKey]; key = string.Format("{0}-{1}", key, widgetName); + + var dynamicControllerWidgetName = controller.ResolveDynamicControllerWidgetName(); + if (!string.IsNullOrEmpty(dynamicControllerWidgetName)) + key = string.Format("{0}-{1}", key, dynamicControllerWidgetName); } var currentPackage = new PackageManager().GetCurrentPackage(); @@ -421,49 +556,65 @@ private static IEnumerable GetViewFileExtensions(Controller controller) .Distinct(); } - private static IEnumerable GetViews(Controller controller, IEnumerable viewLocations) + private static IEnumerable GetViews(Controller controller, IEnumerable viewLocations, string moduleName = null) { var viewExtensions = ControllerExtensions.GetViewFileExtensions(controller); var widgetName = controller.RouteData != null ? controller.RouteData.Values["widgetName"] as string : null; - var baseFiles = ControllerExtensions.GetViewsForAssembly(controller.GetType().Assembly, viewLocations, viewExtensions); + var assembly = controller.GetType().Assembly; + var baseFiles = ControllerExtensions.GetViewsForAssembly(assembly, viewLocations, viewExtensions, moduleName).ToList(); + + foreach (var viewEngine in controller.ViewEngineCollection) + { + var compositeViewEngine = viewEngine as CompositePrecompiledMvcEngineWrapper; + if (compositeViewEngine != null) + { + var files = compositeViewEngine.GetViews(viewLocations); + baseFiles.AddRange(files); + } + } + var filesForBasePath = baseFiles.Distinct(); + if (!widgetName.IsNullOrEmpty()) { var widgetAssembly = FrontendManager.ControllerFactory.ResolveControllerType(widgetName).Assembly; var widgetFiles = ControllerExtensions.GetViewsForAssembly(widgetAssembly, viewLocations, viewExtensions); - return baseFiles.Union(widgetFiles); + return filesForBasePath.Union(widgetFiles); } - return baseFiles; + return filesForBasePath; } - private static IEnumerable GetViews(Controller controller, IEnumerable viewLocations, ref Dictionary viewFilesMappings) + private static IEnumerable GetViews(Controller controller, IEnumerable viewLocations, ref Dictionary viewFilesMappings, string moduleName = null) { var viewExtensions = ControllerExtensions.GetViewFileExtensions(controller); var widgetName = controller.RouteData != null ? controller.RouteData.Values["widgetName"] as string : null; - var baseFiles = ControllerExtensions.GetViewsForAssembly(controller.GetType().Assembly, viewLocations, viewExtensions, ref viewFilesMappings); + var baseFiles = ControllerExtensions.GetViewsForAssembly(controller.GetType().Assembly, viewLocations, viewExtensions, + ref viewFilesMappings, moduleName); if (!widgetName.IsNullOrEmpty()) { var widgetAssembly = FrontendManager.ControllerFactory.ResolveControllerType(widgetName).Assembly; - var widgetFiles = ControllerExtensions.GetViewsForAssembly(widgetAssembly, viewLocations, viewExtensions, ref viewFilesMappings); + var widgetFiles = ControllerExtensions.GetViewsForAssembly(widgetAssembly, viewLocations, viewExtensions, + ref viewFilesMappings, moduleName); return baseFiles.Union(widgetFiles); } return baseFiles; } - private static IEnumerable GetViewsForAssembly(Assembly assembly, IEnumerable viewLocations, IEnumerable viewExtensions) + private static IEnumerable GetViewsForAssembly(Assembly assembly, IEnumerable viewLocations, IEnumerable viewExtensions, string moduleName = null) { - var pathDef = FrontendManager.VirtualPathBuilder.GetPathDefinition(assembly); + var pathDef = FrontendManager.VirtualPathBuilder.GetPathDefinition(assembly, moduleName); return viewLocations .SelectMany(l => ControllerExtensions.GetViewsForPath(pathDef, l, viewExtensions)) .Distinct(); } - private static IEnumerable GetViewsForAssembly(Assembly assembly, IEnumerable viewLocations, IEnumerable viewExtensions, ref Dictionary viewFilesMappings) + private static IEnumerable GetViewsForAssembly(Assembly assembly, IEnumerable viewLocations, IEnumerable viewExtensions, + ref Dictionary viewFilesMappings, string moduleName = null) { - var pathDef = FrontendManager.VirtualPathBuilder.GetPathDefinition(assembly); + var pathDef = FrontendManager.VirtualPathBuilder.GetPathDefinition(assembly, moduleName); var views = new List(); foreach (var viewLocation in viewLocations) diff --git a/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/FeatherEnabledToolboxFilter.cs b/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/FeatherEnabledToolboxFilter.cs index 9e836c37a..a0c4923ec 100644 --- a/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/FeatherEnabledToolboxFilter.cs +++ b/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/FeatherEnabledToolboxFilter.cs @@ -14,7 +14,7 @@ namespace Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Controllers internal class FeatherEnabledToolboxFilter : IToolboxFilter { /// - public virtual bool IsSectionVisible(IToolboxSection section) + public virtual bool IsSectionVisible(IToolboxSection section, IToolboxFilterContext context) { return true; } diff --git a/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/FrontendControllerFactory.cs b/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/FrontendControllerFactory.cs index dd6804770..11fd93e9a 100644 --- a/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/FrontendControllerFactory.cs +++ b/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/FrontendControllerFactory.cs @@ -17,20 +17,8 @@ namespace Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Controllers /// /// This class extends the by adding additional virtual paths for controller view engines. /// - public class FrontendControllerFactory : SitefinityControllerFactory, IDisposable + public class FrontendControllerFactory : SitefinityControllerFactory { - #region Constructors - - /// - /// Initializes a new instance of the class. - /// - public FrontendControllerFactory() - { - this.ninjectKernel = new StandardKernel(); - } - - #endregion - #region Public members /// @@ -59,15 +47,6 @@ public override IController CreateController(RequestContext requestContext, stri return baseController; } - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - /// /// Enhances the view engines. /// @@ -81,24 +60,17 @@ internal static void EnhanceViewEngines(Controller controller) } } - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - /// If false cleans up native resources, otherwise cleans up both managed and native resources. - protected virtual void Dispose(bool cleanManagedResources) - { - if (cleanManagedResources) - this.ninjectKernel.Dispose(); - } - #endregion #region Protected members /// - protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType) + protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { - return (IController)this.ninjectKernel.Get(controllerType); + object controllerObject = FrontendModule.Current.DependencyResolver.Get(controllerType); + IController controller = (IController)controllerObject; + + return controller; } #endregion @@ -135,29 +107,11 @@ private static IList> GetControllerPathTransformations(Cont private static void AddDynamicControllerPathTransformations(Controller controller, string virtualPath, string currentPackage, List> pathTransformations) { - if (controller != null && controller.Request != null && controller.Request.QueryString != null && controller.RouteData != null && controller.RouteData.Values.ContainsKey("widgetName") && (string)controller.RouteData.Values["widgetName"] == "DynamicContent") - { - var controlId = controller.Request.QueryString["controlId"] as string; - Guid controlIdGuid; - - if (!string.IsNullOrEmpty(controlId) && Guid.TryParse(controlId, out controlIdGuid)) - { - var controlObjectData = PageManager.GetManager().GetControl(controlIdGuid); + var dynamicControllerWidgetName = controller.ResolveDynamicControllerWidgetName(); + if (string.IsNullOrEmpty(dynamicControllerWidgetName)) + return; - if (controlObjectData != null && controlObjectData.Properties != null) - { - var controllerWidgetProperty = controlObjectData.Properties.FirstOrDefault(x => x.Name == "WidgetName"); - if (controllerWidgetProperty != null) - { - var dynamicControllerWidgetName = controllerWidgetProperty.Value; - if (!string.IsNullOrEmpty(dynamicControllerWidgetName)) - { - pathTransformations.Add(FrontendControllerFactory.GetPathTransformation(virtualPath, currentPackage, dynamicControllerWidgetName)); - } - } - } - } - } + pathTransformations.Add(FrontendControllerFactory.GetPathTransformation(virtualPath, currentPackage, dynamicControllerWidgetName)); } private static Func GetPathTransformation(string controllerVirtualPath, string currentPackage, string widgetName = null) @@ -226,10 +180,8 @@ private static bool IsInDefaultMvcNamespace(Type controller) #region Fields - private IKernel ninjectKernel; - private static readonly Dictionary EnhanceAttributes = new Dictionary(); #endregion } -} +} \ No newline at end of file diff --git a/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/ICanFilterByParent.cs b/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/ICanFilterByParent.cs new file mode 100644 index 000000000..be2317a25 --- /dev/null +++ b/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/ICanFilterByParent.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Controllers +{ + /// + /// Classes that implement this interface can filter items by parent item resolved from URL + /// + public interface ICanFilterByParent + { + /// + /// Gets the parent types. + /// + /// Collection of parent types to filter by. + IEnumerable GetParentTypes(); + } +} diff --git a/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/MvcWidgetProxy.cs b/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/MvcWidgetProxy.cs index fbc2831cc..95dcf3821 100644 --- a/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/MvcWidgetProxy.cs +++ b/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Controllers/MvcWidgetProxy.cs @@ -25,27 +25,51 @@ public string WidgetName set { this.widgetName = value; - if (this.Controller != null && this.Controller.ViewBag != null) - this.Controller.ViewBag.WidgetName = this.widgetName; + UpdateControllerViewBag(this.Controller); } } /// - /// Gets or sets the instance of that - /// is being proxied. + /// Gets or sets the name of the module. /// - public override Controller Controller + /// The name of the module. + [Browsable(true)] + public string ModuleName { get { - var controller = base.Controller; - if (controller != null && controller.ViewBag != null && !this.WidgetName.IsNullOrEmpty()) - controller.ViewBag.WidgetName = this.WidgetName; + return this.moduleName; + } - return controller; + set + { + this.moduleName = value; + UpdateControllerViewBag(this.Controller); } } + /// + protected override void OnControllerInitialized(Controller controller) + { + UpdateControllerViewBag(controller); + } + private string widgetName; + private string moduleName; + + private void UpdateControllerViewBag(Controller controller) + { + if (controller == null || controller.ViewBag == null) return; + + if (!this.WidgetName.IsNullOrEmpty()) + { + controller.ViewBag.WidgetName = this.WidgetName; + } + + if (!this.ModuleName.IsNullOrEmpty()) + { + controller.ViewBag.ModuleName = this.ModuleName; + } + } } } diff --git a/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/InfrastructureResources.cs b/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/InfrastructureResources.cs index 83bb7e6c2..f1cab8b98 100644 --- a/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/InfrastructureResources.cs +++ b/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/InfrastructureResources.cs @@ -175,5 +175,110 @@ public string LogPrecompiledViewUsageDescription return this["LogPrecompiledViewUsageDescription"]; } } + + /// + /// Caption of the 'Log usage of precompiled views' checkbox. + /// + [ResourceEntry("UseNamedParametersRoutingCaption", + Value = "Use named route parameters", + Description = "Caption of the 'Use named route parameters' checkbox.", + LastModified = "2019/6/20")] + public string UseNamedParametersRoutingCaption + { + get + { + return this["UseNamedParametersRoutingCaption"]; + } + } + + /// + /// Description of the 'Log usage of precompiled views' checkbox. + /// + [ResourceEntry("UseNamedParametersRoutingDescription", + Value = "If enabled, the frontend routes will work only with named params (e.g /tag/london/page/2); otherwise, the route will be /london/2.", + Description = "Description of the 'Use named route parameters' checkbox.", + LastModified = "2019/6/20")] + public string UseNamedParametersRoutingDescription + { + get + { + return this["UseNamedParametersRoutingDescription"]; + } + } + + /// + /// Detailed error message when cannot find template in MVC form. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1304:SpecifyCultureInfo", MessageId = "Telerik.Sitefinity.Localization.Resource.get_Item(System.String)"), ResourceEntry("CannotFindTemplateMvcForm", + Value = "Cannot find template \"{0}\". Most probably it is specific for a particular resource package which is not applied in the current context. Try duplicating the template in SitefinityWebApp root folder while keeping the same folder hierarchy.", + Description = "Detailed error message when cannot find template in MVC form.", + LastModified = "2016/11/28")] + public string CannotFindTemplateMvcForm + { + get + { + return base["CannotFindTemplateMvcForm"]; + } + } + + /// + /// Title of the "Use cached controller container assemblies" setting + /// + [ResourceEntry("UseCachedControllerContainerAssembliesTitle", + Value = "Use cached controller container assemblies", + Description = "Title of the setting", + LastModified = "2019/9/04")] + public string UseCachedControllerContainerAssembliesTitle + { + get + { + return this["UseCachedControllerContainerAssembliesTitle"]; + } + } + + /// + /// Description of the "Use cached controller container assemblies" setting + /// + [ResourceEntry("UseCachedControllerContainerAssembliesDescription", + Value = "Use a JSON with the location to the assemblies that contain controllers.", + Description = "Description of the setting", + LastModified = "2019/9/04")] + public string UseCachedControllerContainerAssembliesDescription + { + get + { + return this["UseCachedControllerContainerAssembliesDescription"]; + } + } + + /// + /// Caption of the NinjectLoadExtensions config property + /// + [ResourceEntry("NinjectLoadExtensionsTitle", + Value = "Automatically load Ninject extensions", + Description = "Caption of the NinjectLoadExtensions config property", + LastModified = "2019/09/18")] + public string NinjectLoadExtensionsTitle + { + get + { + return this["NinjectLoadExtensionsTitle"]; + } + } + + /// + /// Description of the NinjectLoadExtensions config property + /// + [ResourceEntry("NinjectLoadExtensionsDescription", + Value = "Indicates whether the Ninject kernel should automatically load extensions at startup", + Description = "Description of the NinjectLoadExtensions config property", + LastModified = "2019/09/18")] + public string NinjectLoadExtensionsDescription + { + get + { + return this["NinjectLoadExtensionsDescription"]; + } + } } } diff --git a/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Layouts/LayoutInitializer.cs b/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Layouts/LayoutInitializer.cs index c33f58c67..f9264fd4b 100644 --- a/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Layouts/LayoutInitializer.cs +++ b/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Layouts/LayoutInitializer.cs @@ -1,37 +1,45 @@ using System; +using System.Collections.Generic; +using System.Linq; using Telerik.Microsoft.Practices.Unity; using Telerik.Sitefinity.Abstractions; using Telerik.Sitefinity.Abstractions.VirtualPath; +using Telerik.Sitefinity.Data; +using Telerik.Sitefinity.Frontend.FilesMonitoring; using Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Routing; -using Telerik.Sitefinity.Frontend.Resources; using Telerik.Sitefinity.Modules.Pages; -using Telerik.Sitefinity.Modules.Pages.Web.Services; using Telerik.Sitefinity.Pages.Model; -using Telerik.Sitefinity.Services; -using Telerik.Sitefinity.Web; - -namespace Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Layouts -{ - /// - /// This class contains logic for registration and initialization of the layouts. - /// - internal class LayoutInitializer : IInitializer - { - /// - /// Registers the types and resolvers related to the layouts functionality. - /// - public virtual void Initialize() - { - ObjectFactory.Container.RegisterType(new ContainerControlledLifetimeManager()); - ObjectFactory.Container.RegisterType("PureMvcPageResolver", new ContainerControlledLifetimeManager(), new InjectionConstructor()); - - VirtualPathManager.AddVirtualFileResolver(string.Format(System.Globalization.CultureInfo.InvariantCulture, "~/{0}*", LayoutVirtualFileResolver.ResolverPath), typeof(LayoutVirtualFileResolver).FullName); - ObjectFactory.Container.RegisterType(); - ObjectFactory.Container.RegisterType(); +using Telerik.Sitefinity.Security; +using Telerik.Sitefinity.Web; + +namespace Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Layouts +{ + /// + /// This class contains logic for registration and initialization of the layouts. + /// + internal class LayoutInitializer : IInitializer + { + /// + /// Registers the types and resolvers related to the layouts functionality. + /// + public virtual void Initialize() + { + ObjectFactory.Container.RegisterType(new ContainerControlledLifetimeManager()); + ObjectFactory.Container.RegisterType("PureMvcPageResolver", new ContainerControlledLifetimeManager(), new InjectionConstructor()); + + VirtualPathManager.AddVirtualFileResolver(string.Format(System.Globalization.CultureInfo.InvariantCulture, "~/{0}*", LayoutVirtualFileResolver.ResolverPath), typeof(LayoutVirtualFileResolver).FullName); + ObjectFactory.Container.RegisterType(); + ObjectFactory.Container.RegisterType(); ObjectFactory.Container.RegisterType(); this.mvcVersioningRoute = new System.Web.Routing.Route("Sitefinity/Versioning/{itemId}/{VersionNumber}", ObjectFactory.Resolve()); - System.Web.Routing.RouteTable.Routes.Insert(1, this.mvcVersioningRoute); + System.Web.Routing.RouteTable.Routes.Insert(1, this.mvcVersioningRoute); + + PageManager.Executing -= LayoutInitializer.Provider_Executing; + PageManager.Executing += LayoutInitializer.Provider_Executing; + + PageManager.Executed -= LayoutInitializer.Provider_Executed; + PageManager.Executed += LayoutInitializer.Provider_Executed; } /// @@ -40,8 +48,70 @@ public virtual void Initialize() public virtual void Uninitialize() { System.Web.Routing.RouteTable.Routes.Remove(this.mvcVersioningRoute); + + PageManager.Executed -= LayoutInitializer.Provider_Executed; + PageManager.Executing -= LayoutInitializer.Provider_Executing; } - private System.Web.Routing.Route mvcVersioningRoute; - } -} + private System.Web.Routing.Route mvcVersioningRoute; + private const string CreatedPageTemplatesCategoryIds = "created-page-templates"; + + private static void Provider_Executing(object sender, ExecutingEventArgs args) + { + if (!(args.CommandName == "CommitTransaction" || args.CommandName == "FlushTransaction")) + return; + + var provider = sender as PageDataProvider; + + var dirtyItems = provider.GetDirtyItems(); + if (dirtyItems.Count == 0) + return; + + var createdPageTemplates = provider.GetExecutionStateData(CreatedPageTemplatesCategoryIds) as HashSet; + if (createdPageTemplates == null) + createdPageTemplates = new HashSet(); + + foreach (var item in dirtyItems) + { + SecurityConstants.TransactionActionType itemStatus = provider.GetDirtyItemStatus(item); + var pageTemplate = item as PageTemplate; + if (pageTemplate != null) + { + if (itemStatus == SecurityConstants.TransactionActionType.New && pageTemplate.Framework == PageTemplateFramework.Mvc) + { + // the template name is formulated as Bootstrap.default + var nameParts = pageTemplate.Name.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries); + if (nameParts.Length > 1) + { + var package = nameParts[0]; + + // always create the template, desipite if the package is not there + // in case the client first performs a sync and then deploys the files by mistake + createdPageTemplates.Add(package); + } + } + } + } + + if (createdPageTemplates.Any()) + { + provider.SetExecutionStateData(CreatedPageTemplatesCategoryIds, createdPageTemplates); + } + } + + private static void Provider_Executed(object sender, ExecutedEventArgs args) + { + if (args.CommandName != "CommitTransaction") + return; + + var provider = sender as PageDataProvider; + var packageNames = provider.GetExecutionStateData(CreatedPageTemplatesCategoryIds) as HashSet; + if (packageNames != null) + { + provider.SetExecutionStateData(CreatedPageTemplatesCategoryIds, null); + var layoutFileManager = ObjectFactory.Resolve(Enum.GetName(typeof(ResourceType), ResourceType.Layouts)) as LayoutFileManager; + layoutFileManager.CreateTemplateCategories(packageNames, true); + } + } + } +} diff --git a/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Layouts/LayoutRenderer.cs b/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Layouts/LayoutRenderer.cs index 9394a94e8..c8e6d7801 100644 --- a/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Layouts/LayoutRenderer.cs +++ b/Telerik.Sitefinity.Frontend/Mvc/Infrastructure/Layouts/LayoutRenderer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text.RegularExpressions; using System.Web; using System.Web.Hosting; @@ -24,10 +25,9 @@ internal class LayoutRenderer /// /// Creates a controller instance and sets its ControllerContext depending on the current Http context. /// - /// /// The route data. - /// The context. /// + /// Can not create ControllerContext if no active HttpContext instance is available. /// Can't create Controller Context if no active HttpContext instance is available. public virtual Controller CreateController(RouteData routeData = null) { @@ -74,7 +74,7 @@ public virtual string RenderViewToString(ControllerContext context, string viewP if (viewPath == null) throw new ArgumentNullException("viewPath"); - + string result = null; IView view = null; @@ -253,6 +253,8 @@ private void RenderContentPlaceHolders(string layoutPath, StringWriter writer) layoutText = layoutFile.ReadToEnd(); } + layoutText = this.RenderPartialViews(layoutPath, layoutText, new HashSet()); + var matches = new Regex(@"@Html\.SfPlaceHolder(?:\(\s*\""(?\w*)\""\s*\)|(?\(\)))").Matches(layoutText); foreach (Match match in matches) { @@ -270,6 +272,46 @@ private void RenderContentPlaceHolders(string layoutPath, StringWriter writer) writer.Write("
- -