diff --git a/.gitignore b/.gitignore index 284703ec00..0073675d82 100644 --- a/.gitignore +++ b/.gitignore @@ -128,3 +128,4 @@ src/*.boltdata/ /src/Umbraco.Web.UI/Umbraco/Js/canvasdesigner.config.js /src/Umbraco.Web.UI/Umbraco/Js/canvasdesigner.front.js src/umbraco.sln.ide/* +build/UmbracoCms.*/ diff --git a/build/Build.bat b/build/Build.bat index 0f0a6f1711..7410396ead 100644 --- a/build/Build.bat +++ b/build/Build.bat @@ -2,9 +2,18 @@ IF NOT EXIST UmbracoVersion.txt ( ECHO UmbracoVersion.txt missing! GOTO :showerror -) -SET /p release= .\_BuildOutput\WebApp\App_Code\dummy.txt -echo This file is only here so that the containing folder will be included in the NuGet package, it is safe to delete. > .\_BuildOutput\WebApp\App_Data\dummy.txt -echo This file is only here so that the containing folder will be included in the NuGet package, it is safe to delete. > .\_BuildOutput\WebApp\App_Plugins\dummy.txt -echo This file is only here so that the containing folder will be included in the NuGet package, it is safe to delete. > .\_BuildOutput\WebApp\css\dummy.txt -echo This file is only here so that the containing folder will be included in the NuGet package, it is safe to delete. > .\_BuildOutput\WebApp\masterpages\dummy.txt -echo This file is only here so that the containing folder will be included in the NuGet package, it is safe to delete. > .\_BuildOutput\WebApp\media\dummy.txt -echo This file is only here so that the containing folder will be included in the NuGet package, it is safe to delete. > .\_BuildOutput\WebApp\scripts\dummy.txt -echo This file is only here so that the containing folder will be included in the NuGet package, it is safe to delete. > .\_BuildOutput\WebApp\usercontrols\dummy.txt -echo This file is only here so that the containing folder will be included in the NuGet package, it is safe to delete. > .\_BuildOutput\WebApp\Views\Partials\dummy.txt -echo This file is only here so that the containing folder will be included in the NuGet package, it is safe to delete. > .\_BuildOutput\WebApp\Views\MacroPartials\dummy.txt +SET dummytext=This file is only here so that the containing folder will be included in the NuGet package, it is safe to delete. +ECHO %dummytext% > .\_BuildOutput\WebApp\App_Code\dummy.txt +ECHO %dummytext% > .\_BuildOutput\WebApp\App_Data\dummy.txt +ECHO %dummytext% > .\_BuildOutput\WebApp\App_Plugins\dummy.txt +ECHO %dummytext% > .\_BuildOutput\WebApp\css\dummy.txt +ECHO %dummytext% > .\_BuildOutput\WebApp\masterpages\dummy.txt +ECHO %dummytext% > .\_BuildOutput\WebApp\media\dummy.txt +ECHO %dummytext% > .\_BuildOutput\WebApp\scripts\dummy.txt +ECHO %dummytext% > .\_BuildOutput\WebApp\usercontrols\dummy.txt +ECHO %dummytext% > .\_BuildOutput\WebApp\Views\Partials\dummy.txt +ECHO %dummytext% > .\_BuildOutput\WebApp\Views\MacroPartials\dummy.txt ECHO Adding Web.config transform files to the NuGet package -ren .\_BuildOutput\WebApp\Views\Web.config Web.config.transform -ren .\_BuildOutput\WebApp\Xslt\Web.config Web.config.transform +REN .\_BuildOutput\WebApp\MacroScripts\Web.config Web.config.transform +REN .\_BuildOutput\WebApp\Views\Web.config Web.config.transform +REN .\_BuildOutput\WebApp\Xslt\Web.config Web.config.transform ECHO Packing the NuGet release files ..\src\.nuget\NuGet.exe Pack NuSpecs\UmbracoCms.Core.nuspec -Version %version% diff --git a/build/Build.proj b/build/Build.proj index 30d794fb3f..20c36e18aa 100644 --- a/build/Build.proj +++ b/build/Build.proj @@ -156,8 +156,8 @@ - - + + diff --git a/build/NuSpecs/UmbracoCms.Core.AllBinaries.nuspec b/build/NuSpecs/UmbracoCms.Core.AllBinaries.nuspec index fa3881dc92..517f0d441e 100644 --- a/build/NuSpecs/UmbracoCms.Core.AllBinaries.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.AllBinaries.nuspec @@ -1,74 +1,74 @@ - - UmbracoCms.Core.AllBinaries - 6.1.2 - Umbraco Cms Core All Binaries - Morten Christensen - Umbraco HQ - http://umbraco.codeplex.com/license - http://umbraco.com/ - http://umbraco.com/media/357769/100px_transparent.png - false - Contains the core assemblies and all of the dependant assemblies needed to run Umbraco Cms. This package only contains assemblies and can be used for package development. Use the UmbracoCms-package to setup Umbraco in Visual Studio as an ASP.NET project. - Contains the core assemblies and all of the dependant assemblies needed to run Umbraco Cms - en-US - umbraco - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + UmbracoCms.Core.AllBinaries + 6.1.2 + Umbraco Cms Core All Binaries + Morten Christensen + Umbraco HQ + http://umbraco.codeplex.com/license + http://umbraco.com/ + http://umbraco.com/media/357769/100px_transparent.png + false + Contains the core assemblies and all of the dependant assemblies needed to run Umbraco Cms. This package only contains assemblies and can be used for package development. Use the UmbracoCms-package to setup Umbraco in Visual Studio as an ASP.NET project. + Contains the core assemblies and all of the dependant assemblies needed to run Umbraco Cms + en-US + umbraco + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/NuSpecs/UmbracoCms.Core.Symbols.nuspec b/build/NuSpecs/UmbracoCms.Core.Symbols.nuspec index 7acbfed18c..29f7365017 100644 --- a/build/NuSpecs/UmbracoCms.Core.Symbols.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.Symbols.nuspec @@ -1,71 +1,71 @@ - - UmbracoCms.Core - 7.0.0 - Umbraco Cms Core Binaries - Umbraco HQ - Umbraco HQ - http://umbraco.codeplex.com/license - http://umbraco.com/ - http://umbraco.com/media/357769/100px_transparent.png - false - Contains the core assemblies needed to run Umbraco Cms. This package only contains assemblies and can be used for package development. Use the UmbracoCms-package to setup Umbraco in Visual Studio as an ASP.NET project. - Contains the core assemblies needed to run Umbraco Cms - en-US - umbraco - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + UmbracoCms.Core + 7.0.0 + Umbraco Cms Core Binaries + Umbraco HQ + Umbraco HQ + http://umbraco.codeplex.com/license + http://umbraco.com/ + http://umbraco.com/media/357769/100px_transparent.png + false + Contains the core assemblies needed to run Umbraco Cms. This package only contains assemblies and can be used for package development. Use the UmbracoCms-package to setup Umbraco in Visual Studio as an ASP.NET project. + Contains the core assemblies needed to run Umbraco Cms + en-US + umbraco + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index 46819783f0..c680dce419 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -1,74 +1,73 @@ - - UmbracoCms.Core - 7.0.0 - Umbraco Cms Core Binaries - Umbraco HQ - Umbraco HQ - http://umbraco.codeplex.com/license - http://umbraco.com/ - http://umbraco.com/media/357769/100px_transparent.png - false - Contains the core assemblies needed to run Umbraco Cms. This package only contains assemblies and can be used for package development. Use the UmbracoCms-package to setup Umbraco in Visual Studio as an ASP.NET project. - Contains the core assemblies needed to run Umbraco Cms - en-US - umbraco - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + UmbracoCms.Core + 7.0.0 + Umbraco Cms Core Binaries + Umbraco HQ + Umbraco HQ + http://umbraco.codeplex.com/license + http://umbraco.com/ + http://umbraco.com/media/357769/100px_transparent.png + false + Contains the core assemblies needed to run Umbraco Cms. This package only contains assemblies and can be used for package development. Use the UmbracoCms-package to setup Umbraco in Visual Studio as an ASP.NET project. + Contains the core assemblies needed to run Umbraco Cms + en-US + umbraco + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index b4b264e463..a184b79d5d 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -1,43 +1,43 @@ - - UmbracoCms - 7.0.0 - Umbraco Cms - Umbraco HQ - Umbraco HQ - http://umbraco.codeplex.com/license - http://umbraco.com/ - http://umbraco.com/media/357769/100px_transparent.png - false - Installs Umbraco Cms in your Visual Studio ASP.NET project - Installs Umbraco Cms in your Visual Studio ASP.NET project - en-US - umbraco - - - - - + + UmbracoCms + 7.0.0 + Umbraco Cms + Umbraco HQ + Umbraco HQ + http://umbraco.codeplex.com/license + http://umbraco.com/ + http://umbraco.com/media/357769/100px_transparent.png + false + Installs Umbraco Cms in your Visual Studio ASP.NET project + Installs Umbraco Cms in your Visual Studio ASP.NET project + en-US + umbraco + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/build/NuSpecs/UmbracoExamine.PDF.nuspec b/build/NuSpecs/UmbracoExamine.PDF.nuspec index 3b6869cde2..ca4acdcc36 100644 --- a/build/NuSpecs/UmbracoExamine.PDF.nuspec +++ b/build/NuSpecs/UmbracoExamine.PDF.nuspec @@ -1,23 +1,23 @@  - - UmbracoExamine.PDF - 0.7.0 - Umbraco HQ - Umbraco HQ - http://umbraco.codeplex.com/license - http://umbraco.com/ - http://umbraco.com/media/357769/100px_transparent.png - false - UmbracoExmine.PDF - umbraco - - - - - - - - - + + UmbracoExamine.PDF + 0.7.0 + Umbraco HQ + Umbraco HQ + http://umbraco.codeplex.com/license + http://umbraco.com/ + http://umbraco.com/media/357769/100px_transparent.png + false + UmbracoExmine.PDF + umbraco + + + + + + + + + \ No newline at end of file diff --git a/build/NuSpecs/tools/Dashboard.config.install.xdt b/build/NuSpecs/tools/Dashboard.config.install.xdt index a1960e5797..1f5bbb0164 100644 --- a/build/NuSpecs/tools/Dashboard.config.install.xdt +++ b/build/NuSpecs/tools/Dashboard.config.install.xdt @@ -26,11 +26,15 @@ views/dashboard/developer/developerdashboardvideos.html - + + + +
+ views/dashboard/developer/examinemanagement.html - +
diff --git a/build/NuSpecs/tools/Readme.txt b/build/NuSpecs/tools/Readme.txt index 9964be2311..d5121fe521 100644 --- a/build/NuSpecs/tools/Readme.txt +++ b/build/NuSpecs/tools/Readme.txt @@ -1,25 +1,32 @@ + _ _ __ __ ____ _____ _____ ____ + | | | | \/ | _ \| __ \ /\ / ____/ __ \ + | | | | \ / | |_) | |__) | / \ | | | | | | + | | | | |\/| | _ <| _ / / /\ \| | | | | | + | |__| | | | | |_) | | \ \ / ____ | |___| |__| | + \____/|_| |_|____/|_| \_/_/ \_\_____\____/ + +---------------------------------------------------- + A note about running Umbraco from Visual Studio. Don't forget to build! -BETA Notice: We've done our best to transform your config files, we are testing this in 7.2 beta and would love to -hear back if it didn't work so we can improve this feature. We'd love to see your before and after config files -(make sure to delete sensitive data like passwords and API keys) in the issue tracker at http://issues.umbraco.org/ -Remember, we backed up your files in App_Data\NuGetBackup so you can find the original file before it was transformed. - When upgrading your website using NuGet you should answer "No" to the questions to overwrite the Web.config file (and config files in the config folder). -We've overwritten all the files in the Umbraco and Umbraco_Client folder, these have been backed up in -App_Data\NuGetBackup. We didn't overwrite the UI.xml file nor did we remove any files or folders that you or a package -might have added. Only the existing files were overwritten. If you customized anything then make sure to do a compare -and merge with the NuGetBackup folder. +We've done our best to transform your configuration files but in case something is not quite right: remember we +backed up your files in App_Data\NuGetBackup so you can find the original files before they were transformed. -This nuget package includes build targets that extends the creation of a deploy package, which is generated by +We've overwritten all the files in the Umbraco and Umbraco_Client folder, these have been backed up in +App_Data\NuGetBackup. We didn't overwrite the UI.xml file nor did we remove any files or folders that you or +a package might have added. Only the existing files were overwritten. If you customized anything then make +sure to do a compare and merge with the NuGetBackup folder. + +This NuGet package includes build targets that extend the creation of a deploy package, which is generated by Publishing from Visual Studio. The targets will only work once Publishing is configured, so if you don't use Publish this won't affect you. -These things will now be automatically included when creating a deploy package or publishing to the file system: -umbraco, umbraco_client, config\splashes and global.asax. +The following items will now be automatically included when creating a deploy package or publishing to the file +system: umbraco, umbraco_client, config\splashes and global.asax. Please read the release notes on our.umbraco.org: http://our.umbraco.org/contribute/releases diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt index 4dd1cb4391..6215d405fd 100644 --- a/build/NuSpecs/tools/Web.config.install.xdt +++ b/build/NuSpecs/tools/Web.config.install.xdt @@ -6,7 +6,9 @@
- + +
+
@@ -154,10 +156,25 @@ - + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/NuSpecs/tools/install.ps1 b/build/NuSpecs/tools/install.ps1 index 9c7fa57f82..5b9aee0dfb 100644 --- a/build/NuSpecs/tools/install.ps1 +++ b/build/NuSpecs/tools/install.ps1 @@ -118,7 +118,7 @@ if ($project) { $lines = $lastOperation -split "`r`n" - $installMatch = $lines | ? { $_.StartsWith("------- installing...umbracocms ") } | select -first 1 + $installMatch = $lines | ? { $_.Contains("...umbracocms ") } | select -first 1 if ($installMatch) { diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt index fa5fce04b3..bba582b4dc 100644 --- a/build/UmbracoVersion.txt +++ b/build/UmbracoVersion.txt @@ -1 +1,2 @@ +# Usage: on line 2 put the release version, on line 3 put the version comment (example: beta) 8.0.0 \ No newline at end of file diff --git a/src/SQLCE4Umbraco/SqlCEHelper.cs b/src/SQLCE4Umbraco/SqlCEHelper.cs index 1787c05912..26a781360e 100644 --- a/src/SQLCE4Umbraco/SqlCEHelper.cs +++ b/src/SQLCE4Umbraco/SqlCEHelper.cs @@ -229,5 +229,17 @@ namespace SqlCE4Umbraco return new SqlCeDataReaderHelper(SqlCeApplicationBlock.ExecuteReader(ConnectionString, CommandType.Text, commandText, parameters)); } + + + internal IRecordsReader ExecuteReader(string commandText) + { + return ExecuteReader(commandText, new SqlCEParameter(string.Empty, string.Empty)); + } + + + internal int ExecuteNonQuery(string commandText) + { + return ExecuteNonQuery(commandText, new SqlCEParameter(string.Empty, string.Empty)); + } } } \ No newline at end of file diff --git a/src/SQLCE4Umbraco/app.config b/src/SQLCE4Umbraco/app.config index 53f3b4c80b..8f828418f3 100644 --- a/src/SQLCE4Umbraco/app.config +++ b/src/SQLCE4Umbraco/app.config @@ -8,7 +8,7 @@ - + diff --git a/src/Umbraco.Core/IO/FileSystemExtensions.cs b/src/Umbraco.Core/IO/FileSystemExtensions.cs index f513deb9f5..f2152afaab 100644 --- a/src/Umbraco.Core/IO/FileSystemExtensions.cs +++ b/src/Umbraco.Core/IO/FileSystemExtensions.cs @@ -2,17 +2,18 @@ using System.IO; namespace Umbraco.Core.IO -{ +{ public static class FileSystemExtensions { - public static long GetSize(this IFileSystem fs, string path) + public static long GetSize(this IFileSystem fs, string path) { - using (var s = fs.OpenFile(path)) + using (var file = fs.OpenFile(path)) { - var size = s.Length; - s.Close(); - - return size; + using (var sr = new StreamReader(file)) + { + var str = sr.ReadToEnd(); + return str.Length; + } } } @@ -25,14 +26,14 @@ namespace Umbraco.Core.IO } public static string GetExtension(this IFileSystem fs, string path) - { - return Path.GetExtension(fs.GetFullPath(path)); - } + { + return Path.GetExtension(fs.GetFullPath(path)); + } public static string GetFileName(this IFileSystem fs, string path) - { - return Path.GetFileName(fs.GetFullPath(path)); - } + { + return Path.GetFileName(fs.GetFullPath(path)); + } //TODO: Currently this is the only way to do this internal static void CreateFolder(this IFileSystem fs, string folderPath) diff --git a/src/Umbraco.Core/Mandate.cs b/src/Umbraco.Core/Mandate.cs index 54d589b90a..7ae47fd5c0 100644 --- a/src/Umbraco.Core/Mandate.cs +++ b/src/Umbraco.Core/Mandate.cs @@ -8,7 +8,7 @@ namespace Umbraco.Core /// /// Helper class for mandating values, for example on method parameters. /// - internal static class Mandate + public static class Mandate { /// /// Mandates that the specified parameter is not null. diff --git a/src/Umbraco.Core/Manifest/ManifestParser.cs b/src/Umbraco.Core/Manifest/ManifestParser.cs index 49839828fd..fbc91de898 100644 --- a/src/Umbraco.Core/Manifest/ManifestParser.cs +++ b/src/Umbraco.Core/Manifest/ManifestParser.cs @@ -60,7 +60,7 @@ namespace Umbraco.Core.Manifest public IEnumerable GetManifests() { //get all Manifest.js files in the appropriate folders - var manifestFileContents = GetAllManfifestFileContents(_pluginsDir); + var manifestFileContents = GetAllManifestFileContents(_pluginsDir); return CreateManifests(manifestFileContents.ToArray()); } @@ -69,7 +69,7 @@ namespace Umbraco.Core.Manifest /// /// /// - private IEnumerable GetAllManfifestFileContents(DirectoryInfo currDir) + private IEnumerable GetAllManifestFileContents(DirectoryInfo currDir) { var depth = FolderDepth(_pluginsDir, currDir); @@ -79,7 +79,7 @@ namespace Umbraco.Core.Manifest var result = new List(); foreach (var d in dirs) { - result.AddRange(GetAllManfifestFileContents(d)); + result.AddRange(GetAllManifestFileContents(d)); } return result; } diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs index ec763d07fa..fc53a21c3f 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -15,11 +15,11 @@ namespace Umbraco.Core.Models { private int _defaultTemplate; private IEnumerable _allowedTemplates; - + /// /// Constuctor for creating a ContentType with the parent's id. /// - /// You usually only want to use this for creating ContentTypes at the root. + /// Only use this for creating ContentTypes at the root (with ParentId -1). /// public ContentType(int parentId) : base(parentId) { @@ -31,14 +31,26 @@ namespace Umbraco.Core.Models /// /// Use this to ensure inheritance from parent. /// - public ContentType(IContentType parent) : base(parent) - { - _allowedTemplates = new List(); - } + [Obsolete("This method is obsolete, use ContentType(IContentType parent, string alias) instead.", false)] + public ContentType(IContentType parent) : this(parent, null) + { + } + + /// + /// Constuctor for creating a ContentType with the parent as an inherited type. + /// + /// Use this to ensure inheritance from parent. + /// + /// + public ContentType(IContentType parent, string alias) + : base(parent, alias) + { + _allowedTemplates = new List(); + } private static readonly PropertyInfo DefaultTemplateSelector = ExpressionHelper.GetPropertyInfo(x => x.DefaultTemplateId); private static readonly PropertyInfo AllowedTemplatesSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedTemplates); - + /// /// Gets or sets the alias of the default Template. /// @@ -95,7 +107,7 @@ namespace Umbraco.Core.Models } DefaultTemplateId = template.Id; - if(_allowedTemplates.Any(x => x != null && x.Id == template.Id) == false) + if (_allowedTemplates.Any(x => x != null && x.Id == template.Id) == false) { var templates = AllowedTemplates.ToList(); templates.Add(template); @@ -129,7 +141,7 @@ namespace Umbraco.Core.Models { base.AddingEntity(); - if(Key == Guid.Empty) + if (Key == Guid.Empty) Key = Guid.NewGuid(); } @@ -140,7 +152,7 @@ namespace Umbraco.Core.Models /// [Obsolete("Use DeepCloneWithResetIdentities instead")] public IContentType Clone(string alias) - { + { return DeepCloneWithResetIdentities(alias); } diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 50212f2722..30ac526f4a 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; +using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.Serialization; @@ -15,6 +16,7 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] + [DebuggerDisplay("Id: {Id}, Name: {Name}, Alias: {Alias}")] public abstract class ContentTypeBase : Entity, IContentTypeBase { private Lazy _parentId; @@ -48,17 +50,22 @@ namespace Umbraco.Core.Models _additionalData = new Dictionary(); } - protected ContentTypeBase(IContentTypeBase parent) + protected ContentTypeBase(IContentTypeBase parent) : this(parent, null) { - Mandate.ParameterNotNull(parent, "parent"); + } - _parentId = new Lazy(() => parent.Id); - _allowedContentTypes = new List(); - _propertyGroups = new PropertyGroupCollection(); + protected ContentTypeBase(IContentTypeBase parent, string alias) + { + Mandate.ParameterNotNull(parent, "parent"); + + _alias = alias; + _parentId = new Lazy(() => parent.Id); + _allowedContentTypes = new List(); + _propertyGroups = new PropertyGroupCollection(); _propertyTypes = new PropertyTypeCollection(); _propertyTypes.CollectionChanged += PropertyTypesChanged; _additionalData = new Dictionary(); - } + } private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); @@ -492,7 +499,6 @@ namespace Umbraco.Core.Models /// Alias of the to remove public void RemovePropertyType(string propertyTypeAlias) { - //check if the property exist in one of our collections if (PropertyGroups.Any(group => group.PropertyTypes.Any(pt => pt.Alias == propertyTypeAlias)) || _propertyTypes.Any(x => x.Alias == propertyTypeAlias)) @@ -519,6 +525,7 @@ namespace Umbraco.Core.Models public void RemovePropertyGroup(string propertyGroupName) { PropertyGroups.RemoveItem(propertyGroupName); + OnPropertyChanged(PropertyGroupCollectionSelector); } /// diff --git a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs index 3bebb59959..ac358481f1 100644 --- a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs @@ -22,11 +22,16 @@ namespace Umbraco.Core.Models } protected ContentTypeCompositionBase(IContentTypeComposition parent) - : base(parent) + : this(parent, null) { - AddContentType(parent); } + protected ContentTypeCompositionBase(IContentTypeComposition parent, string alias) + : base(parent, alias) + { + AddContentType(parent); + } + private static readonly PropertyInfo ContentTypeCompositionSelector = ExpressionHelper.GetPropertyInfo>( x => x.ContentTypeComposition); @@ -76,6 +81,9 @@ namespace Umbraco.Core.Models if (contentType.ContentTypeComposition.Any(x => x.CompositionAliases().Any(ContentTypeCompositionExists))) return false; + if (string.IsNullOrEmpty(Alias) == false && Alias.Equals(contentType.Alias)) + return false; + if (ContentTypeCompositionExists(contentType.Alias) == false) { //Before we actually go ahead and add the ContentType as a Composition we ensure that we don't diff --git a/src/Umbraco.Core/Models/File.cs b/src/Umbraco.Core/Models/File.cs index d337b82bbe..3513e4e031 100644 --- a/src/Umbraco.Core/Models/File.cs +++ b/src/Umbraco.Core/Models/File.cs @@ -62,7 +62,7 @@ namespace Umbraco.Core.Models } /// - /// Gets or sets the Path to the File from the root of the site + /// Gets or sets the Path to the File from the root of the file's associated IFileSystem /// [DataMember] public virtual string Path @@ -99,6 +99,11 @@ namespace Umbraco.Core.Models } } + /// + /// Gets or sets the file's virtual path (i.e. the file path relative to the root of the website) + /// + public string VirtualPath { get; set; } + /// /// Boolean indicating whether the file could be validated /// diff --git a/src/Umbraco.Core/Models/IFile.cs b/src/Umbraco.Core/Models/IFile.cs index b0cc96b56d..b4d0b75a79 100644 --- a/src/Umbraco.Core/Models/IFile.cs +++ b/src/Umbraco.Core/Models/IFile.cs @@ -19,7 +19,7 @@ namespace Umbraco.Core.Models string Alias { get; } /// - /// Gets or sets the Path to the File from the root of the site + /// Gets or sets the Path to the File from the root of the file's associated IFileSystem /// string Path { get; set; } @@ -28,6 +28,11 @@ namespace Umbraco.Core.Models /// string Content { get; set; } + /// + /// Gets or sets the file's virtual path (i.e. the file path relative to the root of the website) + /// + string VirtualPath { get; set; } + /// /// Boolean indicating whether the file could be validated /// diff --git a/src/Umbraco.Core/Models/IPartialView.cs b/src/Umbraco.Core/Models/IPartialView.cs new file mode 100644 index 0000000000..01127ce22a --- /dev/null +++ b/src/Umbraco.Core/Models/IPartialView.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Core.Models +{ + public interface IPartialView : IFile + { + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Macro.cs b/src/Umbraco.Core/Models/Macro.cs index eb8e0b7a66..0c86f5d1fe 100644 --- a/src/Umbraco.Core/Models/Macro.cs +++ b/src/Umbraco.Core/Models/Macro.cs @@ -9,6 +9,7 @@ using System.Runtime.Serialization; using System.Text.RegularExpressions; using Umbraco.Core.IO; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Strings; namespace Umbraco.Core.Models { @@ -48,7 +49,7 @@ namespace Umbraco.Core.Models Id = id; UseInEditor = useInEditor; CacheDuration = cacheDuration; - Alias = alias; + Alias = alias.ToCleanString(CleanStringType.Alias); Name = name; ControlType = controlType; ControlAssembly = controlAssembly; @@ -87,7 +88,7 @@ namespace Umbraco.Core.Models { UseInEditor = useInEditor; CacheDuration = cacheDuration; - Alias = alias; + Alias = alias.ToCleanString(CleanStringType.Alias); Name = name; ControlType = controlType; ControlAssembly = controlAssembly; @@ -207,7 +208,7 @@ namespace Umbraco.Core.Models { SetPropertyValueAndDetectChanges(o => { - _alias = value; + _alias = value.ToCleanString(CleanStringType.Alias); return _alias; }, _alias, AliasSelector); } diff --git a/src/Umbraco.Core/Models/MediaType.cs b/src/Umbraco.Core/Models/MediaType.cs index d8e3c58538..052e231136 100644 --- a/src/Umbraco.Core/Models/MediaType.cs +++ b/src/Umbraco.Core/Models/MediaType.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core.Models /// /// Constuctor for creating a MediaType with the parent's id. /// - /// You usually only want to use this for creating MediaTypes at the root. + /// Only use this for creating MediaTypes at the root (with ParentId -1). /// public MediaType(int parentId) : base(parentId) { @@ -24,10 +24,21 @@ namespace Umbraco.Core.Models /// /// Use this to ensure inheritance from parent. /// - public MediaType(IMediaType parent) : base(parent) + public MediaType(IMediaType parent) : this(parent, null) { } + /// + /// Constuctor for creating a MediaType with the parent as an inherited type. + /// + /// Use this to ensure inheritance from parent. + /// + /// + public MediaType(IMediaType parent, string alias) + : base(parent, alias) + { + } + /// /// Method to call when Entity is being saved /// diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index 1706ba5ab1..c4c0362acc 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -20,7 +20,6 @@ namespace Umbraco.Core.Models private string _email; private string _rawPasswordValue; private object _providerUserKey; - private Type _userTypeKey; /// /// Constructor for creating an empty Member object @@ -114,7 +113,6 @@ namespace Umbraco.Core.Models private static readonly PropertyInfo EmailSelector = ExpressionHelper.GetPropertyInfo(x => x.Email); private static readonly PropertyInfo PasswordSelector = ExpressionHelper.GetPropertyInfo(x => x.RawPasswordValue); private static readonly PropertyInfo ProviderUserKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ProviderUserKey); - private static readonly PropertyInfo UserTypeKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ProviderUserKeyType); /// /// Gets or sets the Username @@ -502,38 +500,7 @@ namespace Umbraco.Core.Models } } - /// - /// Gets or sets the type of the provider user key. - /// - /// - /// The type of the provider user key. - /// - [IgnoreDataMember] - internal Type ProviderUserKeyType - { - get - { - return _userTypeKey; - } - private set - { - SetPropertyValueAndDetectChanges(o => - { - _userTypeKey = value; - return _userTypeKey; - }, _userTypeKey, UserTypeKeySelector); - } - } - - /// - /// Sets the type of the provider user key. - /// - /// The type. - internal void SetProviderUserKeyType(Type type) - { - ProviderUserKeyType = type; - } - + /// /// Method to call when Entity is being saved /// diff --git a/src/Umbraco.Core/Models/MemberType.cs b/src/Umbraco.Core/Models/MemberType.cs index 6620843ab5..74a879be81 100644 --- a/src/Umbraco.Core/Models/MemberType.cs +++ b/src/Umbraco.Core/Models/MemberType.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Reflection; using System.Runtime.Serialization; @@ -21,7 +20,12 @@ namespace Umbraco.Core.Models MemberTypePropertyTypes = new Dictionary(); } - public MemberType(IContentTypeComposition parent) : base(parent) + public MemberType(IContentTypeComposition parent) : this(parent, null) + { + } + + public MemberType(IContentTypeComposition parent, string alias) + : base(parent, alias) { MemberTypePropertyTypes = new Dictionary(); } diff --git a/src/Umbraco.Core/Models/Membership/MembershipUserExtensions.cs b/src/Umbraco.Core/Models/Membership/MembershipUserExtensions.cs index 6008c0ae02..2289983033 100644 --- a/src/Umbraco.Core/Models/Membership/MembershipUserExtensions.cs +++ b/src/Umbraco.Core/Models/Membership/MembershipUserExtensions.cs @@ -7,9 +7,9 @@ namespace Umbraco.Core.Models.Membership { internal static class MembershipUserExtensions { - internal static UmbracoMembershipMember AsConcreteMembershipUser(this IMembershipUser member, string providerName) + internal static UmbracoMembershipMember AsConcreteMembershipUser(this IMembershipUser member, string providerName, bool providerKeyAsGuid = false) { - var membershipMember = new UmbracoMembershipMember(member, providerName); + var membershipMember = new UmbracoMembershipMember(member, providerName, providerKeyAsGuid); return membershipMember; } diff --git a/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs b/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs index 1b8c7f5393..0f02f4f73b 100644 --- a/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs +++ b/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs @@ -1,5 +1,6 @@ using System; using System.Web.Security; +using Umbraco.Core.Configuration; namespace Umbraco.Core.Models.Membership { @@ -25,7 +26,7 @@ namespace Umbraco.Core.Models.Membership //NOTE: We are not calling the base constructor which will validate that a provider with the specified name exists which causes issues with unit tests. The ctor // validation for that doesn't need to be there anyways (have checked the source). - public UmbracoMembershipMember(IMembershipUser member, string providerName) + public UmbracoMembershipMember(IMembershipUser member, string providerName, bool providerKeyAsGuid = false) { _member = member; //NOTE: We are copying the values here so that everything is consistent with how the underlying built-in ASP.Net membership user @@ -37,7 +38,7 @@ namespace Umbraco.Core.Models.Membership if (member.PasswordQuestion != null) _passwordQuestion = member.PasswordQuestion.Trim(); _providerName = providerName; - _providerUserKey = member.ProviderUserKey; + _providerUserKey = providerKeyAsGuid ? member.ProviderUserKey : member.Id; _comment = member.Comments; _isApproved = member.IsApproved; _isLockedOut = member.IsLockedOut; diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index ba9b89d779..c9b55bd937 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -58,7 +58,6 @@ namespace Umbraco.Core.Models.Membership private IUserType _userType; private string _name; - private Type _userTypeKey; private List _addedSections; private List _removedSections; private ObservableCollection _sectionCollection; @@ -80,7 +79,6 @@ namespace Umbraco.Core.Models.Membership private static readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartMediaId); private static readonly PropertyInfo AllowedSectionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedSections); private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - private static readonly PropertyInfo UserTypeKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ProviderUserKeyType); private static readonly PropertyInfo UsernameSelector = ExpressionHelper.GetPropertyInfo(x => x.Username); private static readonly PropertyInfo EmailSelector = ExpressionHelper.GetPropertyInfo(x => x.Email); @@ -101,37 +99,6 @@ namespace Umbraco.Core.Models.Membership set { throw new NotSupportedException("Cannot set the provider user key for a user"); } } - /// - /// Gets or sets the type of the provider user key. - /// - /// - /// The type of the provider user key. - /// - [IgnoreDataMember] - internal Type ProviderUserKeyType - { - get - { - return _userTypeKey; - } - private set - { - SetPropertyValueAndDetectChanges(o => - { - _userTypeKey = value; - return _userTypeKey; - }, _userTypeKey, UserTypeKeySelector); - } - } - - /// - /// Sets the type of the provider user key. - /// - /// The type. - internal void SetProviderUserKeyType(Type type) - { - ProviderUserKeyType = type; - } [DataMember] public string Username diff --git a/src/Umbraco.Core/Models/PartialView.cs b/src/Umbraco.Core/Models/PartialView.cs index e470d55037..0f0279d8d3 100644 --- a/src/Umbraco.Core/Models/PartialView.cs +++ b/src/Umbraco.Core/Models/PartialView.cs @@ -26,7 +26,7 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - internal class PartialView : File + public class PartialView : File, IPartialView { //public PartialView(): base(string.Empty) //{ diff --git a/src/Umbraco.Core/Models/PropertyGroupCollection.cs b/src/Umbraco.Core/Models/PropertyGroupCollection.cs index 6e1847856b..399fa22be6 100644 --- a/src/Umbraco.Core/Models/PropertyGroupCollection.cs +++ b/src/Umbraco.Core/Models/PropertyGroupCollection.cs @@ -71,16 +71,34 @@ namespace Umbraco.Core.Models { using (new WriteLock(_addLocker)) { - var key = GetKeyForItem(item); - if (key != null) + //Note this is done to ensure existig groups can be renamed + if (item.HasIdentity && item.Id > 0) { - var exists = this.Contains(key); + var exists = this.Contains(item.Id); if (exists) { - SetItem(IndexOfKey(key), item); + var keyExists = this.Contains(item.Name); + if(keyExists) + throw new Exception(string.Format("Naming conflict: Changing the name of PropertyGroup '{0}' would result in duplicates", item.Name)); + + SetItem(IndexOfKey(item.Id), item); return; } } + else + { + var key = GetKeyForItem(item); + if (key != null) + { + var exists = this.Contains(key); + if (exists) + { + SetItem(IndexOfKey(key), item); + return; + } + } + } + base.Add(item); OnAdd.IfNotNull(x => x.Invoke());//Could this not be replaced by a Mandate/Contract for ensuring item is not null @@ -99,6 +117,11 @@ namespace Umbraco.Core.Models return this.Any(x => x.Name == groupName); } + public bool Contains(int id) + { + return this.Any(x => x.Id == id); + } + public void RemoveItem(string propertyGroupName) { var key = IndexOfKey(propertyGroupName); @@ -119,6 +142,18 @@ namespace Umbraco.Core.Models return -1; } + public int IndexOfKey(int id) + { + for (var i = 0; i < this.Count; i++) + { + if (this[i].Id == id) + { + return i; + } + } + return -1; + } + protected override string GetKeyForItem(PropertyGroup item) { return item.Name; diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index fb7e82b27e..a19b7d98c2 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -1,9 +1,9 @@ using System; +using System.Diagnostics; using System.Reflection; using System.Runtime.Serialization; using System.Text.RegularExpressions; using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Persistence; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Strings; @@ -14,6 +14,7 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] + [DebuggerDisplay("Id: {Id}, Name: {Name}, Alias: {Alias}")] public class PropertyType : Entity, IEquatable { private readonly bool _isExplicitDbType; diff --git a/src/Umbraco.Core/Models/Rdbms/NodeDto.cs b/src/Umbraco.Core/Models/Rdbms/NodeDto.cs index a89879d8b5..f9aadf4963 100644 --- a/src/Umbraco.Core/Models/Rdbms/NodeDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/NodeDto.cs @@ -41,6 +41,7 @@ namespace Umbraco.Core.Models.Rdbms [Column("uniqueID")] [NullSetting(NullSetting = NullSettings.Null)] + [Index(IndexTypes.NonClustered, Name = "IX_umbracoNodeUniqueID")] public Guid? UniqueId { get; set; } [Column("text")] diff --git a/src/Umbraco.Core/Models/TaggableObjectTypes.cs b/src/Umbraco.Core/Models/TaggableObjectTypes.cs index be019410e5..ae54bc02bd 100644 --- a/src/Umbraco.Core/Models/TaggableObjectTypes.cs +++ b/src/Umbraco.Core/Models/TaggableObjectTypes.cs @@ -5,6 +5,7 @@ /// public enum TaggableObjectTypes { + All, Content, Media, Member diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index 93960d70c4..0621c83a72 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -1,11 +1,36 @@ using System; using System.Globalization; +using System.Linq; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Services; namespace Umbraco.Core.Models { - internal static class UserExtensions + public static class UserExtensions { + /// + /// Returns the culture info associated with this user, based on the language they're assigned to in the back office + /// + /// + /// + /// + public static CultureInfo GetUserCulture(this IUser user, ILocalizedTextService textService) + { + if (user == null) throw new ArgumentNullException("user"); + if (textService == null) throw new ArgumentNullException("textService"); + return GetUserCulture(user.Language, textService); + } + + internal static CultureInfo GetUserCulture(string userLanguage, ILocalizedTextService textService) + { + return textService.GetSupportedCultures() + .FirstOrDefault(culture => + //match on full name first + culture.Name.InvariantEquals(userLanguage.Replace("_", "-")) || + //then match on the 2 letter name + culture.TwoLetterISOLanguageName.InvariantEquals(userLanguage)); + } + /// /// Checks if the user has access to the content item based on their start noe /// diff --git a/src/Umbraco.Core/ObjectResolution/LazyManyObjectsResolverbase.cs b/src/Umbraco.Core/ObjectResolution/LazyManyObjectsResolverbase.cs index f2223d338e..6aa3cce51c 100644 --- a/src/Umbraco.Core/ObjectResolution/LazyManyObjectsResolverbase.cs +++ b/src/Umbraco.Core/ObjectResolution/LazyManyObjectsResolverbase.cs @@ -23,7 +23,7 @@ namespace Umbraco.Core.ObjectResolution where TResolver : ResolverBase { #region Constructors - + /// /// Initializes a new instance of the class with an empty list of objects, /// with creation of objects based on an HttpRequest lifetime scope. @@ -31,9 +31,11 @@ namespace Umbraco.Core.ObjectResolution /// The lifetime scope of instantiated objects, default is per Application. /// If is per HttpRequest then there must be a current HttpContext. /// is per HttpRequest but the current HttpContext is null. - protected LazyManyObjectsResolverBase(ObjectLifetimeScope scope = ObjectLifetimeScope.Application) - : base(scope) - { } + protected LazyManyObjectsResolverBase(ObjectLifetimeScope scope = ObjectLifetimeScope.Application) + : base(scope) + { + Initialize(); + } /// /// Initializes a new instance of the class with an empty list of objects, @@ -41,9 +43,11 @@ namespace Umbraco.Core.ObjectResolution /// /// The HttpContextBase corresponding to the HttpRequest. /// is null. - protected LazyManyObjectsResolverBase(HttpContextBase httpContext) - : base(httpContext) - { } + protected LazyManyObjectsResolverBase(HttpContextBase httpContext) + : base(httpContext) + { + Initialize(); + } /// /// Initializes a new instance of the class with an initial list @@ -101,23 +105,37 @@ namespace Umbraco.Core.ObjectResolution private readonly List> _lazyTypeList = new List>(); private readonly List>> _typeListProducerList = new List>>(); private readonly List _excludedTypesList = new List(); + private Lazy> _resolvedTypes = null; + + private void Initialize() + { + _resolvedTypes = new Lazy>(() => + { + var resolvedTypes = new List(); + + // get the types by evaluating the lazy & producers + var types = new List(); + types.AddRange(_lazyTypeList.Select(x => x.Value)); + types.AddRange(_typeListProducerList.SelectMany(x => x())); + + // we need to validate each resolved type now since we could + // not do it before evaluating the lazy & producers + foreach (var type in types.Where(x => _excludedTypesList.Contains(x) == false)) + { + AddValidAndNoDuplicate(resolvedTypes, type); + } + + return resolvedTypes; + }); + } - private List _resolvedTypes = null; - private readonly ReaderWriterLockSlim _resolvedTypesLock = new ReaderWriterLockSlim(); - /// /// Gets a value indicating whether the resolver has resolved types to create instances from. /// /// To be used in unit tests. public bool HasResolvedTypes { - get - { - using (new ReadLock(_resolvedTypesLock)) - { - return _resolvedTypes != null; - } - } + get { return _resolvedTypes.IsValueCreated; } } /// @@ -126,32 +144,7 @@ namespace Umbraco.Core.ObjectResolution /// When called, will get the types from the lazy list. protected override IEnumerable InstanceTypes { - get - { - using (var lck = new UpgradeableReadLock(_resolvedTypesLock)) - { - if (_resolvedTypes == null) - { - lck.UpgradeToWriteLock(); - - _resolvedTypes = new List(); - - // get the types by evaluating the lazy & producers - var types = new List(); - types.AddRange(_lazyTypeList.Select(x => x.Value)); - types.AddRange(_typeListProducerList.SelectMany(x => x())); - - // we need to validate each resolved type now since we could - // not do it before evaluating the lazy & producers - foreach (var type in types.Where(x => !_excludedTypesList.Contains(x))) - { - AddValidAndNoDuplicate(_resolvedTypes, type); - } - } - - return _resolvedTypes; - } - } + get { return _resolvedTypes.Value; } } /// diff --git a/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs b/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs index 8bb5995891..95ff923c58 100644 --- a/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs +++ b/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core.ObjectResolution where TResolved : class where TResolver : ResolverBase { - private IEnumerable _applicationInstances = null; + private Lazy> _applicationInstances = null; private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); private readonly string _httpContextKey; private readonly List _instanceTypes = new List(); @@ -47,6 +47,8 @@ namespace Umbraco.Core.ObjectResolution if (scope == ObjectLifetimeScope.HttpRequest) _httpContextKey = this.GetType().FullName; _instanceTypes = new List(); + + InitializeAppInstances(); } /// @@ -64,6 +66,8 @@ namespace Umbraco.Core.ObjectResolution _httpContextKey = this.GetType().FullName; CurrentHttpContext = httpContext; _instanceTypes = new List(); + + InitializeAppInstances(); } /// @@ -94,6 +98,11 @@ namespace Umbraco.Core.ObjectResolution } #endregion + private void InitializeAppInstances() + { + _applicationInstances = new Lazy>(() => CreateInstances().ToArray()); + } + /// /// Gets or sets a value indicating whether the resolver can resolve objects before resolution is frozen. /// @@ -178,30 +187,17 @@ namespace Umbraco.Core.ObjectResolution switch (LifetimeScope) { case ObjectLifetimeScope.HttpRequest: - // create new instances per HttpContext - using (var l = new UpgradeableReadLock(_lock)) - { - // create if not already there - if (CurrentHttpContext.Items[_httpContextKey] == null) - { - l.UpgradeToWriteLock(); - CurrentHttpContext.Items[_httpContextKey] = CreateInstances().ToArray(); - } - return (TResolved[])CurrentHttpContext.Items[_httpContextKey]; - } - case ObjectLifetimeScope.Application: - // create new instances per application - using (var l = new UpgradeableReadLock(_lock)) + // create new instances per HttpContext + if (CurrentHttpContext.Items[_httpContextKey] == null) { - // create if not already there - if (_applicationInstances == null) - { - l.UpgradeToWriteLock(); - _applicationInstances = CreateInstances().ToArray(); - } - return _applicationInstances; + CurrentHttpContext.Items[_httpContextKey] = CreateInstances().ToArray(); } + return (TResolved[])CurrentHttpContext.Items[_httpContextKey]; + + case ObjectLifetimeScope.Application: + + return _applicationInstances.Value; case ObjectLifetimeScope.Transient: default: diff --git a/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs b/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs index 006821e2b3..ea7c8ab8f9 100644 --- a/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs @@ -13,12 +13,21 @@ namespace Umbraco.Core.Persistence.Mappers internal abstract void BuildMap(); - internal string Map(string propertyName) + internal string Map(string propertyName, bool throws = false) { DtoMapModel dtoTypeProperty; - return PropertyInfoCache.TryGetValue(propertyName, out dtoTypeProperty) - ? GetColumnName(dtoTypeProperty.Type, dtoTypeProperty.PropertyInfo) - : string.Empty; + if (PropertyInfoCache.TryGetValue(propertyName, out dtoTypeProperty)) + { + return GetColumnName(dtoTypeProperty.Type, dtoTypeProperty.PropertyInfo); + } + else + { + if (throws) + { + throw new InvalidOperationException("Could not get the value with the key " + propertyName + " from the property info cache, keys available: " + string.Join(", ", PropertyInfoCache.Keys)); + } + return string.Empty; + } } internal void CacheMap(Expression> sourceMember, Expression> destinationMember) @@ -30,7 +39,12 @@ namespace Umbraco.Core.Persistence.Mappers internal DtoMapModel ResolveMapping(Expression> sourceMember, Expression> destinationMember) { var source = ExpressionHelper.FindProperty(sourceMember); - var destination = ExpressionHelper.FindProperty(destinationMember) as PropertyInfo; + var destination = (PropertyInfo)ExpressionHelper.FindProperty(destinationMember); + + if (destination == null) + { + throw new InvalidOperationException("The 'destination' returned was null, cannot resolve the mapping"); + } return new DtoMapModel(typeof(TDestination), destination, source.Name); } diff --git a/src/Umbraco.Core/Persistence/Mappers/DtoMapModel.cs b/src/Umbraco.Core/Persistence/Mappers/DtoMapModel.cs index b897e53895..d351b8abb3 100644 --- a/src/Umbraco.Core/Persistence/Mappers/DtoMapModel.cs +++ b/src/Umbraco.Core/Persistence/Mappers/DtoMapModel.cs @@ -12,8 +12,8 @@ namespace Umbraco.Core.Persistence.Mappers SourcePropertyName = sourcePropertyName; } - public string SourcePropertyName { get; set; } - public Type Type { get; set; } - public PropertyInfo PropertyInfo { get; set; } + public string SourcePropertyName { get; private set; } + public Type Type { get; private set; } + public PropertyInfo PropertyInfo { get; private set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs b/src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs index 4b6b513bf2..007ddbd014 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs @@ -41,11 +41,6 @@ namespace Umbraco.Core.Persistence.Mappers { return byAttribute.Result; } - - //static mapper registration if not using attributes, could be something like this: - //if (type == typeof (UserType)) - // return new UserTypeMapper(); - throw new Exception("Invalid Type: A Mapper could not be resolved based on the passed in Type"); }); } @@ -56,31 +51,18 @@ namespace Umbraco.Core.Persistence.Mappers /// /// private Attempt TryGetMapperByAttribute(Type entityType) - { - //get all BaseMapper types that have a MapperFor attribute: - var assignedMapperTypes = InstanceTypes; - + { //check if any of the mappers are assigned to this type - var mapper = assignedMapperTypes.FirstOrDefault( - x => x.GetCustomAttributes(false) + var mapper = Values.FirstOrDefault( + x => x.GetType().GetCustomAttributes(false) .Any(m => m.EntityType == entityType)); if (mapper == null) { return Attempt.Fail(); } - try - { - var instance = Activator.CreateInstance(mapper) as BaseMapper; - return instance != null - ? Attempt.Succeed(instance) - : Attempt.Fail(); - } - catch (Exception ex) - { - LogHelper.Error(typeof(MappingResolver), "Could not instantiate mapper of type " + mapper, ex); - return Attempt.Fail(ex); - } + + return Attempt.Succeed(mapper); } internal string GetMapping(Type type, string propertyName) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterCmsMacroPropertyTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterCmsMacroPropertyTable.cs index e4425673f2..60828c5a5c 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterCmsMacroPropertyTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterCmsMacroPropertyTable.cs @@ -33,7 +33,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven } else { - //If we are on SQLServer, we need to delete constraints by name, older versions of umbraco did not name these default constraints + //If we are on SQLServer, we need to delete default constraints by name, older versions of umbraco did not name these default constraints // consistently so we need to look up the constraint name to delete, this only pertains to SQL Server and this issue: // http://issues.umbraco.org/issue/U4-4133 var sqlServerSyntaxProvider = new SqlServerSyntaxProvider(); @@ -51,16 +51,15 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven Delete.Column("macroPropertyHidden").FromTable("cmsMacroProperty"); - if (Context.CurrentDatabaseProvider != DatabaseProviders.SqlServer) + if (Context.CurrentDatabaseProvider == DatabaseProviders.MySql) { Delete.ForeignKey().FromTable("cmsMacroProperty").ForeignColumn("macroPropertyType").ToTable("cmsMacroPropertyType").PrimaryColumn("id"); } else { - //If we are on SQLServer, we need to delete constraints by name, older versions of umbraco did not name these key constraints - // consistently so we need to look up the constraint name to delete, this only pertains to SQL Server and this issue: - // http://issues.umbraco.org/issue/U4-4133 - + //Before we try to delete this constraint, we'll see if it exists first, some older schemas never had it and some older schema's had this named + // differently than the default. + var keyConstraints = SqlSyntaxContext.SqlSyntaxProvider.GetConstraintsPerColumn(Context.Database).Distinct(); var constraint = keyConstraints .SingleOrDefault(x => x.Item1 == "cmsMacroProperty" && x.Item2 == "macroPropertyType" && x.Item3.InvariantStartsWith("PK_") == false); diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs index a9d4a148c8..31cfeb7997 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs @@ -31,15 +31,14 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven Alter.Table("cmsTagRelationship").AddColumn("propertyTypeId").AsInt32().Nullable(); //drop the foreign key on umbracoNode. Must drop foreign key first before primary key can be removed in MySql. - if (Context.CurrentDatabaseProvider != DatabaseProviders.SqlServer) + if (Context.CurrentDatabaseProvider == DatabaseProviders.MySql) { Delete.ForeignKey().FromTable("cmsTagRelationship").ForeignColumn("nodeId").ToTable("umbracoNode").PrimaryColumn("id"); } else { - //If we are on SQLServer, we need to delete constraints by name, older versions of umbraco did not name these key constraints - // consistently so we need to look up the constraint name to delete, this only pertains to SQL Server and this issue: - // http://issues.umbraco.org/issue/U4-4133 + //Before we try to delete this constraint, we'll see if it exists first, some older schemas never had it and some older schema's had this named + // differently than the default. var constraint = constraints .SingleOrDefault(x => x.Item1 == "cmsTagRelationship" && x.Item2 == "nodeId" && x.Item3.InvariantStartsWith("PK_") == false); @@ -62,7 +61,12 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven } else { - Delete.PrimaryKey("PK_cmsTagRelationship").FromTable("cmsTagRelationship"); + //lookup the PK by name + var pkName = constraints.FirstOrDefault(x => x.Item1.InvariantEquals("cmsTagRelationship") && x.Item3.InvariantStartsWith("PK_")); + if (pkName != null) + { + Delete.PrimaryKey(pkName.Item3).FromTable("cmsTagRelationship"); + } } } @@ -137,22 +141,6 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven .OnDelete(Rule.None) .OnUpdate(Rule.None); - //Some very old schemas don't have an index on the cmsContent.nodeId column, I'm not actually sure when it was added but - // it is absolutely required to exist in order to add this foreign key, so we'll need to check it's existence - // this came to light from this issue: http://issues.umbraco.org/issue/U4-4133 - var dbIndexes = SqlSyntaxContext.SqlSyntaxProvider.GetDefinedIndexes(Context.Database) - .Select(x => new DbIndexDefinition() - { - TableName = x.Item1, - IndexName = x.Item2, - ColumnName = x.Item3, - IsUnique = x.Item4 - }).ToArray(); - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsContent")) == false) - { - Create.Index("IX_cmsContent").OnTable("cmsContent").OnColumn("nodeId").Ascending().WithOptions().Unique(); - } - //now we need to add a foreign key to the nodeId column to cmsContent (intead of the original umbracoNode) Create.ForeignKey("FK_cmsTagRelationship_cmsContent") .FromTable("cmsTagRelationship") diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AssignMissingKeysAndIndexes.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AssignMissingKeysAndIndexes.cs new file mode 100644 index 0000000000..7be58853c9 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AssignMissingKeysAndIndexes.cs @@ -0,0 +1,93 @@ +using System.Data; +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven +{ + /// + /// I'm not actually sure how this is possible but I've come across one install that was missing these PKs + /// and it wasn't a MySQL install. + /// see: http://issues.umbraco.org/issue/U4-5707 + /// + [Migration("7.0.0", 0, GlobalSettings.UmbracoMigrationName)] + public class AssignMissingKeysAndIndexes : MigrationBase + { + public override void Up() + { + + //Some very old schemas don't have an index on the cmsContent.nodeId column, I'm not actually sure when it was added but + // it is absolutely required to exist in order to have it as a foreign key reference, so we'll need to check it's existence + // this came to light from this issue: http://issues.umbraco.org/issue/U4-4133 + var dbIndexes = SqlSyntaxContext.SqlSyntaxProvider.GetDefinedIndexes(Context.Database) + .Select(x => new DbIndexDefinition() + { + TableName = x.Item1, + IndexName = x.Item2, + ColumnName = x.Item3, + IsUnique = x.Item4 + }).ToArray(); + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsContent")) == false) + { + Create.Index("IX_cmsContent").OnTable("cmsContent").OnColumn("nodeId").Ascending().WithOptions().Unique(); + } + + if (Context.CurrentDatabaseProvider == DatabaseProviders.SqlServer + || Context.CurrentDatabaseProvider == DatabaseProviders.SqlServerCE) + { + var constraints = SqlSyntaxContext.SqlSyntaxProvider.GetConstraintsPerColumn(Context.Database).Distinct().ToArray(); + + //This should be 2 because this table has 2 keys + if (constraints.Count(x => x.Item1.InvariantEquals("cmsPreviewXml") && x.Item3.InvariantStartsWith("PK_")) == 0) + { + Create.PrimaryKey("PK_cmsContentPreviewXml") + .OnTable("cmsPreviewXml") + .Columns(new[] { "nodeId", "versionId" }); + } + + if (constraints.Count(x => x.Item1.InvariantEquals("cmsTags") && x.Item3.InvariantStartsWith("PK_")) == 0) + { + Create.PrimaryKey("PK_cmsTags") + .OnTable("cmsTags") + .Columns(new[] { "id" }); + } + + if (constraints.Count(x => x.Item1.InvariantEquals("cmsStylesheetProperty") && x.Item3.InvariantStartsWith("PK_")) == 0) + { + Create.PrimaryKey("PK_cmsStylesheetProperty") + .OnTable("cmsStylesheetProperty") + .Columns(new[] { "nodeId" }); + } + + if (constraints.Count(x => x.Item1.InvariantEquals("cmsStylesheet") && x.Item3.InvariantStartsWith("PK_")) == 0) + { + Create.PrimaryKey("PK_cmsStylesheet") + .OnTable("cmsStylesheet") + .Columns(new[] { "nodeId" }); + + Create.ForeignKey("FK_cmsStylesheet_umbracoNode_id").FromTable("cmsStylesheet").ForeignColumn("nodeId") + .ToTable("umbracoNode").PrimaryColumn("id").OnDeleteOrUpdate(Rule.None); + } + + if (constraints.Count(x => x.Item1.InvariantEquals("cmsMember") && x.Item3.InvariantStartsWith("PK_")) == 0) + { + Create.PrimaryKey("PK_cmsMember") + .OnTable("cmsMember") + .Columns(new[] { "nodeId" }); + + Create.ForeignKey("FK_cmsMember_umbracoNode_id").FromTable("cmsMember").ForeignColumn("nodeId") + .ToTable("umbracoNode").PrimaryColumn("id").OnDeleteOrUpdate(Rule.None); + + Create.ForeignKey("FK_cmsMember_cmsContent_nodeId").FromTable("cmsMember").ForeignColumn("nodeId") + .ToTable("cmsContent").PrimaryColumn("nodeId").OnDeleteOrUpdate(Rule.None); + } + } + } + + public override void Down() + { + //don't do anything, these keys should have always existed! + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AddIndexToUmbracoNodeTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AddIndexToUmbracoNodeTable.cs new file mode 100644 index 0000000000..90cd2be8c2 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AddIndexToUmbracoNodeTable.cs @@ -0,0 +1,45 @@ +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenTwoZero +{ + [Migration("7.2.0", 3, GlobalSettings.UmbracoMigrationName)] + public class AddIndexToUmbracoNodeTable : MigrationBase + { + private readonly bool _skipIndexCheck; + + internal AddIndexToUmbracoNodeTable(bool skipIndexCheck) + { + _skipIndexCheck = skipIndexCheck; + } + + public AddIndexToUmbracoNodeTable() + { + } + + public override void Up() + { + var dbIndexes = _skipIndexCheck ? new DbIndexDefinition[] { } : SqlSyntaxContext.SqlSyntaxProvider.GetDefinedIndexes(Context.Database) + .Select(x => new DbIndexDefinition + { + TableName = x.Item1, + IndexName = x.Item2, + ColumnName = x.Item3, + IsUnique = x.Item4 + }).ToArray(); + + //make sure it doesn't already exist + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoNodeUniqueID")) == false) + { + Create.Index("IX_umbracoNodeUniqueID").OnTable("umbracoNode").OnColumn("uniqueID").Ascending().WithOptions().NonClustered(); + } + } + + public override void Down() + { + Delete.Index("IX_umbracoNodeUniqueID").OnTable("umbracoNode"); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AlterDataTypePreValueTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AlterDataTypePreValueTable.cs index 8343a9dde5..b15f5eba58 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AlterDataTypePreValueTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AlterDataTypePreValueTable.cs @@ -1,3 +1,5 @@ +using System.Linq; +using AutoMapper; using Umbraco.Core.Configuration; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.SqlSyntax; @@ -11,9 +13,20 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenTwoZero { public override void Up() { - //To text - var textType = SqlSyntaxContext.SqlSyntaxProvider.GetSpecialDbType(SpecialDbTypes.NTEXT); - Alter.Table("cmsDataTypePreValues").AlterColumn("value").AsCustom(textType).Nullable(); + var columns = SqlSyntaxContext.SqlSyntaxProvider.GetColumnsInSchema(Context.Database).Distinct().ToArray(); + + //Check if it's already text + if (columns.Any(x => x.ColumnName.InvariantEquals("value") && x.TableName.InvariantEquals("cmsDataTypePreValues") + //mysql check + && (x.DataType.InvariantEquals("longtext") == false + //sql server check + && x.DataType.InvariantEquals("ntext") == false))) + { + //To text + var textType = SqlSyntaxContext.SqlSyntaxProvider.GetSpecialDbType(SpecialDbTypes.NTEXT); + Alter.Table("cmsDataTypePreValues").AlterColumn("value").AsCustom(textType).Nullable(); + } + } public override void Down() diff --git a/src/Umbraco.Core/Persistence/PetaPoco.cs b/src/Umbraco.Core/Persistence/PetaPoco.cs index 21f97643ec..2d57f99285 100644 --- a/src/Umbraco.Core/Persistence/PetaPoco.cs +++ b/src/Umbraco.Core/Persistence/PetaPoco.cs @@ -562,7 +562,14 @@ namespace Umbraco.Core.Persistence { object val = cmd.ExecuteScalarWithRetry(); OnExecutedCommand(cmd); - return (T)Convert.ChangeType(val, typeof(T)); + + if (val == null || val == DBNull.Value) + return default(T); + + Type t = typeof(T); + Type u = Nullable.GetUnderlyingType(t); + + return (T)Convert.ChangeType(val, u ?? t); } } finally @@ -843,7 +850,7 @@ namespace Umbraco.Core.Persistence public IEnumerable Query(Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql.SQL, sql.Arguments); } // Automagically guess the property relationships between various POCOs and create a delegate that will set them up - object GetAutoMapper(Type[] types) + Delegate GetAutoMapper(Type[] types) { // Build a key var kb = new StringBuilder(); @@ -858,7 +865,7 @@ namespace Umbraco.Core.Persistence RWLock.EnterReadLock(); try { - object mapper; + Delegate mapper; if (AutoMappers.TryGetValue(key, out mapper)) return mapper; } @@ -872,7 +879,7 @@ namespace Umbraco.Core.Persistence try { // Try again - object mapper; + Delegate mapper; if (AutoMappers.TryGetValue(key, out mapper)) return mapper; @@ -945,22 +952,53 @@ namespace Umbraco.Core.Persistence throw new InvalidOperationException(string.Format("Couldn't find split point between {0} and {1}", typeThis, typeNext)); } + // Instance data used by the Multipoco factory delegate - essentially a list of the nested poco factories to call public class MultiPocoFactory { - public List m_Delegates; - public Delegate GetItem(int index) { return m_Delegates[index]; } + + public MultiPocoFactory(IEnumerable dels) + { + Delegates = new List(dels); + } + private List Delegates { get; set; } + private Delegate GetItem(int index) { return Delegates[index]; } + + /// + /// Calls the delegate at the specified index and returns its values + /// + /// + /// + /// + private object CallDelegate(int index, IDataReader reader) + { + var d = GetItem(index); + var output = d.DynamicInvoke(reader); + return output; + } + + /// + /// Calls the callback delegate and passes in the output of all delegates as the parameters + /// + /// + /// + /// + /// + /// + public TRet CallCallback(Delegate callback, IDataReader dr, int count) + { + var args = new List(); + for(var i = 0;i CreateMultiPocoFactory(Type[] types, string sql, IDataReader r) - { - var m = new DynamicMethod("petapoco_multipoco_factory", typeof(TRet), new Type[] { typeof(MultiPocoFactory), typeof(IDataReader), typeof(object) }, typeof(MultiPocoFactory)); - var il = m.GetILGenerator(); - - // Load the callback - il.Emit(OpCodes.Ldarg_2); - + Func CreateMultiPocoFactory(Type[] types, string sql, IDataReader r) + { // Call each delegate var dels = new List(); int pos = 0; @@ -969,33 +1007,19 @@ namespace Umbraco.Core.Persistence // Add to list of delegates to call var del = FindSplitPoint(types[i], i + 1 < types.Length ? types[i + 1] : null, sql, r, ref pos); dels.Add(del); - - // Get the delegate - il.Emit(OpCodes.Ldarg_0); // callback,this - il.Emit(OpCodes.Ldc_I4, i); // callback,this,Index - il.Emit(OpCodes.Callvirt, typeof(MultiPocoFactory).GetMethod("GetItem")); // callback,Delegate - il.Emit(OpCodes.Ldarg_1); // callback,delegate, datareader - - // Call Invoke - var tDelInvoke = del.GetType().GetMethod("Invoke"); - il.Emit(OpCodes.Callvirt, tDelInvoke); // Poco left on stack } - // By now we should have the callback and the N pocos all on the stack. Call the callback and we're done - il.Emit(OpCodes.Callvirt, Expression.GetFuncType(types.Concat(new Type[] { typeof(TRet) }).ToArray()).GetMethod("Invoke")); - il.Emit(OpCodes.Ret); - - // Finish up - return (Func)m.CreateDelegate(typeof(Func), new MultiPocoFactory() { m_Delegates = dels }); + var mpFactory = new MultiPocoFactory(dels); + return (reader, arg3) => mpFactory.CallCallback(arg3, reader, types.Length); } // Various cached stuff static Dictionary MultiPocoFactories = new Dictionary(); - static Dictionary AutoMappers = new Dictionary(); + static Dictionary AutoMappers = new Dictionary(); static System.Threading.ReaderWriterLockSlim RWLock = new System.Threading.ReaderWriterLockSlim(); - // Get (or create) the multi-poco factory for a query - Func GetMultiPocoFactory(Type[] types, string sql, IDataReader r) + // Get (or create) the multi-poco factory for a query + Func GetMultiPocoFactory(Type[] types, string sql, IDataReader r) { // Build a key string (this is crap, should address this at some point) var kb = new StringBuilder(); @@ -1017,7 +1041,10 @@ namespace Umbraco.Core.Persistence { object oFactory; if (MultiPocoFactories.TryGetValue(key, out oFactory)) - return (Func)oFactory; + { + //mpFactory = oFactory; + return (Func)oFactory; + } } finally { @@ -1029,15 +1056,17 @@ namespace Umbraco.Core.Persistence try { // Check again - object oFactory; + object oFactory; ; if (MultiPocoFactories.TryGetValue(key, out oFactory)) - return (Func)oFactory; + { + return (Func)oFactory; + } + + // Create the factory + var factory = CreateMultiPocoFactory(types, sql, r); - // Create the factory - var Factory = CreateMultiPocoFactory(types, sql, r); - - MultiPocoFactories.Add(key, Factory); - return Factory; + MultiPocoFactories.Add(key, factory); + return factory; } finally { @@ -1047,7 +1076,7 @@ namespace Umbraco.Core.Persistence } // Actual implementation of the multi-poco query - public IEnumerable Query(Type[] types, object cb, string sql, params object[] args) + public IEnumerable Query(Type[] types, Delegate cb, string sql, params object[] args) { OpenSharedConnection(); try diff --git a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs index 1a561b7bf4..a0ccfaa070 100644 --- a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics; using System.Globalization; using System.Linq; using System.Linq.Expressions; @@ -26,13 +27,17 @@ namespace Umbraco.Core.Persistence.Querying m.Expression.NodeType == ExpressionType.Parameter && m.Expression.Type == typeof(T)) { - var field = _mapper.Map(m.Member.Name); - return field; + var field = _mapper.Map(m.Member.Name, true); + if (field.IsNullOrWhiteSpace()) + throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name); + return field; } if (m.Expression != null && m.Expression.NodeType == ExpressionType.Convert) { - var field = _mapper.Map(m.Member.Name); + var field = _mapper.Map(m.Member.Name, true); + if (field.IsNullOrWhiteSpace()) + throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name); return field; } diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 49e4673e5c..d89fc79dbe 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -728,7 +728,6 @@ namespace Umbraco.Core.Persistence.Repositories Func> filterCallback = () => new Tuple(sbWhere.ToString(), args.ToArray()); - return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, new Tuple("cmsDocument", "nodeId"), ProcessQuery, orderBy, orderDirection, diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index 2533fe62e8..8c1b24329c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Data; +using System.Diagnostics; using System.Globalization; using System.Linq; using System.Text; @@ -297,9 +298,38 @@ AND umbracoNode.id <> @id", //Delete Tabs/Groups by excepting entries from db with entries from collections var dbPropertyGroups = Database.Fetch("WHERE contenttypeNodeId = @Id", new {Id = entity.Id}) - .Select(x => new Tuple(x.Id, x.Text)); - var entityPropertyGroups = entity.PropertyGroups.Select(x => new Tuple(x.Id, x.Name)); - var tabs = dbPropertyGroups.Except(entityPropertyGroups); + .Select(x => new Tuple(x.Id, x.Text)) + .ToList(); + var entityPropertyGroups = entity.PropertyGroups.Select(x => new Tuple(x.Id, x.Name)).ToList(); + var tabsToDelete = dbPropertyGroups.Select(x => x.Item1).Except(entityPropertyGroups.Select(x => x.Item1)); + var tabs = dbPropertyGroups.Where(x => tabsToDelete.Any(y => y == x.Item1)); + //Update Tab name downstream to ensure renaming is done properly + foreach (var propertyGroup in entityPropertyGroups) + { + Database.Update("SET Text = @TabName WHERE parentGroupId = @TabId", + new { TabName = propertyGroup.Item2, TabId = propertyGroup.Item1 }); + + var childGroups = Database.Fetch("WHERE parentGroupId = @TabId", new { TabId = propertyGroup.Item1 }); + foreach (var childGroup in childGroups) + { + var sibling = Database.Fetch("WHERE contenttypeNodeId = @Id AND text = @Name", + new { Id = childGroup.ContentTypeNodeId, Name = propertyGroup.Item2 }) + .FirstOrDefault(x => x.ParentGroupId.HasValue == false || x.ParentGroupId.Value.Equals(propertyGroup.Item1) == false); + //If the child group doesn't have a sibling there is no chance of duplicates and we continue + if (sibling == null || (sibling.ParentGroupId.HasValue && sibling.ParentGroupId.Value.Equals(propertyGroup.Item1))) continue; + + //Since the child group has a sibling with the same name we need to point any PropertyTypes to the sibling + //as this child group is about to leave the party. + Database.Update( + "SET propertyTypeGroupId = @PropertyTypeGroupId WHERE propertyTypeGroupId = @PropertyGroupId AND ContentTypeId = @ContentTypeId", + new { PropertyTypeGroupId = sibling.Id, PropertyGroupId = childGroup.Id, ContentTypeId = childGroup.ContentTypeNodeId }); + + //Since the parent group has been renamed and we have duplicates we remove this group + //and leave our sibling in charge of the part. + Database.Delete(childGroup); + } + } + //Do Tab updates foreach (var tab in tabs) { Database.Update("SET propertyTypeGroupId = NULL WHERE propertyTypeGroupId = @PropertyGroupId", diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs index 017bb3aeea..7c64620434 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs @@ -375,16 +375,15 @@ AND umbracoNode.id <> @id", var existing = existingByIds.FirstOrDefault(valueDto => valueDto.Id == pre.Value.Id); if (existing != null) { - existing.Value = pre.Value.Value; - existing.SortOrder = sortOrder; _preValRepository.AddOrUpdate(new PreValueEntity { //setting an id will update it Id = existing.Id, - Alias = existing.Alias, - SortOrder = existing.SortOrder, - Value = existing.Value, DataType = dataType, + //These are the new values to update + Alias = pre.Key, + SortOrder = sortOrder, + Value = pre.Value.Value }); } else diff --git a/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs b/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs index 357173dfb9..4020dbb713 100644 --- a/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs @@ -33,12 +33,12 @@ namespace Umbraco.Core.Persistence.Repositories get { return _fileSystem; } } - internal virtual void AddFolder(string folderPath) + public virtual void AddFolder(string folderPath) { _work.RegisterAdded(new Folder(folderPath), this); } - internal virtual void DeleteFolder(string folderPath) + public virtual void DeleteFolder(string folderPath) { _work.RegisterRemoved(new Folder(folderPath), this); } @@ -132,7 +132,7 @@ namespace Umbraco.Core.Persistence.Repositories protected virtual void PersistNewItem(TEntity entity) { - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(entity.Content))) + using (var stream = GetContentStream(entity.Content)) { FileSystem.AddFile(entity.Path, stream, true); entity.CreateDate = FileSystem.GetCreated(entity.Path).UtcDateTime; @@ -140,12 +140,13 @@ namespace Umbraco.Core.Persistence.Repositories //the id can be the hash entity.Id = entity.Path.GetHashCode(); entity.Key = entity.Path.EncodeAsGuid(); + entity.VirtualPath = FileSystem.GetUrl(entity.Path); } } protected virtual void PersistUpdatedItem(TEntity entity) { - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(entity.Content))) + using (var stream = GetContentStream(entity.Content)) { FileSystem.AddFile(entity.Path, stream, true); entity.CreateDate = FileSystem.GetCreated(entity.Path).UtcDateTime; @@ -153,6 +154,7 @@ namespace Umbraco.Core.Persistence.Repositories //the id can be the hash entity.Id = entity.Path.GetHashCode(); entity.Key = entity.Path.EncodeAsGuid(); + entity.VirtualPath = FileSystem.GetUrl(entity.Path); } } @@ -166,6 +168,16 @@ namespace Umbraco.Core.Persistence.Repositories #endregion + /// + /// Gets a stream that is used to write to the file + /// + /// + /// + protected virtual Stream GetContentStream(string content) + { + return new MemoryStream(Encoding.UTF8.GetBytes(content)); + } + protected IEnumerable FindAllFiles(string path) { var list = new List(); diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IPartialViewMacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IPartialViewMacroRepository.cs deleted file mode 100644 index 2d05af7438..0000000000 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IPartialViewMacroRepository.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Umbraco.Core.Models; - -namespace Umbraco.Core.Persistence.Repositories -{ - internal interface IPartialViewMacroRepository : IPartialViewRepository - { - /// - /// Adds or Updates an associated macro - /// - /// - void AddOrUpdate(IMacro macro); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IPartialViewRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IPartialViewRepository.cs index f5e5a80a95..8ba28eeaee 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IPartialViewRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IPartialViewRepository.cs @@ -2,8 +2,9 @@ namespace Umbraco.Core.Persistence.Repositories { - internal interface IPartialViewRepository : IRepository + internal interface IPartialViewRepository : IRepository { - + void AddFolder(string folderPath); + void DeleteFolder(string folderPath); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/PartialViewMacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PartialViewMacroRepository.cs index e9a1b5f457..f4d6906cd1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PartialViewMacroRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PartialViewMacroRepository.cs @@ -5,37 +5,18 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { - internal class PartialViewMacroRepository : PartialViewRepository, IPartialViewMacroRepository + internal class PartialViewMacroRepository : PartialViewRepository { - private readonly IMacroRepository _macroRepository; - public PartialViewMacroRepository(IUnitOfWork work, IMacroRepository macroRepository) - : this(work, new PhysicalFileSystem(SystemDirectories.MvcViews + "/MacroPartials/"), macroRepository) + public PartialViewMacroRepository(IUnitOfWork work) + : this(work, new PhysicalFileSystem(SystemDirectories.MvcViews + "/MacroPartials/")) { } - public PartialViewMacroRepository(IUnitOfWork work, IFileSystem fileSystem, IMacroRepository macroRepository) + public PartialViewMacroRepository(IUnitOfWork work, IFileSystem fileSystem) : base(work, fileSystem) { - _macroRepository = macroRepository; } - /// - /// Ensure the macro repo is disposed which contains a database UOW - /// - protected override void DisposeResources() - { - base.DisposeResources(); - _macroRepository.Dispose(); - } - - /// - /// Adds or updates a macro associated with the partial view - /// - /// - public void AddOrUpdate(IMacro macro) - { - _macroRepository.AddOrUpdate(macro); - } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/PartialViewRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PartialViewRepository.cs index b61b7ff66b..b98b284a1f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PartialViewRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PartialViewRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using Umbraco.Core.IO; @@ -8,7 +9,7 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { - internal class PartialViewRepository : FileRepository, IPartialViewRepository + internal class PartialViewRepository : FileRepository, IPartialViewRepository { public PartialViewRepository(IUnitOfWork work) : this(work, new PhysicalFileSystem(SystemDirectories.MvcViews + "/Partials/")) @@ -19,7 +20,7 @@ namespace Umbraco.Core.Persistence.Repositories { } - public override PartialView Get(string id) + public override IPartialView Get(string id) { if (FileSystem.FileExists(id) == false) { @@ -39,6 +40,7 @@ namespace Umbraco.Core.Persistence.Repositories var created = FileSystem.GetCreated(path).UtcDateTime; var updated = FileSystem.GetLastModified(path).UtcDateTime; + var script = new PartialView(path) { //id can be the hash @@ -46,7 +48,8 @@ namespace Umbraco.Core.Persistence.Repositories Content = content, Key = path.EncodeAsGuid(), CreateDate = created, - UpdateDate = updated + UpdateDate = updated, + VirtualPath = FileSystem.GetUrl(id) }; //on initial construction we don't want to have dirty properties tracked @@ -56,7 +59,7 @@ namespace Umbraco.Core.Persistence.Repositories return script; } - public override IEnumerable GetAll(params string[] ids) + public override IEnumerable GetAll(params string[] ids) { //ensure they are de-duplicated, easy win if people don't do this as this can cause many excess queries ids = ids.Distinct().ToArray(); @@ -77,5 +80,20 @@ namespace Umbraco.Core.Persistence.Repositories } } } + + /// + /// Gets a stream that is used to write to the file + /// + /// + /// + /// + /// This ensures the stream includes a utf8 BOM + /// + protected override Stream GetContentStream(string content) + { + var data = Encoding.UTF8.GetBytes(content); + var withBom = Encoding.UTF8.GetPreamble().Concat(data).ToArray(); + return new MemoryStream(withBom); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs index 3feea435a3..a14507e8a4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs @@ -52,7 +52,8 @@ namespace Umbraco.Core.Persistence.Repositories Content = content, Key = path.EncodeAsGuid(), CreateDate = created, - UpdateDate = updated + UpdateDate = updated, + VirtualPath = FileSystem.GetUrl(id) }; //on initial construction we don't want to have dirty properties tracked diff --git a/src/Umbraco.Core/Persistence/Repositories/StylesheetRepository.cs b/src/Umbraco.Core/Persistence/Repositories/StylesheetRepository.cs index dc240a2396..5537d65f07 100644 --- a/src/Umbraco.Core/Persistence/Repositories/StylesheetRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/StylesheetRepository.cs @@ -18,10 +18,10 @@ namespace Umbraco.Core.Persistence.Repositories private readonly IDatabaseUnitOfWork _dbwork; internal StylesheetRepository(IUnitOfWork work, IDatabaseUnitOfWork db, IFileSystem fileSystem) - : base(work, fileSystem) - { + : base(work, fileSystem) + { _dbwork = db; - } + } public StylesheetRepository(IUnitOfWork work, IDatabaseUnitOfWork db) : this(work, db, new PhysicalFileSystem(SystemDirectories.Css)) @@ -43,7 +43,7 @@ namespace Umbraco.Core.Persistence.Repositories { byte[] bytes = new byte[stream.Length]; stream.Position = 0; - stream.Read(bytes, 0, (int) stream.Length); + stream.Read(bytes, 0, (int)stream.Length); content = Encoding.UTF8.GetString(bytes); } @@ -52,20 +52,21 @@ namespace Umbraco.Core.Persistence.Repositories var updated = FileSystem.GetLastModified(path).UtcDateTime; var stylesheet = new Stylesheet(path) - { - Content = content, - Key = path.EncodeAsGuid(), - CreateDate = created, - UpdateDate = updated, - Id = GetStylesheetId(path) - }; + { + Content = content, + Key = path.EncodeAsGuid(), + CreateDate = created, + UpdateDate = updated, + Id = GetStylesheetId(path), + VirtualPath = FileSystem.GetUrl(id) + }; //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 stylesheet.ResetDirtyProperties(false); return stylesheet; - + } // Fix for missing Id's on FileService.GetStylesheets() call. This is needed as sytlesheets can only bo loaded in the editor via @@ -77,8 +78,11 @@ namespace Umbraco.Core.Persistence.Repositories .Select("*") .From() .Where("nodeObjectType = @NodeObjectType AND umbracoNode.text = @Alias", - new { NodeObjectType = UmbracoObjectTypes.Stylesheet.GetGuid(), - Alias = path.TrimEnd(".css").Replace("\\", "/") }); + new + { + NodeObjectType = UmbracoObjectTypes.Stylesheet.GetGuid(), + Alias = path.TrimEnd(".css").Replace("\\", "/") + }); var nodeDto = _dbwork.Database.FirstOrDefault(sql); return nodeDto == null ? 0 : nodeDto.NodeId; } @@ -90,7 +94,7 @@ namespace Umbraco.Core.Persistence.Repositories .Select("*") .From() .Where("nodeObjectType = @NodeObjectType AND umbracoNode.text in (@aliases)", - new + new { NodeObjectType = UmbracoObjectTypes.Stylesheet.GetGuid(), aliases = paths.Select(x => x.TrimEnd(".css").Replace("\\", "/")).ToArray() diff --git a/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs index 344d9f4c43..ac39d9556e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs @@ -168,8 +168,6 @@ namespace Umbraco.Core.Persistence.Repositories public IEnumerable GetTaggedEntitiesByTagGroup(TaggableObjectTypes objectType, string tagGroup) { - var nodeObjectType = GetNodeObjectType(objectType); - var sql = new Sql() .Select("cmsTagRelationship.nodeId, cmsPropertyType.Alias, cmsPropertyType.id as propertyTypeId, cmsTags.tag, cmsTags.id as tagId, cmsTags." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("group")) .From() @@ -181,17 +179,21 @@ namespace Umbraco.Core.Persistence.Repositories .On(left => left.Id, right => right.PropertyTypeId) .InnerJoin() .On(left => left.NodeId, right => right.NodeId) - .Where(dto => dto.NodeObjectType == nodeObjectType) .Where(dto => dto.Group == tagGroup); + if (objectType != TaggableObjectTypes.All) + { + var nodeObjectType = GetNodeObjectType(objectType); + sql = sql + .Where(dto => dto.NodeObjectType == nodeObjectType); + } + return CreateTaggedEntityCollection( ApplicationContext.Current.DatabaseContext.Database.Fetch(sql)); } public IEnumerable GetTaggedEntitiesByTag(TaggableObjectTypes objectType, string tag, string tagGroup = null) { - var nodeObjectType = GetNodeObjectType(objectType); - var sql = new Sql() .Select("cmsTagRelationship.nodeId, cmsPropertyType.Alias, cmsPropertyType.id as propertyTypeId, cmsTags.tag, cmsTags.id as tagId, cmsTags." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("group")) .From() @@ -203,9 +205,15 @@ namespace Umbraco.Core.Persistence.Repositories .On(left => left.Id, right => right.PropertyTypeId) .InnerJoin() .On(left => left.NodeId, right => right.NodeId) - .Where(dto => dto.NodeObjectType == nodeObjectType) .Where(dto => dto.Tag == tag); + if (objectType != TaggableObjectTypes.All) + { + var nodeObjectType = GetNodeObjectType(objectType); + sql = sql + .Where(dto => dto.NodeObjectType == nodeObjectType); + } + if (tagGroup.IsNullOrWhiteSpace() == false) { sql = sql.Where(dto => dto.Group == tagGroup); @@ -233,8 +241,6 @@ namespace Umbraco.Core.Persistence.Repositories public IEnumerable GetTagsForEntityType(TaggableObjectTypes objectType, string group = null) { - var nodeObjectType = GetNodeObjectType(objectType); - var sql = GetTagsQuerySelect(true); sql = ApplyRelationshipJoinToTagsQuery(sql); @@ -243,8 +249,14 @@ namespace Umbraco.Core.Persistence.Repositories .InnerJoin() .On(left => left.NodeId, right => right.NodeId) .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(dto => dto.NodeObjectType == nodeObjectType); + .On(left => left.NodeId, right => right.NodeId); + + if (objectType != TaggableObjectTypes.All) + { + var nodeObjectType = GetNodeObjectType(objectType); + sql = sql + .Where(dto => dto.NodeObjectType == nodeObjectType); + } sql = ApplyGroupFilterToTagsQuery(sql, group); diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index c529a236c3..26e6ec3469 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -127,7 +127,10 @@ namespace Umbraco.Core.Persistence.Repositories protected override void PersistNewItem(ITemplate entity) { - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(entity.Content))) + var data = Encoding.UTF8.GetBytes(entity.Content); + var withBom = Encoding.UTF8.GetPreamble().Concat(data).ToArray(); + + using (var stream = new MemoryStream(withBom)) { if (entity.GetTypeOfRenderingEngine() == RenderingEngine.Mvc) { @@ -184,7 +187,10 @@ namespace Umbraco.Core.Persistence.Repositories protected override void PersistUpdatedItem(ITemplate entity) { - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(entity.Content))) + var data = Encoding.UTF8.GetBytes(entity.Content); + var withBom = Encoding.UTF8.GetPreamble().Concat(data).ToArray(); + + using (var stream = new MemoryStream(withBom)) { if (entity.GetTypeOfRenderingEngine() == RenderingEngine.Mvc) { @@ -352,15 +358,13 @@ namespace Umbraco.Core.Persistence.Repositories private void PopulateViewTemplate(ITemplate template, string fileName) { - string content = string.Empty; - string path = string.Empty; + string content; + var path = string.Empty; using (var stream = _viewsFileSystem.OpenFile(fileName)) + using (var reader = new StreamReader(stream, Encoding.UTF8, true)) { - byte[] bytes = new byte[stream.Length]; - stream.Position = 0; - stream.Read(bytes, 0, (int)stream.Length); - content = Encoding.UTF8.GetString(bytes); + content = reader.ReadToEnd(); } template.Path = _viewsFileSystem.GetRelativePath(fileName); @@ -374,15 +378,13 @@ namespace Umbraco.Core.Persistence.Repositories private void PopulateMasterpageTemplate(ITemplate template, string fileName) { - string content = string.Empty; - string path = string.Empty; - + string content; + var path = string.Empty; + using (var stream = _masterpagesFileSystem.OpenFile(fileName)) + using (var reader = new StreamReader(stream, Encoding.UTF8, true)) { - byte[] bytes = new byte[stream.Length]; - stream.Position = 0; - stream.Read(bytes, 0, (int)stream.Length); - content = Encoding.UTF8.GetString(bytes); + content = reader.ReadToEnd(); } template.Path = _masterpagesFileSystem.GetRelativePath(fileName); diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 7fa6fef92f..3acc06b132 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -308,7 +308,11 @@ namespace Umbraco.Core.Persistence.Repositories IEnumerable result; var pagedResult = Database.Page(pageIndex + 1, pageSize, sqlNodeIdsWithSort); totalRecords = Convert.ToInt32(pagedResult.TotalItems); - if (totalRecords > 0) + + //NOTE: We need to check the actual items returned, not the 'totalRecords', that is because if you request a page number + // that doesn't actually have any data on it, the totalRecords will still indicate there are records but there are none in + // the pageResult, then the GetAll will actually return ALL records in the db. + if (pagedResult.Items.Any()) { //Crete the inner paged query that was used above to get the paged result, we'll use that as the inner sub query var args = sqlNodeIdsWithSort.Arguments; diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index f549617d86..1a8752739f 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -149,9 +149,9 @@ namespace Umbraco.Core.Persistence return new PartialViewRepository(uow); } - internal virtual IPartialViewMacroRepository CreatePartialViewMacroRepository(IUnitOfWork uow, IDatabaseUnitOfWork duow) + internal virtual IPartialViewRepository CreatePartialViewMacroRepository(IUnitOfWork uow) { - return new PartialViewMacroRepository(uow, CreateMacroRepository(duow)); + return new PartialViewMacroRepository(uow); } public virtual IStylesheetRepository CreateStylesheetRepository(IUnitOfWork uow, IDatabaseUnitOfWork db) diff --git a/src/Umbraco.Core/PropertyEditors/EmailValidator.cs b/src/Umbraco.Core/PropertyEditors/EmailValidator.cs index 0cda202756..403e26ca70 100644 --- a/src/Umbraco.Core/PropertyEditors/EmailValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/EmailValidator.cs @@ -16,16 +16,16 @@ namespace Umbraco.Core.PropertyEditors var emailVal = new EmailAddressAttribute(); - if (emailVal.IsValid(asString) == false) + if (asString != string.Empty && emailVal.IsValid(asString) == false) { - //TODO: localize these! + // TODO: localize these! yield return new ValidationResult("Email is invalid", new[] { "value" }); } } public IEnumerable Validate(object value, PreValueCollection preValues, PropertyEditor editor) { - return Validate(value, null, preValues, editor); + return this.Validate(value, null, preValues, editor); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/RequiredManifestValueValidator.cs b/src/Umbraco.Core/PropertyEditors/RequiredManifestValueValidator.cs index 757e09f30f..33c2c02cb6 100644 --- a/src/Umbraco.Core/PropertyEditors/RequiredManifestValueValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/RequiredManifestValueValidator.cs @@ -21,6 +21,15 @@ namespace Umbraco.Core.PropertyEditors else { var asString = value.ToString(); + + if (editor.ValueEditor.ValueType.InvariantEquals("JSON")) + { + if (asString.DetectIsEmptyJson()) + { + yield return new ValidationResult("Value cannot be empty", new[] { "value" }); + } + } + if (asString.IsNullOrWhiteSpace()) { yield return new ValidationResult("Value cannot be empty", new[] { "value" }); diff --git a/src/Umbraco.Core/Security/AuthenticationExtensions.cs b/src/Umbraco.Core/Security/AuthenticationExtensions.cs index f5b41d0e34..8511d39125 100644 --- a/src/Umbraco.Core/Security/AuthenticationExtensions.cs +++ b/src/Umbraco.Core/Security/AuthenticationExtensions.cs @@ -76,9 +76,10 @@ namespace Umbraco.Core.Security /// This will return the current back office identity. /// /// - /// + /// /// If set to true and a back office identity is not found and not authenticated, this will attempt to authenticate the - /// request just as is done in the Umbraco module and then set the current identity if it is valid + /// request just as is done in the Umbraco module and then set the current identity if it is valid. + /// Just like in the UmbracoModule, if this is true then the user's culture will be assigned to the request. /// /// /// Returns the current back office identity if an admin is authenticated otherwise null @@ -89,12 +90,20 @@ namespace Umbraco.Core.Security if (http.User == null) return null; //there's no user at all so no identity var identity = http.User.Identity as UmbracoBackOfficeIdentity; if (identity != null) return identity; + if (authenticateRequestIfNotFound == false) return null; + //even if authenticateRequestIfNotFound is true we cannot continue if the request is actually authenticated // which would mean something strange is going on that it is not an umbraco identity. if (http.User.Identity.IsAuthenticated) return null; + + //So the user is not authed but we've been asked to do the auth if authenticateRequestIfNotFound = true, + // which might occur in old webforms style things or for routes that aren't included as a back office request. + // in this case, we are just reverting to authing using the cookie. + + // TODO: Even though this is in theory legacy, we have legacy bits laying around and we'd need to do the auth based on + // how the Module will eventually do it (by calling in to any registered authenticators). - //now we just need to try to authenticate the current request var ticket = http.GetUmbracoAuthTicket(); if (http.AuthenticateCurrentRequest(ticket, true)) { diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 00dd4ddb0e..93069b7bd9 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -407,7 +407,7 @@ namespace Umbraco.Core.Services { using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork())) { - var query = Query.Builder.Where(x => x.Level == level && !x.Path.StartsWith("-20")); + var query = Query.Builder.Where(x => x.Level == level && !x.Path.StartsWith(Constants.System.RecycleBinContent.ToInvariantString())); var contents = repository.GetByQuery(query); return contents; @@ -460,7 +460,7 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetAncestors(IContent content) { - var ids = content.Path.Split(',').Where(x => x != "-1" && x != content.Id.ToString(CultureInfo.InvariantCulture)).Select(int.Parse).ToArray(); + var ids = content.Path.Split(',').Where(x => x != Constants.System.Root.ToInvariantString() && x != content.Id.ToString(CultureInfo.InvariantCulture)).Select(int.Parse).ToArray(); if (ids.Any() == false) return new List(); @@ -506,8 +506,8 @@ namespace Umbraco.Core.Services { var query = Query.Builder; - //if the id is -1, then just get all - if (id > 0) + //if the id is System Root, then just get all + if (id != Constants.System.Root) { query.Where(x => x.ParentId == id); } @@ -536,8 +536,8 @@ namespace Umbraco.Core.Services { var query = Query.Builder; - //if the id is -1, then just get all - if (id > 0) + //if the id is System Root, then just get all + if (id != Constants.System.Root) { query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); } @@ -614,7 +614,7 @@ namespace Umbraco.Core.Services /// Parent object public IContent GetParent(IContent content) { - if (content.ParentId == -1 || content.ParentId == -20) + if (content.ParentId == Constants.System.Root || content.ParentId == Constants.System.RecycleBinContent) return null; return GetById(content.ParentId); @@ -639,7 +639,7 @@ namespace Umbraco.Core.Services { using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork())) { - var query = Query.Builder.Where(x => x.ParentId == -1); + var query = Query.Builder.Where(x => x.ParentId == Constants.System.Root); var contents = repository.GetByQuery(query); return contents; @@ -697,7 +697,7 @@ namespace Umbraco.Core.Services { using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork())) { - var query = Query.Builder.Where(x => x.Path.Contains("-20")); + var query = Query.Builder.Where(x => x.Path.Contains(Constants.System.RecycleBinContent.ToInvariantString())); var contents = repository.GetByQuery(query); return contents; @@ -960,7 +960,7 @@ namespace Umbraco.Core.Services if (raiseEvents) Saved.RaiseEvent(new SaveEventArgs(asArray, false), this); - Audit.Add(AuditTypes.Save, "Bulk Save content performed by user", userId == -1 ? 0 : userId, -1); + Audit.Add(AuditTypes.Save, "Bulk Save content performed by user", userId == -1 ? 0 : userId, Constants.System.Root); } } @@ -1004,7 +1004,7 @@ namespace Umbraco.Core.Services Audit.Add(AuditTypes.Delete, string.Format("Delete Content of Type {0} performed by user", contentTypeId), - userId, -1); + userId, Constants.System.Root); } } @@ -1075,7 +1075,7 @@ namespace Umbraco.Core.Services DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, dateToRetain: versionDate), this); - Audit.Add(AuditTypes.Delete, "Delete Content by version date performed by user", userId, -1); + Audit.Add(AuditTypes.Delete, "Delete Content by version date performed by user", userId, Constants.System.Root); } /// @@ -1108,7 +1108,7 @@ namespace Umbraco.Core.Services DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, specificVersion: versionId), this); - Audit.Add(AuditTypes.Delete, "Delete Content by version performed by user", userId, -1); + Audit.Add(AuditTypes.Delete, "Delete Content by version performed by user", userId, Constants.System.Root); } } @@ -1191,7 +1191,7 @@ namespace Umbraco.Core.Services using (new WriteLock(Locker)) { //This ensures that the correct method is called if this method is used to Move to recycle bin. - if (parentId == -20) + if (parentId == Constants.System.RecycleBinContent) { MoveToRecycleBin(content, userId); return; @@ -1251,7 +1251,7 @@ namespace Umbraco.Core.Services } - Audit.Add(AuditTypes.Delete, "Empty Content Recycle Bin performed by user", 0, -20); + Audit.Add(AuditTypes.Delete, "Empty Content Recycle Bin performed by user", 0, Constants.System.RecycleBinContent); } /// @@ -1486,7 +1486,7 @@ namespace Umbraco.Core.Services contentTypeIds: contentTypeIds.Length == 0 ? null : contentTypeIds); } - Audit.Add(AuditTypes.Publish, "ContentService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, -1); + Audit.Add(AuditTypes.Publish, "ContentService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, Constants.System.Root); } @@ -1518,9 +1518,9 @@ namespace Umbraco.Core.Services moveInfo.Add(new MoveEventInfo(content, content.Path, parentId)); content.WriterId = userId; - if (parentId == -1) + if (parentId == Constants.System.Root) { - content.Path = string.Concat("-1,", content.Id); + content.Path = string.Concat(Constants.System.Root, ",", content.Id); content.Level = 1; } else @@ -1609,7 +1609,7 @@ namespace Umbraco.Core.Services var result = new List>(); //Check if parent is published (although not if its a root node) - if parent isn't published this Content cannot be published - if (content.ParentId != -1 && content.ParentId != -20 && IsPublishable(content) == false) + if (content.ParentId != Constants.System.Root && content.ParentId != Constants.System.RecycleBinContent && IsPublishable(content) == false) { LogHelper.Info( string.Format( @@ -1862,11 +1862,11 @@ namespace Umbraco.Core.Services foreach (var id in ids) { //If Id equals that of the recycle bin we return false because nothing in the bin can be published - if (id == -20) + if (id == Constants.System.RecycleBinContent) return false; //We don't check the System Root, so just continue - if (id == -1) continue; + if (id == Constants.System.Root) continue; //If the current id equals that of the passed in content and if current shouldn't be checked we skip it. if (checkCurrent == false && id == content.Id) continue; @@ -1883,7 +1883,7 @@ namespace Umbraco.Core.Services private PublishStatusType CheckAndLogIsPublishable(IContent content) { //Check if parent is published (although not if its a root node) - if parent isn't published this Content cannot be published - if (content.ParentId != -1 && content.ParentId != -20 && IsPublishable(content) == false) + if (content.ParentId != Constants.System.Root && content.ParentId != Constants.System.RecycleBinContent && IsPublishable(content) == false) { LogHelper.Info( string.Format( diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index b87dfa1ac1..4c1f9cd038 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Data; +using System.Diagnostics; using System.Linq; using System.Text; using System.Xml.Linq; @@ -8,11 +9,13 @@ using System.Threading; using Umbraco.Core.Auditing; using Umbraco.Core.Configuration; using Umbraco.Core.Events; +using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Services @@ -273,6 +276,74 @@ namespace Umbraco.Core.Services } + public void Validate(IContentTypeComposition compo) + { + using (new WriteLock(Locker)) + { + ValidateLocked(compo); + } + } + + private void ValidateLocked(IContentTypeComposition compositionContentType) + { + // performs business-level validation of the composition + // should ensure that it is absolutely safe to save the composition + + // eg maybe a property has been added, with an alias that's OK (no conflict with ancestors) + // but that cannot be used (conflict with descendants) + + var contentType = compositionContentType as IContentType; + var mediaType = compositionContentType as IMediaType; + + IContentTypeComposition[] allContentTypes; + if (contentType != null) + allContentTypes = GetAllContentTypes().Cast().ToArray(); + else if (mediaType != null) + allContentTypes = GetAllMediaTypes().Cast().ToArray(); + else + throw new Exception("Composition is neither IContentType nor IMediaType?"); + + var compositionAliases = compositionContentType.CompositionAliases(); + var compositions = allContentTypes.Where(x => compositionAliases.Any(y => x.Alias.Equals(y))); + var propertyTypeAliases = compositionContentType.PropertyTypes.Select(x => x.Alias.ToLowerInvariant()).ToArray(); + var indirectReferences = allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == compositionContentType.Id)); + var comparer = new DelegateEqualityComparer((x, y) => x.Id == y.Id, x => x.Id); + var dependencies = new HashSet(compositions, comparer); + var stack = new Stack(); + indirectReferences.ForEach(stack.Push);//Push indirect references to a stack, so we can add recursively + while (stack.Count > 0) + { + var indirectReference = stack.Pop(); + dependencies.Add(indirectReference); + //Get all compositions for the current indirect reference + var directReferences = indirectReference.ContentTypeComposition; + + foreach (var directReference in directReferences) + { + if (directReference.Id == compositionContentType.Id || directReference.Alias.Equals(compositionContentType.Alias)) continue; + dependencies.Add(directReference); + //A direct reference has compositions of its own - these also need to be taken into account + var directReferenceGraph = directReference.CompositionAliases(); + allContentTypes.Where(x => directReferenceGraph.Any(y => x.Alias.Equals(y, StringComparison.InvariantCultureIgnoreCase))).ForEach(c => dependencies.Add(c)); + } + //Recursive lookup of indirect references + allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == indirectReference.Id)).ForEach(stack.Push); + } + + foreach (var dependency in dependencies) + { + if (dependency.Id == compositionContentType.Id) continue; + var contentTypeDependency = allContentTypes.FirstOrDefault(x => x.Alias.Equals(dependency.Alias, StringComparison.InvariantCultureIgnoreCase)); + if (contentTypeDependency == null) continue; + var intersect = contentTypeDependency.PropertyTypes.Select(x => x.Alias.ToLowerInvariant()).Intersect(propertyTypeAliases).ToArray(); + if (intersect.Length == 0) continue; + + var message = string.Format("The following PropertyType aliases from the current ContentType conflict with existing PropertyType aliases: {0}.", + string.Join(", ", intersect)); + throw new Exception(message); + } + } + /// /// Saves a single object /// @@ -288,6 +359,7 @@ namespace Umbraco.Core.Services var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateContentTypeRepository(uow)) { + ValidateLocked(contentType); // throws if invalid contentType.CreatorId = userId; repository.AddOrUpdate(contentType); @@ -317,6 +389,11 @@ namespace Umbraco.Core.Services var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateContentTypeRepository(uow)) { + // all-or-nothing, validate them all first + foreach (var contentType in asArray) + { + ValidateLocked(contentType); // throws if invalid + } foreach (var contentType in asArray) { contentType.CreatorId = userId; @@ -487,6 +564,7 @@ namespace Umbraco.Core.Services var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateMediaTypeRepository(uow)) { + ValidateLocked(mediaType); // throws if invalid mediaType.CreatorId = userId; repository.AddOrUpdate(mediaType); uow.Commit(); @@ -517,7 +595,11 @@ namespace Umbraco.Core.Services var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateMediaTypeRepository(uow)) { - + // all-or-nothing, validate them all first + foreach (var mediaType in asArray) + { + ValidateLocked(mediaType); // throws if invalid + } foreach (var mediaType in asArray) { mediaType.CreatorId = userId; diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 39e7b63e99..63e0a390e7 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -200,8 +200,22 @@ namespace Umbraco.Core.Services /// Id of the user issueing the save public void Save(IEnumerable dataTypeDefinitions, int userId = 0) { - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(dataTypeDefinitions), this)) - return; + Save(dataTypeDefinitions, userId, true); + } + + /// + /// Saves a collection of + /// + /// to save + /// Id of the user issueing the save + /// Boolean indicating whether or not to raise events + public void Save(IEnumerable dataTypeDefinitions, int userId, bool raiseEvents) + { + if (raiseEvents) + { + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(dataTypeDefinitions), this)) + return; + } var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow)) @@ -213,7 +227,8 @@ namespace Umbraco.Core.Services } uow.Commit(); - Saved.RaiseEvent(new SaveEventArgs(dataTypeDefinitions, false), this); + if (raiseEvents) + Saved.RaiseEvent(new SaveEventArgs(dataTypeDefinitions, false), this); } Audit.Add(AuditTypes.Save, string.Format("Save DataTypeDefinition performed by user"), userId, -1); @@ -229,30 +244,27 @@ namespace Umbraco.Core.Services { //TODO: Should we raise an event here since we are really saving values for the data type? - using (new WriteLock(Locker)) + using (var uow = _uowProvider.GetUnitOfWork()) { - using (var uow = _uowProvider.GetUnitOfWork()) + using (var transaction = uow.Database.GetTransaction()) { - using (var transaction = uow.Database.GetTransaction()) + var sortOrderObj = + uow.Database.ExecuteScalar( + "SELECT max(sortorder) FROM cmsDataTypePreValues WHERE datatypeNodeId = @DataTypeId", new { DataTypeId = dataTypeId }); + int sortOrder; + if (sortOrderObj == null || int.TryParse(sortOrderObj.ToString(), out sortOrder) == false) { - var sortOrderObj = - uow.Database.ExecuteScalar( - "SELECT max(sortorder) FROM cmsDataTypePreValues WHERE datatypeNodeId = @DataTypeId", new { DataTypeId = dataTypeId }); - int sortOrder; - if (sortOrderObj == null || int.TryParse(sortOrderObj.ToString(), out sortOrder) == false) - { - sortOrder = 1; - } - - foreach (var value in values) - { - var dto = new DataTypePreValueDto { DataTypeNodeId = dataTypeId, Value = value, SortOrder = sortOrder }; - uow.Database.Insert(dto); - sortOrder++; - } - - transaction.Complete(); + sortOrder = 1; } + + foreach (var value in values) + { + var dto = new DataTypePreValueDto { DataTypeNodeId = dataTypeId, Value = value, SortOrder = sortOrder }; + uow.Database.Insert(dto); + sortOrder++; + } + + transaction.Complete(); } } } diff --git a/src/Umbraco.Core/Services/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/EntityXmlSerializer.cs index df29d02ca4..3e5eb6483b 100644 --- a/src/Umbraco.Core/Services/EntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/EntityXmlSerializer.cs @@ -250,7 +250,9 @@ namespace Umbraco.Core.Services { var tab = new XElement("Tab", new XElement("Id", propertyGroup.Id.ToString(CultureInfo.InvariantCulture)), - new XElement("Caption", propertyGroup.Name)); + new XElement("Caption", propertyGroup.Name), + new XElement("SortOrder", propertyGroup.SortOrder)); + tabs.Add(tab); } @@ -309,9 +311,17 @@ namespace Umbraco.Core.Services new XElement("AllowAtRoot", contentType.AllowedAsRoot.ToString()), new XElement("IsListView", contentType.IsContainer.ToString())); - var masterContentType = contentType.CompositionAliases().FirstOrDefault(); - if (masterContentType != null) - info.Add(new XElement("Master", masterContentType)); + var masterContentType = contentType.ContentTypeComposition.FirstOrDefault(x => x.Id == contentType.ParentId); + if(masterContentType != null) + info.Add(new XElement("Master", masterContentType.Alias)); + + var compositionsElement = new XElement("Compositions"); + var compositions = contentType.ContentTypeComposition; + foreach (var composition in compositions) + { + compositionsElement.Add(new XElement("Composition", composition.Alias)); + } + info.Add(compositionsElement); var allowedTemplates = new XElement("AllowedTemplates"); foreach (var template in contentType.AllowedTemplates) @@ -319,6 +329,7 @@ namespace Umbraco.Core.Services allowedTemplates.Add(new XElement("Template", template.Alias)); } info.Add(allowedTemplates); + if (contentType.DefaultTemplate != null && contentType.DefaultTemplate.Id != 0) info.Add(new XElement("DefaultTemplate", contentType.DefaultTemplate.Alias)); else @@ -356,7 +367,8 @@ namespace Umbraco.Core.Services { var tab = new XElement("Tab", new XElement("Id", propertyGroup.Id.ToString(CultureInfo.InvariantCulture)), - new XElement("Caption", propertyGroup.Name)); + new XElement("Caption", propertyGroup.Name), + new XElement("SortOrder", propertyGroup.SortOrder)); tabs.Add(tab); } diff --git a/src/Umbraco.Core/Services/FileService.cs b/src/Umbraco.Core/Services/FileService.cs index 50eff2da44..56f7d3a07b 100644 --- a/src/Umbraco.Core/Services/FileService.cs +++ b/src/Umbraco.Core/Services/FileService.cs @@ -388,16 +388,23 @@ namespace Umbraco.Core.Services #region Partial Views - internal IEnumerable GetPartialViewSnippetNames(params string[] filterNames) + public IEnumerable GetPartialViewSnippetNames(params string[] filterNames) { var snippetPath = IOHelper.MapPath(string.Format("{0}/PartialViewMacros/Templates/", SystemDirectories.Umbraco)); - return Directory.GetFiles(snippetPath, "*.cshtml") + var files = Directory.GetFiles(snippetPath, "*.cshtml") .Select(Path.GetFileNameWithoutExtension) .Except(filterNames, StringComparer.InvariantCultureIgnoreCase) .ToArray(); + + //Ensure the ones that are called 'Empty' are at the top + var empty = files.Where(x => Path.GetFileName(x).InvariantStartsWith("Empty")) + .OrderBy(x => x.Length) + .ToArray(); + + return empty.Union(files.Except(empty)); } - internal void DeletePartialViewFolder(string folderPath) + public void DeletePartialViewFolder(string folderPath) { var uow = _fileUowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreatePartialViewRepository(uow)) @@ -407,18 +414,17 @@ namespace Umbraco.Core.Services } } - internal void DeletePartialViewMacroFolder(string folderPath) + public void DeletePartialViewMacroFolder(string folderPath) { var uow = _fileUowProvider.GetUnitOfWork(); - var duow = _dataUowProvider.GetUnitOfWork(); - using (var repository = _repositoryFactory.CreatePartialViewMacroRepository(uow, duow)) + using (var repository = _repositoryFactory.CreatePartialViewMacroRepository(uow)) { ((PartialViewMacroRepository)repository).DeleteFolder(folderPath); uow.Commit(); } } - internal PartialView GetPartialView(string path) + public IPartialView GetPartialView(string path) { using (var repository = _repositoryFactory.CreatePartialViewRepository(_fileUowProvider.GetUnitOfWork())) { @@ -426,20 +432,44 @@ namespace Umbraco.Core.Services } } - internal PartialView GetPartialViewMacro(string path) + public IPartialView GetPartialViewMacro(string path) { - using (var repository = _repositoryFactory.CreatePartialViewMacroRepository( - _fileUowProvider.GetUnitOfWork(), - _dataUowProvider.GetUnitOfWork())) + using (var repository = _repositoryFactory.CreatePartialViewMacroRepository(_fileUowProvider.GetUnitOfWork())) { return repository.Get(path); } } - internal Attempt CreatePartialView(PartialView partialView, string snippetName = null, int userId = 0) + public Attempt CreatePartialView(IPartialView partialView, string snippetName = null, int userId = 0) { - if (CreatingPartialView.IsRaisedEventCancelled(new NewEventArgs(partialView, true, partialView.Alias, -1), this)) - return Attempt.Fail(); + return CreatePartialViewMacro(partialView, PartialViewType.PartialView, snippetName, userId); + } + + public Attempt CreatePartialViewMacro(IPartialView partialView, string snippetName = null, int userId = 0) + { + return CreatePartialViewMacro(partialView, PartialViewType.PartialViewMacro, snippetName, userId); + } + + private Attempt CreatePartialViewMacro(IPartialView partialView, PartialViewType partialViewType, string snippetName = null, int userId = 0) + { + if (CreatingPartialView.IsRaisedEventCancelled(new NewEventArgs(partialView, true, partialView.Alias, -1), this)) + return Attempt.Fail(); + + var uow = _fileUowProvider.GetUnitOfWork(); + string partialViewHeader = null; + IPartialViewRepository repository; + switch (partialViewType) + { + case PartialViewType.PartialView: + repository = _repositoryFactory.CreatePartialViewRepository(uow); + partialViewHeader = PartialViewHeader; + break; + case PartialViewType.PartialViewMacro: + default: + repository = _repositoryFactory.CreatePartialViewMacroRepository(uow); + partialViewHeader = PartialViewMacroHeader; + break; + } if (snippetName.IsNullOrWhiteSpace() == false) { @@ -456,210 +486,114 @@ namespace Umbraco.Core.Services //strip the @inherits if it's there snippetContent = StripPartialViewHeader(snippetContent); - - var content = string.Format("{0}{1}{2}", PartialViewHeader, Environment.NewLine, snippetContent); + + var content = string.Format("{0}{1}{2}", + partialViewHeader, + Environment.NewLine, snippetContent); partialView.Content = content; } } - var uow = _fileUowProvider.GetUnitOfWork(); - using (var repository = _repositoryFactory.CreatePartialViewRepository(uow)) + using (repository) { repository.AddOrUpdate(partialView); uow.Commit(); - CreatedPartialView.RaiseEvent(new NewEventArgs(partialView, false, partialView.Alias, -1), this); + CreatedPartialView.RaiseEvent(new NewEventArgs(partialView, false, partialView.Alias, -1), this); } - Audit.Add(AuditTypes.Save, string.Format("Save PartialView performed by user"), userId, -1); + Audit.Add(AuditTypes.Save, string.Format("Save {0} performed by user", partialViewType), userId, -1); - return Attempt.Succeed(partialView); + return Attempt.Succeed(partialView); } - internal Attempt CreatePartialViewMacro(PartialView partialView, bool createMacro, string snippetName = null, int userId = 0) + public bool DeletePartialView(string path, int userId = 0) { - if (CreatingPartialView.IsRaisedEventCancelled(new NewEventArgs(partialView, true, partialView.Alias, -1), this)) - return Attempt.Fail(); - - if (snippetName.IsNullOrWhiteSpace() == false) - { - //create the file - var snippetPathAttempt = TryGetSnippetPath(snippetName); - if (snippetPathAttempt.Success == false) - { - throw new InvalidOperationException("Could not load snippet with name " + snippetName); - } - - using (var snippetFile = new StreamReader(System.IO.File.OpenRead(snippetPathAttempt.Result))) - { - var snippetContent = snippetFile.ReadToEnd().Trim(); - - //strip the @inherits if it's there - snippetContent = StripPartialViewHeader(snippetContent); - - var content = string.Format("{0}{1}{2}", PartialViewMacroHeader, Environment.NewLine, snippetContent); - partialView.Content = content; - } - } - - var uow = _fileUowProvider.GetUnitOfWork(); - var duow = _dataUowProvider.GetUnitOfWork(); - using (var repository = _repositoryFactory.CreatePartialViewMacroRepository(uow, duow)) - { - repository.AddOrUpdate(partialView); - - if (createMacro) - { - var name = Path.GetFileNameWithoutExtension(partialView.Path) - .SplitPascalCasing() - .ToFirstUpperInvariant() - .ToSafeAlias(false); - - repository.AddOrUpdate(new Macro(name, name) { ScriptPath = partialView.Path }); - } - - //commit both - ensure that the macro is created if one was added - uow.Commit(); - duow.Commit(); - - CreatedPartialView.RaiseEvent(new NewEventArgs(partialView, false, partialView.Alias, -1), this); - } - - Audit.Add(AuditTypes.Save, string.Format("Save PartialViewMacro performed by user"), userId, -1); - - return Attempt.Succeed(partialView); + return DeletePartialViewMacro(path, PartialViewType.PartialView, userId); } - internal bool DeletePartialView(string path, int userId = 0) + public bool DeletePartialViewMacro(string path, int userId = 0) + { + return DeletePartialViewMacro(path, PartialViewType.PartialViewMacro, userId); + } + + private bool DeletePartialViewMacro(string path, PartialViewType partialViewType, int userId = 0) { var uow = _fileUowProvider.GetUnitOfWork(); - using (var repository = _repositoryFactory.CreatePartialViewRepository(uow)) + + IPartialViewRepository repository; + switch (partialViewType) + { + case PartialViewType.PartialView: + repository = _repositoryFactory.CreatePartialViewRepository(uow); + break; + case PartialViewType.PartialViewMacro: + default: + repository = _repositoryFactory.CreatePartialViewMacroRepository(uow); + break; + } + + using (repository) { var partialView = repository.Get(path); if (partialView == null) return true; - if (DeletingPartialView.IsRaisedEventCancelled(new DeleteEventArgs(partialView), this)) + if (DeletingPartialView.IsRaisedEventCancelled(new DeleteEventArgs(partialView), this)) return false; repository.Delete(partialView); uow.Commit(); - DeletedPartialView.RaiseEvent(new DeleteEventArgs(partialView, false), this); + DeletedPartialView.RaiseEvent(new DeleteEventArgs(partialView, false), this); - Audit.Add(AuditTypes.Delete, string.Format("Delete PartialView performed by user"), userId, -1); + Audit.Add(AuditTypes.Delete, string.Format("Delete {0} performed by user", partialViewType), userId, -1); } return true; } - internal bool DeletePartialViewMacro(string path, int userId = 0) + public Attempt SavePartialView(IPartialView partialView, int userId = 0) { - var uow = _fileUowProvider.GetUnitOfWork(); - var duow = _dataUowProvider.GetUnitOfWork(); - using (var repository = _repositoryFactory.CreatePartialViewMacroRepository(uow, duow)) - { - var partialView = repository.Get(path); - if (partialView == null) - return true; - - if (DeletingPartialView.IsRaisedEventCancelled(new DeleteEventArgs(partialView), this)) - return false; - - repository.Delete(partialView); - - //commit both (though in the case of deleting, there's no db changes) - uow.Commit(); - duow.Commit(); - - DeletedPartialView.RaiseEvent(new DeleteEventArgs(partialView, false), this); - - Audit.Add(AuditTypes.Delete, string.Format("Delete PartialViewMacro performed by user"), userId, -1); - } - - return true; - + return SavePartialView(partialView, PartialViewType.PartialView, userId); } - internal Attempt SavePartialView(PartialView partialView, int userId = 0) + public Attempt SavePartialViewMacro(IPartialView partialView, int userId = 0) { - if (SavingPartialView.IsRaisedEventCancelled(new SaveEventArgs(partialView), this)) - return Attempt.Fail(); + return SavePartialView(partialView, PartialViewType.PartialViewMacro, userId); + } + + private Attempt SavePartialView(IPartialView partialView, PartialViewType partialViewType, int userId = 0) + { + if (SavingPartialView.IsRaisedEventCancelled(new SaveEventArgs(partialView), this)) + return Attempt.Fail(); var uow = _fileUowProvider.GetUnitOfWork(); - using (var repository = _repositoryFactory.CreatePartialViewRepository(uow)) + + IPartialViewRepository repository; + switch (partialViewType) + { + case PartialViewType.PartialView: + repository = _repositoryFactory.CreatePartialViewRepository(uow); + break; + case PartialViewType.PartialViewMacro: + default: + repository = _repositoryFactory.CreatePartialViewMacroRepository(uow); + break; + } + + using (repository) { repository.AddOrUpdate(partialView); uow.Commit(); - SavedPartialView.RaiseEvent(new SaveEventArgs(partialView, false), this); + SavedPartialView.RaiseEvent(new SaveEventArgs(partialView, false), this); } - Audit.Add(AuditTypes.Save, string.Format("Save PartialView performed by user"), userId, -1); + Audit.Add(AuditTypes.Save, string.Format("Save {0} performed by user", partialViewType), userId, -1); - ////NOTE: I've left the below here just for informational purposes. If we save a file this way, then the UTF8 - //// BOM mucks everything up, strangely, if we use WriteAllText everything is ok! - //// http://issues.umbraco.org/issue/U4-2118 - ////using (var sw = System.IO.File.CreateText(savePath)) - ////{ - //// sw.Write(val); - ////} - - //System.IO.File.WriteAllText(partialView.Path, partialView.Content, Encoding.UTF8); - ////deletes the old file - //if (partialView.FileName != partialView.OldFileName) - //{ - // // Create a new PartialView class so that we can set the FileName of the file that needs deleting - // var deletePartial = partialView; - // deletePartial.FileName = partialView.OldFileName; - // DeletePartialView(deletePartial, userId); - //} - - //SavedPartialView.RaiseEvent(new SaveEventArgs(partialView), this); - - return Attempt.Succeed(partialView); - } - - internal Attempt SavePartialViewMacro(PartialView partialView, int userId = 0) - { - if (SavingPartialView.IsRaisedEventCancelled(new SaveEventArgs(partialView), this)) - return Attempt.Fail(); - - var uow = _fileUowProvider.GetUnitOfWork(); - var duow = _dataUowProvider.GetUnitOfWork(); - using (var repository = _repositoryFactory.CreatePartialViewMacroRepository(uow, duow)) - { - repository.AddOrUpdate(partialView); - - //commit both (though in the case of updating, there's no db changes) - uow.Commit(); - duow.Commit(); - - SavedPartialView.RaiseEvent(new SaveEventArgs(partialView, false), this); - } - - Audit.Add(AuditTypes.Save, string.Format("Save PartialViewMacro performed by user"), userId, -1); - - ////NOTE: I've left the below here just for informational purposes. If we save a file this way, then the UTF8 - //// BOM mucks everything up, strangely, if we use WriteAllText everything is ok! - //// http://issues.umbraco.org/issue/U4-2118 - ////using (var sw = System.IO.File.CreateText(savePath)) - ////{ - //// sw.Write(val); - ////} - - //System.IO.File.WriteAllText(partialView.Path, partialView.Content, Encoding.UTF8); - ////deletes the old file - //if (partialView.FileName != partialView.OldFileName) - //{ - // // Create a new PartialView class so that we can set the FileName of the file that needs deleting - // var deletePartial = partialView; - // deletePartial.FileName = partialView.OldFileName; - // DeletePartialView(deletePartial, userId); - //} - - //SavedPartialView.RaiseEvent(new SaveEventArgs(partialView), this); + SavedPartialView.RaiseEvent(new SaveEventArgs(partialView), this); return Attempt.Succeed(partialView); } @@ -686,7 +620,14 @@ namespace Umbraco.Core.Services return System.IO.File.Exists(snippetPath) ? Attempt.Succeed(snippetPath) : Attempt.Fail(); - } + } + + private enum PartialViewType + { + PartialView, + PartialViewMacro + } + #endregion //TODO Method to change name and/or alias of view/masterpage template @@ -755,32 +696,32 @@ namespace Umbraco.Core.Services /// /// Occurs before Save /// - internal static event TypedEventHandler> SavingPartialView; + public static event TypedEventHandler> SavingPartialView; /// /// Occurs after Save /// - internal static event TypedEventHandler> SavedPartialView; + public static event TypedEventHandler> SavedPartialView; /// /// Occurs before Create /// - internal static event TypedEventHandler> CreatingPartialView; + public static event TypedEventHandler> CreatingPartialView; /// /// Occurs after Create /// - internal static event TypedEventHandler> CreatedPartialView; + public static event TypedEventHandler> CreatedPartialView; /// /// Occurs before Delete /// - internal static event TypedEventHandler> DeletingPartialView; + public static event TypedEventHandler> DeletingPartialView; /// /// Occurs after Delete /// - internal static event TypedEventHandler> DeletedPartialView; + public static event TypedEventHandler> DeletedPartialView; #endregion diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index 916294937f..9500cbfecf 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -52,6 +52,14 @@ namespace Umbraco.Core.Services /// Id of the user issueing the save void Save(IEnumerable dataTypeDefinitions, int userId = 0); + /// + /// Saves a collection of + /// + /// to save + /// Id of the user issueing the save + /// Boolean indicating whether or not to raise events + void Save(IEnumerable dataTypeDefinitions, int userId, bool raiseEvents); + /// /// Deletes an /// diff --git a/src/Umbraco.Core/Services/IFileService.cs b/src/Umbraco.Core/Services/IFileService.cs index 9bcce63373..924fb0c093 100644 --- a/src/Umbraco.Core/Services/IFileService.cs +++ b/src/Umbraco.Core/Services/IFileService.cs @@ -8,6 +8,18 @@ namespace Umbraco.Core.Services /// public interface IFileService : IService { + IEnumerable GetPartialViewSnippetNames(params string[] filterNames); + void DeletePartialViewFolder(string folderPath); + void DeletePartialViewMacroFolder(string folderPath); + IPartialView GetPartialView(string path); + IPartialView GetPartialViewMacro(string path); + Attempt CreatePartialView(IPartialView partialView, string snippetName = null, int userId = 0); + Attempt CreatePartialViewMacro(IPartialView partialView, string snippetName = null, int userId = 0); + bool DeletePartialView(string path, int userId = 0); + bool DeletePartialViewMacro(string path, int userId = 0); + Attempt SavePartialView(IPartialView partialView, int userId = 0); + Attempt SavePartialViewMacro(IPartialView partialView, int userId = 0); + /// /// Gets a list of all objects /// diff --git a/src/Umbraco.Core/Services/ILocalizedTextService.cs b/src/Umbraco.Core/Services/ILocalizedTextService.cs new file mode 100644 index 0000000000..de95f24efa --- /dev/null +++ b/src/Umbraco.Core/Services/ILocalizedTextService.cs @@ -0,0 +1,37 @@ +using System.Collections; +using System.Collections.Generic; +using System.Globalization; + +namespace Umbraco.Core.Services +{ + /// + /// The entry point to localize any key in the text storage source for a given culture + /// + /// + /// This class is created to be as simple as possible so that it can be replaced very easily, + /// all other methods are extension methods that simply call the one underlying method in this class + /// + public interface ILocalizedTextService + { + /// + /// Localize a key with variables + /// + /// + /// + /// This can be null + /// + string Localize(string key, CultureInfo culture, IDictionary tokens = null); + + /// + /// Returns all key/values in storage for the given culture + /// + /// + IDictionary GetAllStoredValues(CultureInfo culture); + + /// + /// Returns a list of all currently supported cultures + /// + /// + IEnumerable GetSupportedCultures(); + } +} diff --git a/src/Umbraco.Core/Services/IUserService.cs b/src/Umbraco.Core/Services/IUserService.cs index 559bf100d0..5a77956931 100644 --- a/src/Umbraco.Core/Services/IUserService.cs +++ b/src/Umbraco.Core/Services/IUserService.cs @@ -53,6 +53,14 @@ namespace Umbraco.Core.Services /// Alias of the section to remove void DeleteSectionFromAllUsers(string sectionAlias); + /// + /// Add a specific section to all users or those specified as parameters + /// + /// This is useful when a new section is created to allow specific users accessing it + /// Alias of the section to add + /// Specifiying nothing will add the section to all user + void AddSectionToAllUsers(string sectionAlias, params int[] userIds); + /// /// Get permissions set for a user and optional node ids /// @@ -117,4 +125,4 @@ namespace Umbraco.Core.Services #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Services/LocalizedTextService.cs b/src/Umbraco.Core/Services/LocalizedTextService.cs new file mode 100644 index 0000000000..c9d05f3a3b --- /dev/null +++ b/src/Umbraco.Core/Services/LocalizedTextService.cs @@ -0,0 +1,232 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Xml; +using System.Xml.Linq; +using System.Xml.XPath; +using Umbraco.Core.Logging; + +namespace Umbraco.Core.Services +{ + //TODO: Convert all of this over to Niels K's localization framework one day + + public class LocalizedTextService : ILocalizedTextService + { + private readonly IDictionary>> _dictionarySource; + private readonly IDictionary> _xmlSource; + + /// + /// Initializes with a file sources instance + /// + /// + public LocalizedTextService(LocalizedTextServiceFileSources fileSources) + { + _xmlSource = fileSources.GetXmlSources(); + } + + /// + /// Initializes with an XML source + /// + /// + public LocalizedTextService(IDictionary> source) + { + if (source == null) throw new ArgumentNullException("source"); + _xmlSource = source; + } + + /// + /// Initializes with a source of a dictionary of culture -> areas -> sub dictionary of keys/values + /// + /// + public LocalizedTextService(IDictionary>> source) + { + if (source == null) throw new ArgumentNullException("source"); + _dictionarySource = source; + } + + public string Localize(string key, CultureInfo culture, IDictionary tokens = null) + { + Mandate.ParameterNotNull(culture, "culture"); + + //This is what the legacy ui service did + if (string.IsNullOrEmpty(key)) + return string.Empty; + + var keyParts = key.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries); + var area = keyParts.Length > 1 ? keyParts[0] : null; + var alias = keyParts.Length > 1 ? keyParts[1] : keyParts[0]; + + if (_xmlSource != null) + { + return GetFromXmlSource(culture, area, alias, tokens); + } + else + { + return GetFromDictionarySource(culture, area, alias, tokens); + } + } + + /// + /// Returns all key/values in storage for the given culture + /// + /// + public IDictionary GetAllStoredValues(CultureInfo culture) + { + if (culture == null) throw new ArgumentNullException("culture"); + + var result = new Dictionary(); + + if (_xmlSource != null) + { + if (_xmlSource.ContainsKey(culture) == false) + { + LogHelper.Warn("The culture specified {0} was not found in any configured sources for this service", () => culture); + return result; + } + + //convert all areas + keys to a single key with a '/' + var areas = _xmlSource[culture].Value.XPathSelectElements("//area"); + foreach (var area in areas) + { + var keys = area.XPathSelectElements("./key"); + foreach (var key in keys) + { + var dictionaryKey = string.Format("{0}/{1}", (string) area.Attribute("alias"), (string) key.Attribute("alias")); + //there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files + if (result.ContainsKey(dictionaryKey) == false) + { + result.Add(dictionaryKey, key.Value); + } + } + } + } + else + { + if (_dictionarySource.ContainsKey(culture) == false) + { + LogHelper.Warn("The culture specified {0} was not found in any configured sources for this service", () => culture); + return result; + } + + //convert all areas + keys to a single key with a '/' + foreach (var area in _dictionarySource[culture]) + { + foreach (var key in area.Value) + { + var dictionaryKey = string.Format("{0}/{1}", area.Key, key.Key); + //i don't think it's possible to have duplicates because we're dealing with a dictionary in the first place, but we'll double check here just in case. + if (result.ContainsKey(dictionaryKey) == false) + { + result.Add(dictionaryKey, key.Value); + } + } + } + } + + return result; + } + + /// + /// Returns a list of all currently supported cultures + /// + /// + public IEnumerable GetSupportedCultures() + { + return _xmlSource != null ? _xmlSource.Keys : _dictionarySource.Keys; + } + + private string GetFromDictionarySource(CultureInfo culture, string area, string key, IDictionary tokens) + { + if (_dictionarySource.ContainsKey(culture) == false) + { + LogHelper.Warn("The culture specified {0} was not found in any configured sources for this service", () => culture); + return "[" + key + "]"; + } + + var cultureSource = _dictionarySource[culture]; + + string found; + if (area.IsNullOrWhiteSpace()) + { + found = cultureSource + .SelectMany(x => x.Value) + .Where(keyvals => keyvals.Key.InvariantEquals(key)) + .Select(x => x.Value) + .FirstOrDefault(); + } + else + { + found = cultureSource + .Where(areas => areas.Key.InvariantEquals(area)) + .SelectMany(a => a.Value) + .Where(keyvals => keyvals.Key.InvariantEquals(key)) + .Select(x => x.Value) + .FirstOrDefault(); + } + + if (found != null) + { + return ParseTokens(found, tokens); + } + + //NOTE: Based on how legacy works, the default text does not contain the area, just the key + return "[" + key + "]"; + } + + private string GetFromXmlSource(CultureInfo culture, string area, string key, IDictionary tokens) + { + if (_xmlSource.ContainsKey(culture) == false) + { + LogHelper.Warn("The culture specified {0} was not found in any configured sources for this service", () => culture); + return "[" + key + "]"; + } + + var cultureSource = _xmlSource[culture].Value; + + var xpath = area.IsNullOrWhiteSpace() + ? string.Format("//key [@alias = '{0}']", key) + : string.Format("//area [@alias = '{0}']/key [@alias = '{1}']", area, key); + + var found = cultureSource.XPathSelectElement(xpath); + + if (found != null) + { + return ParseTokens(found.Value, tokens); + } + + //NOTE: Based on how legacy works, the default text does not contain the area, just the key + return "[" + key + "]"; + } + + /// + /// Parses the tokens in the value + /// + /// + /// + /// + /// + /// This is based on how the legacy ui localized text worked, each token was just a sequential value delimited with a % symbol. + /// For example: hello %0%, you are %1% ! + /// + /// Since we're going to continue using the same language files for now, the token system needs to remain the same. With our new service + /// we support a dictionary which means in the future we can really have any sort of token system. + /// Currently though, the token key's will need to be an integer and sequential - though we aren't going to throw exceptions if that is not the case. + /// + internal string ParseTokens(string value, IDictionary tokens) + { + if (tokens == null || tokens.Any() == false) + { + return value; + } + + foreach (var token in tokens) + { + value = value.Replace(string.Format("{0}{1}{0}", "%", token.Key), token.Value); + } + + return value; + } + + } +} diff --git a/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs b/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs new file mode 100644 index 0000000000..6ed20b2a45 --- /dev/null +++ b/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace Umbraco.Core.Services +{ + + /// + /// Extension methods for ILocalizedTextService + /// + public static class LocalizedTextServiceExtensions + { + /// + /// Localize a key without any variables + /// + /// + /// + /// + /// + /// + public static string Localize(this ILocalizedTextService manager, string key, CultureInfo culture, string[] tokens) + { + return manager.Localize(key, culture, ConvertToDictionaryVars(tokens)); + } + + /// + /// Convert an array of strings to a dictionary of indicies -> values + /// + /// + /// + internal static IDictionary ConvertToDictionaryVars(string[] variables) + { + if (variables == null) return null; + if (variables.Any() == false) return null; + + return variables.Select((s, i) => new { index = i.ToString(CultureInfo.InvariantCulture), value = s }) + .ToDictionary(keyvals => keyvals.index, keyvals => keyvals.value); + } + } +} diff --git a/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs b/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs new file mode 100644 index 0000000000..30e43a694c --- /dev/null +++ b/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Xml.Linq; +using Umbraco.Core.Cache; +using Umbraco.Core.Logging; + +namespace Umbraco.Core.Services +{ + /// + /// Exposes the XDocument sources from files for the default localization text service and ensure caching is taken care of + /// + public class LocalizedTextServiceFileSources + { + private readonly IRuntimeCacheProvider _cache; + private readonly DirectoryInfo _fileSourceFolder; + + public LocalizedTextServiceFileSources(IRuntimeCacheProvider cache, DirectoryInfo fileSourceFolder) + { + if (cache == null) throw new ArgumentNullException("cache"); + if (fileSourceFolder == null) throw new ArgumentNullException("fileSourceFolder"); + _cache = cache; + + if (fileSourceFolder.Exists == false) + { + LogHelper.Warn("The folder does not exist: {0}, therefore no sources will be discovered", () => fileSourceFolder.FullName); + } + else + { + _fileSourceFolder = fileSourceFolder; + } + } + + /// + /// returns all xml sources for all culture files found in the folder + /// + /// + public IDictionary> GetXmlSources() + { + var result = new Dictionary>(); + + if (_fileSourceFolder == null) return result; + + foreach (var fileInfo in _fileSourceFolder.GetFiles("*.xml")) + { + var localCopy = fileInfo; + var filename = Path.GetFileNameWithoutExtension(localCopy.FullName).Replace("_", "-"); + var culture = CultureInfo.GetCultureInfo(filename); + //get the lazy value from cache + result.Add(culture, new Lazy(() => _cache.GetCacheItem( + string.Format("{0}-{1}", typeof (LocalizedTextServiceFileSources).Name, culture.TwoLetterISOLanguageName), () => + { + using (var fs = localCopy.OpenRead()) + { + return XDocument.Load(fs); + } + }, isSliding: true, timeout: TimeSpan.FromMinutes(10), dependentFiles: new[] {localCopy.FullName}))); + } + return result; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/MacroService.cs b/src/Umbraco.Core/Services/MacroService.cs index 376d33503b..9e7dc66b59 100644 --- a/src/Umbraco.Core/Services/MacroService.cs +++ b/src/Umbraco.Core/Services/MacroService.cs @@ -200,5 +200,7 @@ namespace Umbraco.Core.Services /// public static event TypedEventHandler> Saved; #endregion + + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index f2c5526e8d..279ec9f07f 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Text.RegularExpressions; using System.Threading; using System.Xml.Linq; using Umbraco.Core.Auditing; @@ -413,7 +414,7 @@ namespace Umbraco.Core.Services { var query = Query.Builder; //if the id is -1, then just get all - if (id > 0) + if (id != -1) { query.Where(x => x.ParentId == id); } @@ -443,7 +444,7 @@ namespace Umbraco.Core.Services var query = Query.Builder; //if the id is -1, then just get all - if (id > 0) + if (id != -1) { query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); } @@ -567,28 +568,38 @@ namespace Umbraco.Core.Services public IMedia GetMediaByPath(string mediaPath) { var umbracoFileValue = mediaPath; - var isResized = mediaPath.Contains("_") && mediaPath.Contains("x"); + const string Pattern = ".*[_][0-9]+[x][0-9]+[.].*"; + var isResized = Regex.IsMatch(mediaPath, Pattern); // If the image has been resized we strip the "_403x328" of the original "/media/1024/koala_403x328.jpg" url. if (isResized) { - var underscoreIndex = mediaPath.LastIndexOf('_'); var dotIndex = mediaPath.LastIndexOf('.'); umbracoFileValue = string.Concat(mediaPath.Substring(0, underscoreIndex), mediaPath.Substring(dotIndex)); } - var sql = new Sql() - .Select("*") - .From() - .InnerJoin() - .On(left => left.PropertyTypeId, right => right.Id) - .Where(x => x.Alias == "umbracoFile") - .Where(x => x.VarChar == umbracoFileValue); + Func createSql = url => new Sql().Select("*") + .From() + .InnerJoin() + .On(left => left.PropertyTypeId, right => right.Id) + .Where(x => x.Alias == "umbracoFile") + .Where(x => x.VarChar == url); + + var sql = createSql(umbracoFileValue); using (var uow = _uowProvider.GetUnitOfWork()) { var propertyDataDto = uow.Database.Fetch(sql).FirstOrDefault(); + + // If the stripped-down url returns null, we try again with the original url. + // Previously, the function would fail on e.g. "my_x_image.jpg" + if (propertyDataDto == null) + { + sql = createSql(mediaPath); + propertyDataDto = uow.Database.Fetch(sql).FirstOrDefault(); + } + return propertyDataDto == null ? null : GetById(propertyDataDto.NodeId); } } diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index 64b5f7629b..579da33a9d 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -76,7 +76,7 @@ namespace Umbraco.Core.Services public XElement Export(IContent content, bool deep = false, bool raiseEvents = true) { var nodeName = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema ? "node" : content.ContentType.Alias.ToSafeAliasWithForcingCheck(); - + if (raiseEvents) { if (ExportingContent.IsRaisedEventCancelled(new ExportEventArgs(content, nodeName), this)) @@ -86,13 +86,13 @@ namespace Umbraco.Core.Services var exporter = new EntityXmlSerializer(); var xml = exporter.Serialize(_contentService, _dataTypeService, _userService, content, deep); - if(raiseEvents) + if (raiseEvents) ExportedContent.RaiseEvent(new ExportEventArgs(content, xml, false), this); return xml; } - + /// /// Imports and saves package xml as @@ -109,7 +109,7 @@ namespace Umbraco.Core.Services if (ImportingContent.IsRaisedEventCancelled(new ImportEventArgs(element), this)) return Enumerable.Empty(); } - + var name = element.Name.LocalName; if (name.Equals("DocumentSet")) { @@ -122,7 +122,7 @@ namespace Umbraco.Core.Services if (contents.Any()) _contentService.Save(contents, userId); - if(raiseEvents) + if (raiseEvents) ImportedContent.RaiseEvent(new ImportEventArgs(contents, element, false), this); return contents; } @@ -136,7 +136,7 @@ namespace Umbraco.Core.Services if (contents.Any()) _contentService.Save(contents, userId); - if(raiseEvents) + if (raiseEvents) ImportedContent.RaiseEvent(new ImportEventArgs(contents, element, false), this); return contents; } @@ -250,7 +250,7 @@ namespace Umbraco.Core.Services //TODO: We need to refactor this so the packager isn't making direct db calls for an 'edge' case var database = ApplicationContext.Current.DatabaseContext.Database; - var dtos = database.Fetch("WHERE datatypeNodeId = @Id", new {Id = propertyType.DataTypeDefinitionId}); + var dtos = database.Fetch("WHERE datatypeNodeId = @Id", new { Id = propertyType.DataTypeDefinitionId }); var propertyValueList = new List(); foreach (var preValue in propertyValue.Split(',')) @@ -332,11 +332,62 @@ namespace Umbraco.Core.Services } _importedContentTypes = new Dictionary(); - var documentTypes = name.Equals("DocumentTypes") + var unsortedDocumentTypes = name.Equals("DocumentTypes") ? (from doc in element.Elements("DocumentType") select doc).ToList() : new List { element }; - //NOTE it might be an idea to sort the doctype XElements based on dependencies - //before creating the doc types - should also allow for a better structure/inheritance support. + + //When you are importing a single doc type we have to assume that the depedencies are already there. + //Otherwise something like uSync won't work. + var fields = new List>(); + var isSingleDocTypeImport = unsortedDocumentTypes.Count == 1; + if (isSingleDocTypeImport == false) + { + //NOTE Here we sort the doctype XElements based on dependencies + //before creating the doc types - this should also allow for a better structure/inheritance support. + foreach (var documentType in unsortedDocumentTypes) + { + var elementCopy = documentType; + var infoElement = elementCopy.Element("Info"); + var dependencies = new List(); + + //Add the Master as a dependency + if (elementCopy.Element("Master") != null && + string.IsNullOrEmpty(elementCopy.Element("Master").Value) == false) + { + dependencies.Add(elementCopy.Element("Master").Value); + } + + //Add compositions as dependencies + var compositionsElement = infoElement.Element("Compositions"); + if (compositionsElement != null && compositionsElement.HasElements) + { + var compositions = compositionsElement.Elements("Composition"); + if (compositions.Any()) + { + foreach (var composition in compositions) + { + dependencies.Add(composition.Value); + } + } + } + + var field = new TopologicalSorter.DependencyField + { + Alias = infoElement.Element("Alias").Value, + Item = new Lazy(() => elementCopy), + DependsOn = dependencies.ToArray() + }; + + fields.Add(field); + } + } + + //Sorting the Document Types based on dependencies - if its not a single doc type import ref. #U4-5921 + var documentTypes = isSingleDocTypeImport + ? unsortedDocumentTypes.ToList() + : TopologicalSorter.GetSortedItems(fields).ToList(); + + //Iterate the sorted document types and create them as IContentType objects foreach (var documentType in documentTypes) { var alias = documentType.Element("Info").Element("Alias").Value; @@ -349,9 +400,12 @@ namespace Umbraco.Core.Services } } + //Save the newly created/updated IContentType objects var list = _importedContentTypes.Select(x => x.Value).ToList(); _contentTypeService.Save(list, userId); + //Now we can finish the import by updating the 'structure', + //which requires the doc types to be saved/available in the db if (importStructure) { var updatedContentTypes = new List(); @@ -392,15 +446,10 @@ namespace Umbraco.Core.Services : _contentTypeService.GetContentType(masterAlias); } + var alias = infoElement.Element("Alias").Value; var contentType = parent == null - ? new ContentType(-1) - { - Alias = infoElement.Element("Alias").Value - } - : new ContentType(parent) - { - Alias = infoElement.Element("Alias").Value - }; + ? new ContentType(-1) { Alias = alias } + : new ContentType(parent, alias); if (parent != null) contentType.AddContentType(parent); @@ -417,15 +466,47 @@ namespace Umbraco.Core.Services contentType.Icon = infoElement.Element("Icon").Value; contentType.Thumbnail = infoElement.Element("Thumbnail").Value; contentType.Description = infoElement.Element("Description").Value; + //NOTE AllowAtRoot is a new property in the package xml so we need to verify it exists before using it. var allowAtRoot = infoElement.Element("AllowAtRoot"); if (allowAtRoot != null) contentType.AllowedAsRoot = allowAtRoot.Value.InvariantEquals("true"); + //NOTE IsListView is a new property in the package xml so we need to verify it exists before using it. var isListView = infoElement.Element("IsListView"); if (isListView != null) contentType.IsContainer = isListView.Value.InvariantEquals("true"); + //Name of the master corresponds to the parent and we need to ensure that the Parent Id is set + var masterElement = infoElement.Element("Master"); + if (masterElement != null) + { + var masterAlias = masterElement.Value; + IContentType parent = _importedContentTypes.ContainsKey(masterAlias) + ? _importedContentTypes[masterAlias] + : _contentTypeService.GetContentType(masterAlias); + + contentType.SetLazyParentId(new Lazy(() => parent.Id)); + } + + //Update Compositions on the ContentType to ensure that they are as is defined in the package xml + var compositionsElement = infoElement.Element("Compositions"); + if (compositionsElement != null && compositionsElement.HasElements) + { + var compositions = compositionsElement.Elements("Composition"); + if (compositions.Any()) + { + foreach (var composition in compositions) + { + var compositionAlias = composition.Value; + var compositionContentType = _importedContentTypes.ContainsKey(compositionAlias) + ? _importedContentTypes[compositionAlias] + : _contentTypeService.GetContentType(compositionAlias); + var added = contentType.AddContentType(compositionContentType); + } + } + } + UpdateContentTypesAllowedTemplates(contentType, infoElement.Element("AllowedTemplates"), defaultTemplateElement); UpdateContentTypesTabs(contentType, documentType.Element("Tabs")); UpdateContentTypesProperties(contentType, documentType.Element("GenericProperties")); @@ -492,13 +573,13 @@ namespace Umbraco.Core.Services { contentType.AddPropertyGroup(caption); - int sortOrder; - if(tab.Element("SortOrder") != null && - int.TryParse(tab.Element("SortOrder").Value, out sortOrder)) - { - // Override the sort order with the imported value - contentType.PropertyGroups[caption].SortOrder = sortOrder; - } + } + + int sortOrder; + if (tab.Element("SortOrder") != null && int.TryParse(tab.Element("SortOrder").Value, out sortOrder)) + { + // Override the sort order with the imported value + contentType.PropertyGroups[caption].SortOrder = sortOrder; } } } @@ -538,7 +619,7 @@ namespace Umbraco.Core.Services dataTypeDefinition = dataTypeDefinitions.First(); } } - + // For backwards compatibility, if no datatype with that ID can be found, we're letting this fail silently. // This means that the property will not be created. if (dataTypeDefinition == null) @@ -738,9 +819,15 @@ namespace Umbraco.Core.Services var list = dataTypes.Select(x => x.Value).ToList(); if (list.Any()) { - _dataTypeService.Save(list, userId); + //NOTE: As long as we have to deal with the two types of PreValue lists (with/without Keys) + //this is a bit of a pain to handle while ensuring that the imported DataTypes has PreValues + //place when triggering the save event. - SavePrevaluesFromXml(list, dataTypeElements); + _dataTypeService.Save(list, userId, false);//Save without raising events + + SavePrevaluesFromXml(list, dataTypeElements);//Save the PreValues for the current list of DataTypes + + _dataTypeService.Save(list, userId, true);//Re-save and raise events } if (raiseEvents) @@ -760,15 +847,15 @@ namespace Umbraco.Core.Services var dataTypeDefinition = dataTypes.First(x => x.Name == dataTypeDefinitionName); var valuesWithoutKeys = prevaluesElement.Elements("PreValue") - .Where(x => ((string) x.Attribute("Alias")).IsNullOrWhiteSpace()) + .Where(x => ((string)x.Attribute("Alias")).IsNullOrWhiteSpace()) .Select(x => x.Attribute("Value").Value); var valuesWithKeys = prevaluesElement.Elements("PreValue") - .Where(x => ((string) x.Attribute("Alias")).IsNullOrWhiteSpace() == false) + .Where(x => ((string)x.Attribute("Alias")).IsNullOrWhiteSpace() == false) .ToDictionary( - key => (string) key.Attribute("Alias"), - val => new PreValue((string) val.Attribute("Value"))); - + key => (string)key.Attribute("Alias"), + val => new PreValue((string)val.Attribute("Value"))); + //save the values with keys _dataTypeService.SavePreValues(dataTypeDefinition, valuesWithKeys); @@ -815,7 +902,7 @@ namespace Umbraco.Core.Services var exporter = new EntityXmlSerializer(); var xml = exporter.Serialize(dictionaryItem); - + if (includeChildren) { var children = _localizationService.GetDictionaryItemChildren(dictionaryItem.Key); @@ -1089,7 +1176,7 @@ namespace Umbraco.Core.Services dontRender = bool.Parse(dontRenderElement.Value); } - var existingMacro = _macroService.GetByAlias(macroAlias) as Macro; + var existingMacro = _macroService.GetByAlias(macroAlias) as Macro; var macro = existingMacro ?? new Macro(macroAlias, macroName, controlType, controlAssembly, xsltPath, scriptPath, cacheByPage, cacheByMember, dontRender, useInEditor, cacheDuration); @@ -1184,7 +1271,7 @@ namespace Umbraco.Core.Services public XElement Export(IMedia media, bool deep = false, bool raiseEvents = true) { var nodeName = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema ? "node" : media.ContentType.Alias.ToSafeAliasWithForcingCheck(); - + if (raiseEvents) { if (ExportingMedia.IsRaisedEventCancelled(new ExportEventArgs(media, nodeName), this)) @@ -1194,7 +1281,7 @@ namespace Umbraco.Core.Services var exporter = new EntityXmlSerializer(); var xml = exporter.Serialize(_mediaService, _dataTypeService, _userService, media, deep); - if(raiseEvents) + if (raiseEvents) ExportedMedia.RaiseEvent(new ExportEventArgs(media, xml, false), this); return xml; @@ -1308,7 +1395,7 @@ namespace Umbraco.Core.Services if (templates.Any()) _fileService.SaveTemplate(templates, userId); - if(raiseEvents) + if (raiseEvents) ImportedTemplate.RaiseEvent(new ImportEventArgs(templates, element, false), this); return templates; @@ -1326,10 +1413,10 @@ namespace Umbraco.Core.Services IEnumerable styleSheets = Enumerable.Empty(); - if(element.Elements().Any()) + if (element.Elements().Any()) throw new NotImplementedException("This needs to be implimentet"); - + if (raiseEvents) ImportingStylesheets.RaiseEvent(new ImportEventArgs(styleSheets, element, false), this); @@ -1454,7 +1541,7 @@ namespace Umbraco.Core.Services /// public static event TypedEventHandler> ImportedContent; - + public static event TypedEventHandler> ExportingContent; /// @@ -1581,7 +1668,7 @@ namespace Umbraco.Core.Services /// Occurs before Importing Stylesheets /// public static event TypedEventHandler> ImportingStylesheets; - + /// /// Occurs after Template is Imported and Saved /// @@ -1596,7 +1683,7 @@ namespace Umbraco.Core.Services /// Occurs after Template is Exported to Xml /// public static event TypedEventHandler> ExportedTemplate; - + /// /// Occurs before Importing umbraco package /// diff --git a/src/Umbraco.Core/Services/SectionService.cs b/src/Umbraco.Core/Services/SectionService.cs index 1a3568cff3..fd9519bc1c 100644 --- a/src/Umbraco.Core/Services/SectionService.cs +++ b/src/Umbraco.Core/Services/SectionService.cs @@ -140,6 +140,8 @@ namespace Umbraco.Core.Services } } } + + _isInitialized = true; } } diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index 79280b371f..6dabf8b0b6 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -1,4 +1,6 @@ using System; +using System.IO; +using Umbraco.Core.IO; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Publishing; @@ -12,6 +14,7 @@ namespace Umbraco.Core.Services /// public class ServiceContext { + private Lazy _localizedTextService; private Lazy _tagService; private Lazy _contentService; private Lazy _userService; @@ -52,6 +55,7 @@ namespace Umbraco.Core.Services /// /// /// + /// public ServiceContext( IContentService contentService, IMediaService mediaService, @@ -69,8 +73,10 @@ namespace Umbraco.Core.Services ISectionService sectionService, IApplicationTreeService treeService, ITagService tagService, - INotificationService notificationService) + INotificationService notificationService, + ILocalizedTextService localizedTextService) { + _localizedTextService = new Lazy(() => localizedTextService); _tagService = new Lazy(() => tagService); _contentService = new Lazy(() => contentService); _mediaService = new Lazy(() => mediaService); @@ -119,6 +125,12 @@ namespace Umbraco.Core.Services var provider = dbUnitOfWorkProvider; var fileProvider = fileUnitOfWorkProvider; + + + if (_localizedTextService == null) + _localizedTextService = new Lazy(() => new LocalizedTextService( + new LocalizedTextServiceFileSources(cache.RuntimeCache, new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Umbraco + "/config/lang/"))))); + if (_notificationService == null) _notificationService = new Lazy(() => new NotificationService(provider, _userService.Value, _contentService.Value)); @@ -174,11 +186,20 @@ namespace Umbraco.Core.Services if (_tagService == null) _tagService = new Lazy(() => new TagService(provider, repositoryFactory.Value)); + if (_memberGroupService == null) _memberGroupService = new Lazy(() => new MemberGroupService(provider, repositoryFactory.Value)); } + /// + /// Gets the + /// + public ILocalizedTextService TextService + { + get { return _localizedTextService.Value; } + } + /// /// Gets the /// diff --git a/src/Umbraco.Core/Services/TagService.cs b/src/Umbraco.Core/Services/TagService.cs index cf477a125b..a1339ce360 100644 --- a/src/Umbraco.Core/Services/TagService.cs +++ b/src/Umbraco.Core/Services/TagService.cs @@ -133,14 +133,7 @@ namespace Umbraco.Core.Services { using (var repository = _repositoryFactory.CreateTagRepository(_uowProvider.GetUnitOfWork())) { - if (tagGroup.IsNullOrWhiteSpace()) - { - return repository.GetAll(); - } - - var query = Query.Builder.Where(x => x.Group == tagGroup); - var definitions = repository.GetByQuery(query); - return definitions; + return repository.GetTagsForEntityType(TaggableObjectTypes.All, tagGroup); } } diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 1a523835c8..9be1ce0d11 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -672,6 +672,36 @@ namespace Umbraco.Core.Services uow.Commit(); } } + + /// + /// Add a specific section to all users or those specified as parameters + /// + /// This is useful when a new section is created to allow specific users accessing it + /// Alias of the section to add + /// Specifiying nothing will add the section to all user + public void AddSectionToAllUsers(string sectionAlias, params int[] userIds) + { + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateUserRepository(uow)) + { + IEnumerable users; + if (userIds.Any()) + { + users = repository.GetAll(userIds); + } + else + { + users = repository.GetAll(); + } + foreach (var user in users.Where(u => !u.AllowedSections.InvariantContains(sectionAlias))) + { + //now add the section for each user and commit + user.AddAllowedSection(sectionAlias); + repository.AddOrUpdate(user); + } + uow.Commit(); + } + } /// /// Get permissions set for a user and optional node ids @@ -745,4 +775,4 @@ namespace Umbraco.Core.Services /// public static event TypedEventHandler> DeletedUserType; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Strategies/RelateOnCopyHandler.cs b/src/Umbraco.Core/Strategies/RelateOnCopyHandler.cs new file mode 100644 index 0000000000..e8c1956f2d --- /dev/null +++ b/src/Umbraco.Core/Strategies/RelateOnCopyHandler.cs @@ -0,0 +1,42 @@ +using System; +using Umbraco.Core.Auditing; +using Umbraco.Core.Models; +using Umbraco.Core.Services; + +namespace Umbraco.Core.Strategies +{ + public sealed class RelateOnCopyHandler : ApplicationEventHandler + { + protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) + { + ContentService.Copied += ContentServiceCopied; + } + + private void ContentServiceCopied(IContentService sender, Core.Events.CopyEventArgs e) + { + if (e.RelateToOriginal) + { + var relationService = ApplicationContext.Current.Services.RelationService; + + var relationType = relationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias); + + if (relationType == null) + { + relationType = new RelationType(new Guid(Constants.ObjectTypes.Document), + new Guid(Constants.ObjectTypes.Document), + Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, + Constants.Conventions.RelationTypes.RelateDocumentOnCopyName) { IsBidirectional = true }; + + relationService.Save(relationType); + } + + var relation = new Relation(e.Original.Id, e.Copy.Id, relationType); + relationService.Save(relation); + + Audit.Add(AuditTypes.Copy, + string.Format("Copied content with Id: '{0}' related to original content with Id: '{1}'", + e.Copy.Id, e.Original.Id), e.Copy.WriterId, e.Copy.Id); + } + } + } +} diff --git a/src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs b/src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs index 52480a1ca7..1d5ee7855a 100644 --- a/src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs +++ b/src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs @@ -59,7 +59,7 @@ namespace Umbraco.Core.Sync server.ServerAddress, server.ForcePortnumber.IsNullOrWhiteSpace() ? "80" : server.ForcePortnumber, IOHelper.ResolveUrl(SystemDirectories.Umbraco).TrimStart('/')); - } + } } // cannot be determined, return null if no config/original url, else use config/original url as base @@ -86,7 +86,7 @@ namespace Umbraco.Core.Sync } var master = servers.FirstOrDefault(); - + if (master == null) { return CurrentServerEnvironmentStatus.Unknown; @@ -105,12 +105,12 @@ namespace Umbraco.Core.Sync } if ((appId.IsNullOrWhiteSpace() == false && appId.Trim().InvariantEquals(HttpRuntime.AppDomainAppId)) - || (serverName.IsNullOrWhiteSpace() == false && serverName.Trim().InvariantEquals(NetworkHelper.MachineName))) + || (serverName.IsNullOrWhiteSpace() == false && serverName.Trim().InvariantEquals(NetworkHelper.MachineName))) { //match by appdid or server name! - return CurrentServerEnvironmentStatus.Master; + return CurrentServerEnvironmentStatus.Master; } - + return CurrentServerEnvironmentStatus.Slave; } diff --git a/src/Umbraco.Core/TypeFinder.cs b/src/Umbraco.Core/TypeFinder.cs index 2b8ebb99cc..6b257fecfe 100644 --- a/src/Umbraco.Core/TypeFinder.cs +++ b/src/Umbraco.Core/TypeFinder.cs @@ -15,6 +15,7 @@ using System.Web.Compilation; using System.Web.Hosting; using Umbraco.Core.Configuration; using Umbraco.Core.IO; +using Umbraco.Core.Logging; namespace Umbraco.Core { @@ -450,14 +451,23 @@ namespace Umbraco.Core var allTypes = GetTypesWithFormattedException(a) .ToArray(); - //now filter the types based on the onlyConcreteClasses flag, not interfaces, not static classes but have - //the specified attribute - var attributedTypes = allTypes - .Where(t => (TypeHelper.IsNonStaticClass(t) - && (onlyConcreteClasses == false || t.IsAbstract == false)) - //the type must have this attribute - && t.GetCustomAttributes(attributeType, false).Any()) - .ToArray(); + var attributedTypes = new Type[] {}; + try + { + //now filter the types based on the onlyConcreteClasses flag, not interfaces, not static classes but have + //the specified attribute + attributedTypes = allTypes + .Where(t => (TypeHelper.IsNonStaticClass(t) + && (onlyConcreteClasses == false || t.IsAbstract == false)) + //the type must have this attribute + && t.GetCustomAttributes(attributeType, false).Any()) + .ToArray(); + } + catch (TypeLoadException ex) + { + LogHelper.Error(typeof(TypeFinder), string.Format("Could not query types on {0} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", a), ex); + continue; + } //add the types to our list to return foreach (var t in attributedTypes) @@ -583,16 +593,25 @@ namespace Umbraco.Core .Where(assignTypeFrom.IsAssignableFrom) .ToArray(); - //now filter the types based on the onlyConcreteClasses flag, not interfaces, not static classes - var filteredTypes = allSubTypes - .Where(t => (TypeHelper.IsNonStaticClass(t) - //Do not include nested private classes - since we are in full trust now this will find those too! - && t.IsNestedPrivate == false - && (onlyConcreteClasses == false || t.IsAbstract == false) - //Do not include classes that are flagged to hide from the type finder - && t.GetCustomAttribute() == null - && additionalFilter(t))) - .ToArray(); + var filteredTypes = new Type[] { }; + try + { + //now filter the types based on the onlyConcreteClasses flag, not interfaces, not static classes + filteredTypes = allSubTypes + .Where(t => (TypeHelper.IsNonStaticClass(t) + //Do not include nested private classes - since we are in full trust now this will find those too! + && t.IsNestedPrivate == false + && (onlyConcreteClasses == false || t.IsAbstract == false) + //Do not include classes that are flagged to hide from the type finder + && t.GetCustomAttribute() == null + && additionalFilter(t))) + .ToArray(); + } + catch (TypeLoadException ex) + { + LogHelper.Error(typeof(TypeFinder), string.Format("Could not query types on {0} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", a), ex); + continue; + } //add the types to our list to return foreach (var t in filteredTypes) diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 59a980aeef..e1eff16813 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -99,9 +99,9 @@ True ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.Helpers.dll - + True - ..\packages\Microsoft.AspNet.Mvc.4.0.40804.0\lib\net40\System.Web.Mvc.dll + ..\packages\Microsoft.AspNet.Mvc.4.0.30506.0\lib\net40\System.Web.Mvc.dll True @@ -299,6 +299,9 @@ + + + @@ -376,13 +379,14 @@ + + - @@ -1046,6 +1050,8 @@ + + diff --git a/src/Umbraco.Core/app.config b/src/Umbraco.Core/app.config index 53f3b4c80b..8f828418f3 100644 --- a/src/Umbraco.Core/app.config +++ b/src/Umbraco.Core/app.config @@ -8,7 +8,7 @@ - + diff --git a/src/Umbraco.Core/packages.config b/src/Umbraco.Core/packages.config index f803d1ac3e..f01b9e68ba 100644 --- a/src/Umbraco.Core/packages.config +++ b/src/Umbraco.Core/packages.config @@ -3,7 +3,7 @@ - + diff --git a/src/Umbraco.Tests/App.config b/src/Umbraco.Tests/App.config index 45a43631f7..3d35ae5c93 100644 --- a/src/Umbraco.Tests/App.config +++ b/src/Umbraco.Tests/App.config @@ -100,7 +100,7 @@ - + diff --git a/src/Umbraco.Tests/CoreStrings/StringExtensionsTests.cs b/src/Umbraco.Tests/CoreStrings/StringExtensionsTests.cs index c86ee01100..43e5d0fcf9 100644 --- a/src/Umbraco.Tests/CoreStrings/StringExtensionsTests.cs +++ b/src/Umbraco.Tests/CoreStrings/StringExtensionsTests.cs @@ -7,7 +7,7 @@ using Umbraco.Core.ObjectResolution; namespace Umbraco.Tests.CoreStrings { - [TestFixture] + [TestFixture] public class StringExtensionsTests { [SetUp] diff --git a/src/Umbraco.Tests/CoreStrings/StringValidationTests.cs b/src/Umbraco.Tests/CoreStrings/StringValidationTests.cs new file mode 100644 index 0000000000..c1f8b92438 --- /dev/null +++ b/src/Umbraco.Tests/CoreStrings/StringValidationTests.cs @@ -0,0 +1,28 @@ +using System.ComponentModel.DataAnnotations; +using NUnit.Framework; + +namespace Umbraco.Tests.CoreStrings +{ + [TestFixture] + public class StringValidationTests + { + [Test] + public void Validate_Email_Address() + { + var foo = new EmailAddressAttribute(); + + Assert.IsTrue(foo.IsValid("someone@somewhere.com")); + Assert.IsTrue(foo.IsValid("someone@somewhere.co.uk")); + Assert.IsTrue(foo.IsValid("someone+tag@somewhere.net")); + Assert.IsTrue(foo.IsValid("futureTLD@somewhere.fooo")); + + Assert.IsTrue(foo.IsValid("abc@xyz.financial")); + + Assert.IsFalse(foo.IsValid("fdsa")); + Assert.IsFalse(foo.IsValid("fdsa@")); + Assert.IsFalse(foo.IsValid("fdsa@fdsa")); + Assert.IsFalse(foo.IsValid("fdsa@fdsa.")); + + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs b/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs new file mode 100644 index 0000000000..7bde2ffddd --- /dev/null +++ b/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NUnit.Framework; +using Umbraco.Web; + +namespace Umbraco.Tests.FrontEnd +{ + [TestFixture] + public class UmbracoHelperTests + { + [Test] + public void Truncate_Simple() + { + var text = "Hello world, this is some text with a link"; + + var helper = new UmbracoHelper(); + + var result = helper.Truncate(text, 25).ToString(); + + Assert.AreEqual("Hello world, this is some…", result); + } + + [Test] + public void Truncate_Inside_Word() + { + var text = "Hello world, this is some text with a link"; + + var helper = new UmbracoHelper(); + + var result = helper.Truncate(text, 24).ToString(); + + Assert.AreEqual("Hello world, this is som…", result); + } + + [Test] + public void Truncate_With_Tag() + { + var text = "Hello world, this is some text with a link"; + + var helper = new UmbracoHelper(); + + var result = helper.Truncate(text, 35).ToString(); + + Assert.AreEqual("Hello world, this is some text with…", result); + } + + } +} diff --git a/src/Umbraco.Tests/MockTests.cs b/src/Umbraco.Tests/MockTests.cs index 30fedf2465..95c324fd49 100644 --- a/src/Umbraco.Tests/MockTests.cs +++ b/src/Umbraco.Tests/MockTests.cs @@ -57,7 +57,8 @@ namespace Umbraco.Tests new Mock().Object, new Mock().Object, new Mock().Object, - new Mock().Object); + new Mock().Object, + Mock.Of()); Assert.Pass(); } @@ -103,7 +104,8 @@ namespace Umbraco.Tests new Mock().Object, new Mock().Object, new Mock().Object, - new Mock().Object), + new Mock().Object, + Mock.Of()), CacheHelper.CreateDisabledCacheHelper()); Assert.Pass(); diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index e687a74d1b..dfaaae1d27 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -6,6 +6,7 @@ using System.Web; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Exceptions; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Persistence.Caching; @@ -418,7 +419,7 @@ namespace Umbraco.Tests.Models Assert.That(content.Properties["title"], Is.Not.Null); Assert.That(content.Properties["title"].Alias, Is.EqualTo("title")); Assert.That(content.Properties["title"].Value, Is.EqualTo("This is the new title")); - Assert.That(content.Properties["metaDescription"].Value, Is.EqualTo("This is the meta description for a textpage")); + Assert.That(content.Properties["description"].Value, Is.EqualTo("This is the meta description for a textpage")); } [Test] @@ -612,9 +613,9 @@ namespace Umbraco.Tests.Models // Assert Assert.That(content.Properties.Contains("author"), Is.True); Assert.That(content.Properties.Contains("keywords"), Is.True); - Assert.That(content.Properties.Contains("metaDescription"), Is.True); + Assert.That(content.Properties.Contains("description"), Is.True); Assert.That(content.Properties["keywords"].Value, Is.EqualTo("text,page,meta")); - Assert.That(content.Properties["metaDescription"].Value, Is.EqualTo("This is the meta description for a textpage")); + Assert.That(content.Properties["description"].Value, Is.EqualTo("This is the meta description for a textpage")); } [Test] @@ -631,7 +632,7 @@ namespace Umbraco.Tests.Models // Assert Assert.That(content.Properties.Contains("author"), Is.True); Assert.That(content.Properties.Contains("keywords"), Is.False); - Assert.That(content.Properties.Contains("metaDescription"), Is.False); + Assert.That(content.Properties.Contains("description"), Is.False); } [Test] @@ -828,7 +829,7 @@ namespace Umbraco.Tests.Models public void Can_Avoid_Circular_Dependencies_In_Composition() { var textPage = MockedContentTypes.CreateTextpageContentType(); - var parent = MockedContentTypes.CreateSimpleContentType("parent", "Parent"); + var parent = MockedContentTypes.CreateSimpleContentType("parent", "Parent", null, true); var meta = MockedContentTypes.CreateMetaContentType(); var mixin1 = MockedContentTypes.CreateSimpleContentType("mixin1", "Mixin1", new PropertyTypeCollection( new List @@ -863,7 +864,9 @@ namespace Umbraco.Tests.Models var addedMetaMixin2 = mixin2.AddContentType(meta); var addedMixin2 = mixin1.AddContentType(mixin2); var addedMeta = parent.AddContentType(meta); + var addedMixin1 = parent.AddContentType(mixin1); + var addedMixin1Textpage = textPage.AddContentType(mixin1); var addedTextpageParent = parent.AddContentType(textPage); diff --git a/src/Umbraco.Tests/Models/ContentXmlTest.cs b/src/Umbraco.Tests/Models/ContentXmlTest.cs index 11b925e9fd..ac7bae8ce2 100644 --- a/src/Umbraco.Tests/Models/ContentXmlTest.cs +++ b/src/Umbraco.Tests/Models/ContentXmlTest.cs @@ -69,7 +69,7 @@ namespace Umbraco.Tests.Models Assert.AreEqual(content.Properties["title"].Value.ToString(), element.Elements("title").Single().Value); Assert.AreEqual(content.Properties["bodyText"].Value.ToString(), element.Elements("bodyText").Single().Value); Assert.AreEqual(content.Properties["keywords"].Value.ToString(), element.Elements("keywords").Single().Value); - Assert.AreEqual(content.Properties["metaDescription"].Value.ToString(), element.Elements("metaDescription").Single().Value); + Assert.AreEqual(content.Properties["description"].Value.ToString(), element.Elements("description").Single().Value); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Mvc/UmbracoViewPageTests.cs b/src/Umbraco.Tests/Mvc/UmbracoViewPageTests.cs index c9ed53ef2a..f5ffee1ce3 100644 --- a/src/Umbraco.Tests/Mvc/UmbracoViewPageTests.cs +++ b/src/Umbraco.Tests/Mvc/UmbracoViewPageTests.cs @@ -408,7 +408,7 @@ namespace Umbraco.Tests.Mvc var ctx = new UmbracoContext( GetHttpContextFactory(url, routeData).HttpContext, appCtx, - new PublishedCaches(cache, new PublishedMediaCache()), + new PublishedCaches(cache, new PublishedMediaCache(appCtx)), new WebSecurity(http, appCtx)); //if (setSingleton) diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index f9bc86de62..5d94e604a4 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -193,8 +193,8 @@ namespace Umbraco.Tests.Persistence.Repositories using (var repository = CreateRepository(unitOfWork)) { var ctMain = MockedContentTypes.CreateSimpleContentType(); - var ctChild1 = MockedContentTypes.CreateSimpleContentType("child1", "Child 1", ctMain); - var ctChild2 = MockedContentTypes.CreateSimpleContentType("child2", "Child 2", ctChild1); + var ctChild1 = MockedContentTypes.CreateSimpleContentType("child1", "Child 1", ctMain, true); + var ctChild2 = MockedContentTypes.CreateSimpleContentType("child2", "Child 2", ctChild1, true); repository.AddOrUpdate(ctMain); repository.AddOrUpdate(ctChild1); @@ -282,14 +282,14 @@ namespace Umbraco.Tests.Persistence.Repositories var contentType = repository.Get(NodeDto.NodeIdSeed + 1); // Act - contentType.PropertyGroups["Meta"].PropertyTypes.Remove("metaDescription"); + contentType.PropertyGroups["Meta"].PropertyTypes.Remove("description"); repository.AddOrUpdate(contentType); unitOfWork.Commit(); var result = repository.Get(NodeDto.NodeIdSeed + 1); // Assert - Assert.That(result.PropertyTypes.Any(x => x.Alias == "metaDescription"), Is.False); + Assert.That(result.PropertyTypes.Any(x => x.Alias == "description"), Is.False); Assert.That(contentType.PropertyGroups.Count, Is.EqualTo(result.PropertyGroups.Count)); Assert.That(contentType.PropertyTypes.Count(), Is.EqualTo(result.PropertyTypes.Count())); } @@ -434,7 +434,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Assert Assert.That(contentType.PropertyTypes.Count(), Is.EqualTo(3)); Assert.That(contentType.PropertyTypes.Any(x => x.Alias == "keywords"), Is.False); - Assert.That(subpage.Properties.First(x => x.Alias == "metaDescription").Value, Is.EqualTo("This is the meta description for a textpage")); + Assert.That(subpage.Properties.First(x => x.Alias == "description").Value, Is.EqualTo("This is the meta description for a textpage")); } } @@ -534,7 +534,7 @@ namespace Umbraco.Tests.Persistence.Repositories //Assert var updated = contentRepository.Get(subpage.Id); Assert.That(updated.GetValue("metaAuthor").ToString(), Is.EqualTo("John Doe")); - Assert.That(updated.Properties.First(x => x.Alias == "metaDescription").Value, Is.EqualTo("This is the meta description for a textpage")); + Assert.That(updated.Properties.First(x => x.Alias == "description").Value, Is.EqualTo("This is the meta description for a textpage")); Assert.That(contentType.PropertyTypes.Count(), Is.EqualTo(4)); Assert.That(contentType.PropertyTypes.Any(x => x.Alias == "metaAuthor"), Is.True); diff --git a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs index bd35197193..c3524580dd 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs @@ -658,14 +658,20 @@ namespace Umbraco.Tests.Persistence.Repositories new[] { new Tag {Text = "tag1", Group = "test"}, - new Tag {Text = "tag2", Group = "test1"} + new Tag {Text = "tag4", Group = "test1"} }, false); var result1 = repository.GetTagsForEntityType(TaggableObjectTypes.Content).ToArray(); var result2 = repository.GetTagsForEntityType(TaggableObjectTypes.Media).ToArray(); + var result3 = repository.GetTagsForEntityType(TaggableObjectTypes.All).ToArray(); Assert.AreEqual(3, result1.Count()); Assert.AreEqual(2, result2.Count()); + Assert.AreEqual(4, result3.Count()); + + Assert.AreEqual(1, result1.Single(x => x.Text == "tag1").NodeCount); + Assert.AreEqual(2, result3.Single(x => x.Text == "tag1").NodeCount); + Assert.AreEqual(1, result3.Single(x => x.Text == "tag4").NodeCount); } } diff --git a/src/Umbraco.Tests/Plugins/PluginManagerTests.cs b/src/Umbraco.Tests/Plugins/PluginManagerTests.cs index 037d728b90..a8fae01850 100644 --- a/src/Umbraco.Tests/Plugins/PluginManagerTests.cs +++ b/src/Umbraco.Tests/Plugins/PluginManagerTests.cs @@ -277,7 +277,7 @@ namespace Umbraco.Tests.Plugins public void Resolves_Attributed_Trees() { var trees = PluginManager.Current.ResolveAttributedTrees(); - Assert.AreEqual(18, trees.Count()); + Assert.AreEqual(19, trees.Count()); } [Test] diff --git a/src/Umbraco.Tests/PublishedCache/PublishedContentCacheTests.cs b/src/Umbraco.Tests/PublishedCache/PublishedContentCacheTests.cs index b94dc9892d..03e13acdd5 100644 --- a/src/Umbraco.Tests/PublishedCache/PublishedContentCacheTests.cs +++ b/src/Umbraco.Tests/PublishedCache/PublishedContentCacheTests.cs @@ -92,9 +92,9 @@ namespace Umbraco.Tests.PublishedCache _umbracoContext = new UmbracoContext( _httpContextFactory.HttpContext, - ApplicationContext.Current, - new PublishedCaches(cache, new PublishedMediaCache()), - new WebSecurity(_httpContextFactory.HttpContext, ApplicationContext.Current)); + ApplicationContext, + new PublishedCaches(cache, new PublishedMediaCache(ApplicationContext)), + new WebSecurity(_httpContextFactory.HttpContext, ApplicationContext)); _cache = _umbracoContext.ContentCache; } diff --git a/src/Umbraco.Tests/PublishedCache/PublishedMediaCacheTests.cs b/src/Umbraco.Tests/PublishedCache/PublishedMediaCacheTests.cs index 53a8b5b658..6d0b5b18b7 100644 --- a/src/Umbraco.Tests/PublishedCache/PublishedMediaCacheTests.cs +++ b/src/Umbraco.Tests/PublishedCache/PublishedMediaCacheTests.cs @@ -37,7 +37,7 @@ namespace Umbraco.Tests.PublishedCache var mChild2 = global::umbraco.cms.businesslogic.media.Media.MakeNew("Child2", mType, user, mRoot2.Id); var ctx = GetUmbracoContext("/test", 1234); - var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(), ctx); + var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application), ctx); var roots = cache.GetAtRoot(); Assert.AreEqual(2, roots.Count()); Assert.IsTrue(roots.Select(x => x.Id).ContainsAll(new[] {mRoot1.Id, mRoot2.Id})); @@ -126,6 +126,8 @@ namespace Umbraco.Tests.PublishedCache [Test] public void Convert_From_Search_Result() { + var ctx = GetUmbracoContext("/test", 1234); + var result = new SearchResult() { Id = 1234, @@ -144,7 +146,7 @@ namespace Umbraco.Tests.PublishedCache result.Fields.Add("updateDate", "2012-07-16T10:34:09"); result.Fields.Add("writerName", "Shannon"); - var store = new PublishedMediaCache(); + var store = new PublishedMediaCache(ctx.Application); var doc = store.ConvertFromSearchResult(result); DoAssert(doc, 1234, 0, 0, "", "Image", 0, "Shannon", "", 0, 0, "-1,1234", default(DateTime), DateTime.Parse("2012-07-16T10:34:09"), 2); @@ -154,9 +156,11 @@ namespace Umbraco.Tests.PublishedCache [Test] public void Convert_From_XPath_Navigator() { + var ctx = GetUmbracoContext("/test", 1234); + var xmlDoc = GetMediaXml(); var navigator = xmlDoc.SelectSingleNode("/root/Image").CreateNavigator(); - var cache = new PublishedMediaCache(); + var cache = new PublishedMediaCache(ctx.Application); var doc = cache.ConvertFromXPathNavigator(navigator); DoAssert(doc, 2000, 0, 2, "image1", "Image", 2044, "Shannon", "Shannon2", 22, 33, "-1,2000", DateTime.Parse("2012-06-12T14:13:17"), DateTime.Parse("2012-07-20T18:50:43"), 1); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs index 5a9067a095..e2e1b91087 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs @@ -74,7 +74,8 @@ namespace Umbraco.Tests.PublishedContent { var contentTypeService = ctx.Application.Services.ContentTypeService; var baseType = new ContentType(-1) {Alias = "base", Name = "Base"}; - var inheritedType = new ContentType(baseType) {Alias = "inherited", Name = "Inherited"}; + const string contentTypeAlias = "inherited"; + var inheritedType = new ContentType(baseType, contentTypeAlias) {Alias = contentTypeAlias, Name = "Inherited"}; contentTypeService.Save(baseType); contentTypeService.Save(inheritedType); createContentTypes = false; diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs index d776e61776..478335af15 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs @@ -49,7 +49,7 @@ namespace Umbraco.Tests.PublishedContent }); PublishedCachesResolver.Current = new PublishedCachesResolver(new PublishedCaches( - new PublishedContentCache(), new PublishedMediaCache())); + new PublishedContentCache(), new PublishedMediaCache(ApplicationContext))); if (PublishedContentModelFactoryResolver.HasCurrent == false) PublishedContentModelFactoryResolver.Current = new PublishedContentModelFactoryResolver(); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs index 6d2b85ff3a..425da6ac2e 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs @@ -11,6 +11,7 @@ using Examine.LuceneEngine; using Examine.LuceneEngine.Providers; using Lucene.Net.Analysis.Standard; using Lucene.Net.Store; +using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Configuration; @@ -60,7 +61,7 @@ namespace Umbraco.Tests.PublishedContent internal static IPublishedContent GetNode(int id, UmbracoContext umbracoContext) { var ctx = umbracoContext; - var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(), ctx); + var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application), ctx); var doc = cache.GetById(id); Assert.IsNotNull(doc); return doc; @@ -112,7 +113,7 @@ namespace Umbraco.Tests.PublishedContent indexer.RebuildIndex(); var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); var ctx = GetUmbracoContext("/test", 1234); - var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(searcher, indexer), ctx); + var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx); //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace var publishedMedia = cache.GetById(1111); @@ -141,7 +142,7 @@ namespace Umbraco.Tests.PublishedContent indexer.RebuildIndex(); var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); var ctx = GetUmbracoContext("/test", 1234); - var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(searcher, indexer), ctx); + var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx); //ensure it is found var publishedMedia = cache.GetById(3113); @@ -181,7 +182,7 @@ namespace Umbraco.Tests.PublishedContent indexer.RebuildIndex(); var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); var ctx = GetUmbracoContext("/test", 1234); - var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(searcher, indexer), ctx); + var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx); //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace var publishedMedia = cache.GetById(1111); @@ -203,7 +204,7 @@ namespace Umbraco.Tests.PublishedContent indexer.RebuildIndex(); var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); var ctx = GetUmbracoContext("/test", 1234); - var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(searcher, indexer), ctx); + var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx); //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace var publishedMedia = cache.GetById(1111); @@ -225,7 +226,7 @@ namespace Umbraco.Tests.PublishedContent indexer.RebuildIndex(); var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); var ctx = GetUmbracoContext("/test", 1234); - var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(searcher, indexer), ctx); + var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx); //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace var publishedMedia = cache.GetById(1111); @@ -247,7 +248,7 @@ namespace Umbraco.Tests.PublishedContent indexer.RebuildIndex(); var ctx = GetUmbracoContext("/test", 1234); var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); - var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(searcher, indexer), ctx); + var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx); //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace var publishedMedia = cache.GetById(3113); @@ -266,7 +267,7 @@ namespace Umbraco.Tests.PublishedContent indexer.RebuildIndex(); var ctx = GetUmbracoContext("/test", 1234); var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); - var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(searcher, indexer), ctx); + var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx); //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace var publishedMedia = cache.GetById(3113); @@ -412,6 +413,100 @@ namespace Umbraco.Tests.PublishedContent Assert.IsTrue(publishedSubChild1.AncestorsOrSelf().Select(x => x.Id).ContainsAll( new[] { mSubChild1.Id, mChild1.Id, mRoot.Id })); } + + [Test] + public void Convert_From_Legacy_Xml() + { + var config = SettingsForTests.GenerateMockSettings(); + + var contentMock = Mock.Get(config.Content); + contentMock.Setup(x => x.UseLegacyXmlSchema).Returns(true); + + SettingsForTests.ConfigureSettings(config); + + var nodeId = 2112; + + var xml = XElement.Parse(@" + + 115 + 268 + 10726 + jpg + + + 115 + 268 + 10726 + jpg + + "); + var node = xml.DescendantsAndSelf("node").Single(x => (int) x.Attribute("id") == nodeId); + + var publishedMedia = new PublishedMediaCache(ApplicationContext); + + var nav = node.CreateNavigator(); + + var converted = publishedMedia.ConvertFromXPathNodeIterator(nav.Select("/node"), nodeId); + + Assert.AreEqual(nodeId, converted.Id); + Assert.AreEqual(3, converted.Level); + Assert.AreEqual(1, converted.SortOrder); + Assert.AreEqual("Sam's Umbraco Image", converted.Name); + Assert.AreEqual("-1,1111,2222,2112", converted.Path); + } + + [Test] + public void Convert_From_Standard_Xml() + { + var config = SettingsForTests.GenerateMockSettings(); + + var contentMock = Mock.Get(config.Content); + contentMock.Setup(x => x.UseLegacyXmlSchema).Returns(true); + + SettingsForTests.ConfigureSettings(config); + + var nodeId = 2112; + + var xml = XElement.Parse(@" + + 115 + 268 + 10726 + jpg + + + 115 + 268 + 10726 + jpg + + "); + var node = xml.DescendantsAndSelf("Image").Single(x => (int)x.Attribute("id") == nodeId); + + var publishedMedia = new PublishedMediaCache(ApplicationContext); + + var nav = node.CreateNavigator(); + + var converted = publishedMedia.ConvertFromXPathNodeIterator(nav.Select("/Image"), nodeId); + + Assert.AreEqual(nodeId, converted.Id); + Assert.AreEqual(3, converted.Level); + Assert.AreEqual(1, converted.SortOrder); + Assert.AreEqual("Sam's Umbraco Image", converted.Name); + Assert.AreEqual("-1,1111,2222,2112", converted.Path); + } + + [Test] + public void Detects_Error_In_Xml() + { + var errorXml = new XElement("error", string.Format("No media is maching '{0}'", 1234)); + var nav = errorXml.CreateNavigator(); + + var publishedMedia = new PublishedMediaCache(ApplicationContext); + var converted = publishedMedia.ConvertFromXPathNodeIterator(nav.Select("/"), 1234); + + Assert.IsNull(converted); + } } diff --git a/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs b/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs index 8162b27c62..8b1981bc9e 100644 --- a/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs +++ b/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs @@ -230,7 +230,7 @@ namespace Umbraco.Tests.Scheduling //wait till the thread is done await tManager; - + foreach (var task in tasks) { Assert.IsTrue(task.Ended != default(DateTime)); @@ -241,6 +241,7 @@ namespace Umbraco.Tests.Scheduling Assert.IsFalse(tManager.IsDisposed); } } + private class MyTask : BaseTask { diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 7766f8a929..5714ec2c9f 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -1335,7 +1335,7 @@ namespace Umbraco.Tests.Services //MCH: I'm guessing this is an issue because of the format the date is actually stored as, right? Cause we don't do any formatting when saving or loading Assert.That(sut.GetValue("dateTime").ToString("G"), Is.EqualTo(content.GetValue("dateTime").ToString("G"))); Assert.That(sut.GetValue("colorPicker"), Is.EqualTo("black")); - Assert.That(sut.GetValue("folderBrowser"), Is.Empty); + Assert.That(sut.GetValue("folderBrowser"), Is.Null); Assert.That(sut.GetValue("ddlMultiple"), Is.EqualTo("1234,1235")); Assert.That(sut.GetValue("rbList"), Is.EqualTo("random")); Assert.That(sut.GetValue("date").ToString("G"), Is.EqualTo(content.GetValue("date").ToString("G"))); diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs index 892076f54f..f430a93e23 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs @@ -1,3 +1,4 @@ +using System.Runtime.Remoting; using NUnit.Framework; using System; using System.Collections.Generic; @@ -5,6 +6,8 @@ using System.Linq; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Caching; +using Umbraco.Tests.CodeFirst.TestModels.Composition; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; @@ -178,10 +181,11 @@ namespace Umbraco.Tests.Services /*,"Navigation"*/); cts.Save(ctBase); - var ctHomePage = new ContentType(ctBase) + const string contentTypeAlias = "HomePage"; + var ctHomePage = new ContentType(ctBase, contentTypeAlias) { Name = "Home Page", - Alias = "HomePage", + Alias = contentTypeAlias, Icon = "settingDomain.gif", Thumbnail = "folder.png", AllowedAsRoot = true @@ -191,7 +195,7 @@ namespace Umbraco.Tests.Services cts.Save(ctHomePage); // Act - var homeDoc = cs.CreateContent("Home Page", -1, "HomePage"); + var homeDoc = cs.CreateContent("Home Page", -1, contentTypeAlias); cs.SaveAndPublishWithStatus(homeDoc); // Assert @@ -243,13 +247,13 @@ namespace Umbraco.Tests.Services var global = MockedContentTypes.CreateSimpleContentType("global", "Global"); service.Save(global); - var components = MockedContentTypes.CreateSimpleContentType("components", "Components", global); + var components = MockedContentTypes.CreateSimpleContentType("components", "Components", global, true); service.Save(components); - var component = MockedContentTypes.CreateSimpleContentType("component", "Component", components); + var component = MockedContentTypes.CreateSimpleContentType("component", "Component", components, true); service.Save(component); - var category = MockedContentTypes.CreateSimpleContentType("category", "Category", global); + var category = MockedContentTypes.CreateSimpleContentType("category", "Category", global, true); service.Save(category); var success = category.AddContentType(component); @@ -339,10 +343,10 @@ namespace Umbraco.Tests.Services var parentContentType1 = MockedContentTypes.CreateSimpleContentType("parent1", "Parent1"); service.Save(parentContentType1); - var parentContentType2 = MockedContentTypes.CreateSimpleContentType("parent2", "Parent2"); + var parentContentType2 = MockedContentTypes.CreateSimpleContentType("parent2", "Parent2", null, true); service.Save(parentContentType2); - var simpleContentType = MockedContentTypes.CreateSimpleContentType("category", "Category", parentContentType1); + var simpleContentType = MockedContentTypes.CreateSimpleContentType("category", "Category", parentContentType1, true); service.Save(simpleContentType); // Act @@ -371,8 +375,8 @@ namespace Umbraco.Tests.Services Assert.AreNotEqual(clonedContentType.Key, originalContentType.Key); Assert.AreNotEqual(clonedContentType.Path, originalContentType.Path); - Assert.AreNotEqual(clonedContentType.PropertyTypes.First(x => x.Alias.Equals("title")).Id, originalContentType.PropertyTypes.First(x => x.Alias.Equals("title")).Id); - Assert.AreNotEqual(clonedContentType.PropertyGroups.First(x => x.Name.Equals("Content")).Id, originalContentType.PropertyGroups.First(x => x.Name.Equals("Content")).Id); + Assert.AreNotEqual(clonedContentType.PropertyTypes.First(x => x.Alias.StartsWith("title")).Id, originalContentType.PropertyTypes.First(x => x.Alias.StartsWith("title")).Id); + Assert.AreNotEqual(clonedContentType.PropertyGroups.First(x => x.Name.StartsWith("Content")).Id, originalContentType.PropertyGroups.First(x => x.Name.StartsWith("Content")).Id); } [Test] @@ -432,10 +436,10 @@ namespace Umbraco.Tests.Services var parentContentType1 = MockedContentTypes.CreateSimpleContentType("parent1", "Parent1"); service.Save(parentContentType1); - var parentContentType2 = MockedContentTypes.CreateSimpleContentType("parent2", "Parent2"); + var parentContentType2 = MockedContentTypes.CreateSimpleContentType("parent2", "Parent2", null, true); service.Save(parentContentType2); - var simpleContentType = MockedContentTypes.CreateSimpleContentType("category", "Category", parentContentType1); + var simpleContentType = MockedContentTypes.CreateSimpleContentType("category", "Category", parentContentType1, true); service.Save(simpleContentType); // Act @@ -460,8 +464,768 @@ namespace Umbraco.Tests.Services Assert.AreNotEqual(clonedContentType.Key, originalContentType.Key); Assert.AreNotEqual(clonedContentType.Path, originalContentType.Path); - Assert.AreNotEqual(clonedContentType.PropertyTypes.First(x => x.Alias.Equals("title")).Id, originalContentType.PropertyTypes.First(x => x.Alias.Equals("title")).Id); - Assert.AreNotEqual(clonedContentType.PropertyGroups.First(x => x.Name.Equals("Content")).Id, originalContentType.PropertyGroups.First(x => x.Name.Equals("Content")).Id); + Assert.AreNotEqual(clonedContentType.PropertyTypes.First(x => x.Alias.StartsWith("title")).Id, originalContentType.PropertyTypes.First(x => x.Alias.StartsWith("title")).Id); + Assert.AreNotEqual(clonedContentType.PropertyGroups.First(x => x.Name.StartsWith("Content")).Id, originalContentType.PropertyGroups.First(x => x.Name.StartsWith("Content")).Id); + } + + [Test] + public void Cannot_Add_Duplicate_PropertyType_Alias_To_Referenced_Composition() + { + //Related the second issue in screencast from this post http://issues.umbraco.org/issue/U4-5986 + + // Arrange + var service = ServiceContext.ContentTypeService; + + var parent = MockedContentTypes.CreateSimpleContentType(); + service.Save(parent); + var child = MockedContentTypes.CreateSimpleContentType("simpleChildPage", "Simple Child Page", parent, true); + service.Save(child); + var composition = MockedContentTypes.CreateMetaContentType(); + service.Save(composition); + + //Adding Meta-composition to child doc type + child.AddContentType(composition); + service.Save(child); + + // Act + var duplicatePropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "title", Name = "Title", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var added = composition.AddPropertyType(duplicatePropertyType, "Meta"); + + // Assert + Assert.That(added, Is.True); + Assert.Throws(() => service.Save(composition)); + Assert.DoesNotThrow(() => service.GetContentType("simpleChildPage")); + } + + [Test] + public void Cannot_Add_Duplicate_PropertyType_Alias_In_Composition_Graph() + { + // Arrange + var service = ServiceContext.ContentTypeService; + + var basePage = MockedContentTypes.CreateSimpleContentType("basePage", "Base Page", null, true); + service.Save(basePage); + var contentPage = MockedContentTypes.CreateSimpleContentType("contentPage", "Content Page", basePage); + service.Save(contentPage); + var advancedPage = MockedContentTypes.CreateSimpleContentType("advancedPage", "Advanced Page", contentPage, true); + service.Save(advancedPage); + + var metaComposition = MockedContentTypes.CreateMetaContentType(); + service.Save(metaComposition); + var seoComposition = MockedContentTypes.CreateSeoContentType(); + service.Save(seoComposition); + + var metaAdded = contentPage.AddContentType(metaComposition); + service.Save(contentPage); + var seoAdded = advancedPage.AddContentType(seoComposition); + service.Save(advancedPage); + + // Act + var duplicatePropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "title", Name = "Title", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var addedToBasePage = basePage.AddPropertyType(duplicatePropertyType, "Content"); + var addedToAdvancedPage = advancedPage.AddPropertyType(duplicatePropertyType, "Content"); + var addedToMeta = metaComposition.AddPropertyType(duplicatePropertyType, "Meta"); + var addedToSeo = seoComposition.AddPropertyType(duplicatePropertyType, "Seo"); + + // Assert + Assert.That(metaAdded, Is.True); + Assert.That(seoAdded, Is.True); + + Assert.That(addedToBasePage, Is.True); + Assert.That(addedToAdvancedPage, Is.False); + Assert.That(addedToMeta, Is.True); + Assert.That(addedToSeo, Is.True); + + Assert.Throws(() => service.Save(basePage)); + Assert.Throws(() => service.Save(metaComposition)); + Assert.Throws(() => service.Save(seoComposition)); + + Assert.DoesNotThrow(() => service.GetContentType("contentPage")); + Assert.DoesNotThrow(() => service.GetContentType("advancedPage")); + Assert.DoesNotThrow(() => service.GetContentType("meta")); + Assert.DoesNotThrow(() => service.GetContentType("seo")); + } + + [Test] + public void Cannot_Add_Duplicate_PropertyType_Alias_At_Root_Which_Conflicts_With_Third_Levels_Composition() + { + /* + * BasePage, gets 'Title' added but should not be allowed + * -- Content Page + * ---- Advanced Page -> Content Meta + * Content Meta :: Composition, has 'Title' + * + * Content Meta has 'Title' PropertyType + * Adding 'Title' to BasePage should fail + */ + + // Arrange + var service = ServiceContext.ContentTypeService; + var basePage = MockedContentTypes.CreateBasicContentType(); + service.Save(basePage); + var contentPage = MockedContentTypes.CreateBasicContentType("contentPage", "Content Page", basePage); + service.Save(contentPage); + var advancedPage = MockedContentTypes.CreateBasicContentType("advancedPage", "Advanced Page", contentPage); + service.Save(advancedPage); + + var contentMetaComposition = MockedContentTypes.CreateContentMetaContentType(); + service.Save(contentMetaComposition); + + // Act + var bodyTextPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "bodyText", Name = "Body Text", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var bodyTextAdded = basePage.AddPropertyType(bodyTextPropertyType, "Content"); + service.Save(basePage); + + var authorPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "author", Name = "Author", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var authorAdded = contentPage.AddPropertyType(authorPropertyType, "Content"); + service.Save(contentPage); + + var compositionAdded = advancedPage.AddContentType(contentMetaComposition); + service.Save(advancedPage); + + //NOTE: It should not be possible to Save 'BasePage' with the Title PropertyType added + var titlePropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "title", Name = "Title", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var titleAdded = basePage.AddPropertyType(titlePropertyType, "Content"); + + // Assert + Assert.That(bodyTextAdded, Is.True); + Assert.That(authorAdded, Is.True); + Assert.That(titleAdded, Is.True); + Assert.That(compositionAdded, Is.True); + + Assert.Throws(() => service.Save(basePage)); + + Assert.DoesNotThrow(() => service.GetContentType("contentPage")); + Assert.DoesNotThrow(() => service.GetContentType("advancedPage")); + } + + [Test] + public void Cannot_Rename_PropertyType_Alias_On_Composition_Which_Would_Cause_Conflict_In_Other_Composition() + { + /* + * Meta renames alias to 'title' + * Seo has 'Title' + * BasePage + * -- ContentPage + * ---- AdvancedPage -> Seo + * ------ MoreAdvanedPage -> Meta + */ + + // Arrange + var service = ServiceContext.ContentTypeService; + var basePage = MockedContentTypes.CreateBasicContentType(); + service.Save(basePage); + var contentPage = MockedContentTypes.CreateBasicContentType("contentPage", "Content Page", basePage); + service.Save(contentPage); + var advancedPage = MockedContentTypes.CreateBasicContentType("advancedPage", "Advanced Page", contentPage); + service.Save(advancedPage); + var moreAdvancedPage = MockedContentTypes.CreateBasicContentType("moreAdvancedPage", "More Advanced Page", advancedPage); + service.Save(moreAdvancedPage); + + var seoComposition = MockedContentTypes.CreateSeoContentType(); + service.Save(seoComposition); + var metaComposition = MockedContentTypes.CreateMetaContentType(); + service.Save(metaComposition); + + // Act + var bodyTextPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "bodyText", Name = "Body Text", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var bodyTextAdded = basePage.AddPropertyType(bodyTextPropertyType, "Content"); + service.Save(basePage); + + var authorPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "author", Name = "Author", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var authorAdded = contentPage.AddPropertyType(authorPropertyType, "Content"); + service.Save(contentPage); + + var subtitlePropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "subtitle", Name = "Subtitle", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var subtitleAdded = advancedPage.AddPropertyType(subtitlePropertyType, "Content"); + service.Save(advancedPage); + + var titlePropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "title", Name = "Title", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var titleAdded = seoComposition.AddPropertyType(titlePropertyType, "Content"); + service.Save(seoComposition); + + var seoCompositionAdded = advancedPage.AddContentType(seoComposition); + var metaCompositionAdded = moreAdvancedPage.AddContentType(metaComposition); + service.Save(advancedPage); + service.Save(moreAdvancedPage); + + var keywordsPropertyType = metaComposition.PropertyTypes.First(x => x.Alias.Equals("metakeywords")); + keywordsPropertyType.Alias = "title"; + + // Assert + Assert.That(bodyTextAdded, Is.True); + Assert.That(subtitleAdded, Is.True); + Assert.That(authorAdded, Is.True); + Assert.That(titleAdded, Is.True); + Assert.That(seoCompositionAdded, Is.True); + Assert.That(metaCompositionAdded, Is.True); + + Assert.Throws(() => service.Save(metaComposition)); + + Assert.DoesNotThrow(() => service.GetContentType("contentPage")); + Assert.DoesNotThrow(() => service.GetContentType("advancedPage")); + Assert.DoesNotThrow(() => service.GetContentType("moreAdvancedPage")); + } + + [Test] + public void Can_Add_Additional_Properties_On_Composition_Once_Composition_Has_Been_Saved() + { + /* + * Meta renames alias to 'title' + * Seo has 'Title' + * BasePage + * -- ContentPage + * ---- AdvancedPage -> Seo + * ------ MoreAdvancedPage -> Meta + */ + + // Arrange + var service = ServiceContext.ContentTypeService; + var basePage = MockedContentTypes.CreateBasicContentType(); + service.Save(basePage); + var contentPage = MockedContentTypes.CreateBasicContentType("contentPage", "Content Page", basePage); + service.Save(contentPage); + var advancedPage = MockedContentTypes.CreateBasicContentType("advancedPage", "Advanced Page", contentPage); + service.Save(advancedPage); + var moreAdvancedPage = MockedContentTypes.CreateBasicContentType("moreAdvancedPage", "More Advanced Page", advancedPage); + service.Save(moreAdvancedPage); + + var seoComposition = MockedContentTypes.CreateSeoContentType(); + service.Save(seoComposition); + var metaComposition = MockedContentTypes.CreateMetaContentType(); + service.Save(metaComposition); + + // Act + var bodyTextPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "bodyText", Name = "Body Text", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var bodyTextAdded = basePage.AddPropertyType(bodyTextPropertyType, "Content"); + service.Save(basePage); + + var authorPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "author", Name = "Author", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var authorAdded = contentPage.AddPropertyType(authorPropertyType, "Content"); + service.Save(contentPage); + + var subtitlePropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "subtitle", Name = "Subtitle", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var subtitleAdded = advancedPage.AddPropertyType(subtitlePropertyType, "Content"); + service.Save(advancedPage); + + var titlePropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "title", Name = "Title", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var titleAdded = seoComposition.AddPropertyType(titlePropertyType, "Content"); + service.Save(seoComposition); + + var seoCompositionAdded = advancedPage.AddContentType(seoComposition); + var metaCompositionAdded = moreAdvancedPage.AddContentType(metaComposition); + service.Save(advancedPage); + service.Save(moreAdvancedPage); + + // Assert + Assert.That(bodyTextAdded, Is.True); + Assert.That(subtitleAdded, Is.True); + Assert.That(authorAdded, Is.True); + Assert.That(titleAdded, Is.True); + Assert.That(seoCompositionAdded, Is.True); + Assert.That(metaCompositionAdded, Is.True); + + var testPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "test", Name = "Test", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var testAdded = seoComposition.AddPropertyType(testPropertyType, "Content"); + service.Save(seoComposition); + + Assert.That(testAdded, Is.True); + + Assert.DoesNotThrow(() => service.GetContentType("contentPage")); + Assert.DoesNotThrow(() => service.GetContentType("advancedPage")); + Assert.DoesNotThrow(() => service.GetContentType("moreAdvancedPage")); + } + + [Test] + public void Cannot_Rename_PropertyGroup_On_Child_Avoiding_Conflict_With_Parent_PropertyGroup() + { + // Arrange + var service = ServiceContext.ContentTypeService; + var page = MockedContentTypes.CreateSimpleContentType("page", "Page", null, true, "Content"); + service.Save(page); + var contentPage = MockedContentTypes.CreateSimpleContentType("contentPage", "Content Page", page, true, "Content_"); + service.Save(contentPage); + var advancedPage = MockedContentTypes.CreateSimpleContentType("advancedPage", "Advanced Page", contentPage, true, "Details"); + service.Save(advancedPage); + + var contentMetaComposition = MockedContentTypes.CreateContentMetaContentType(); + service.Save(contentMetaComposition); + + // Act + var subtitlePropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "subtitle", Name = "Subtitle", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var authorPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "author", Name = "Author", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var subtitleAdded = contentPage.AddPropertyType(subtitlePropertyType, "Content"); + var authorAdded = contentPage.AddPropertyType(authorPropertyType, "Content"); + service.Save(contentPage); + + var compositionAdded = contentPage.AddContentType(contentMetaComposition); + service.Save(contentPage); + + //Change the name of the tab on the "root" content type 'page'. + var propertyGroup = contentPage.PropertyGroups["Content_"]; + Assert.Throws(() => contentPage.PropertyGroups.Add(new PropertyGroup + { + Id = propertyGroup.Id, + Name = "Content", + SortOrder = 0 + })); + + // Assert + Assert.That(compositionAdded, Is.True); + Assert.That(subtitleAdded, Is.True); + Assert.That(authorAdded, Is.True); + + Assert.DoesNotThrow(() => service.GetContentType("contentPage")); + Assert.DoesNotThrow(() => service.GetContentType("advancedPage")); + } + + [Test] + public void Cannot_Rename_PropertyType_Alias_Causing_Conflicts_With_Parents() + { + // Arrange + var service = ServiceContext.ContentTypeService; + var basePage = MockedContentTypes.CreateBasicContentType(); + service.Save(basePage); + var contentPage = MockedContentTypes.CreateBasicContentType("contentPage", "Content Page", basePage); + service.Save(contentPage); + var advancedPage = MockedContentTypes.CreateBasicContentType("advancedPage", "Advanced Page", contentPage); + service.Save(advancedPage); + + // Act + var titlePropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "title", Name = "Title", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var titleAdded = basePage.AddPropertyType(titlePropertyType, "Content"); + var bodyTextPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "bodyText", Name = "Body Text", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var bodyTextAdded = contentPage.AddPropertyType(bodyTextPropertyType, "Content"); + var subtitlePropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "subtitle", Name = "Subtitle", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var subtitleAdded = contentPage.AddPropertyType(subtitlePropertyType, "Content"); + var authorPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "author", Name = "Author", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var authorAdded = advancedPage.AddPropertyType(authorPropertyType, "Content"); + service.Save(basePage); + service.Save(contentPage); + service.Save(advancedPage); + + //Rename the PropertyType to something that already exists in the Composition - NOTE this should not be allowed and Saving should throw an exception + var authorPropertyTypeToRename = advancedPage.PropertyTypes.First(x => x.Alias.Equals("author")); + authorPropertyTypeToRename.Alias = "title"; + + // Assert + Assert.That(bodyTextAdded, Is.True); + Assert.That(authorAdded, Is.True); + Assert.That(titleAdded, Is.True); + Assert.That(subtitleAdded, Is.True); + + Assert.Throws(() => service.Save(advancedPage)); + + Assert.DoesNotThrow(() => service.GetContentType("contentPage")); + Assert.DoesNotThrow(() => service.GetContentType("advancedPage")); + } + + [Test] + public void Can_Add_PropertyType_Alias_Which_Exists_In_Composition_Outside_Graph() + { + /* + * Meta (Composition) + * Content Meta (Composition) has 'Title' -> Meta + * BasePage + * -- ContentPage gets 'Title' added -> Meta + * ---- Advanced Page + */ + // Arrange + var service = ServiceContext.ContentTypeService; + + var basePage = MockedContentTypes.CreateSimpleContentType("basePage", "Base Page", null, true); + service.Save(basePage); + var contentPage = MockedContentTypes.CreateSimpleContentType("contentPage", "Content Page", basePage, true); + service.Save(contentPage); + var advancedPage = MockedContentTypes.CreateSimpleContentType("advancedPage", "Advanced Page", contentPage, true); + service.Save(advancedPage); + + var metaComposition = MockedContentTypes.CreateMetaContentType(); + service.Save(metaComposition); + + var contentMetaComposition = MockedContentTypes.CreateContentMetaContentType(); + service.Save(contentMetaComposition); + + var metaAdded = contentPage.AddContentType(metaComposition); + service.Save(contentPage); + + var metaAddedToComposition = contentMetaComposition.AddContentType(metaComposition); + service.Save(contentMetaComposition); + + // Act + var propertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "title", Name = "Title", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var addedToContentPage = contentPage.AddPropertyType(propertyType, "Content"); + + // Assert + Assert.That(metaAdded, Is.True); + Assert.That(metaAddedToComposition, Is.True); + + Assert.That(addedToContentPage, Is.True); + Assert.DoesNotThrow(() => service.Save(contentPage)); + } + + [Test] + public void Can_Rename_PropertyGroup_With_Inherited_PropertyGroups() + { + //Related the first issue in screencast from this post http://issues.umbraco.org/issue/U4-5986 + + // Arrange + var service = ServiceContext.ContentTypeService; + + var page = MockedContentTypes.CreateSimpleContentType("page", "Page", null, false, "Content_"); + service.Save(page); + var contentPage = MockedContentTypes.CreateSimpleContentType("contentPage", "Content Page", page, true); + service.Save(contentPage); + var composition = MockedContentTypes.CreateMetaContentType(); + composition.AddPropertyGroup("Content"); + service.Save(composition); + //Adding Meta-composition to child doc type + contentPage.AddContentType(composition); + service.Save(contentPage); + + // Act + var propertyTypeOne = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "testTextbox", Name = "Test Textbox", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var firstOneAdded = contentPage.AddPropertyType(propertyTypeOne, "Content_"); + var propertyTypeTwo = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "anotherTextbox", Name = "Another Test Textbox", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var secondOneAdded = contentPage.AddPropertyType(propertyTypeTwo, "Content"); + service.Save(contentPage); + + Assert.That(page.PropertyGroups.Contains("Content_"), Is.True); + var propertyGroup = page.PropertyGroups["Content_"]; + page.PropertyGroups.Add(new PropertyGroup{ Id = propertyGroup.Id, Name = "ContentTab", SortOrder = 0}); + service.Save(page); + + // Assert + Assert.That(firstOneAdded, Is.True); + Assert.That(secondOneAdded, Is.True); + + var contentType = service.GetContentType("contentPage"); + Assert.That(contentType, Is.Not.Null); + + var compositionPropertyGroups = contentType.CompositionPropertyGroups; + Assert.That(compositionPropertyGroups.Count(x => x.Name.Equals("Content_")), Is.EqualTo(0)); + + var propertyTypeCount = contentType.PropertyTypes.Count(); + var compPropertyTypeCount = contentType.CompositionPropertyTypes.Count(); + Assert.That(propertyTypeCount, Is.EqualTo(5)); + Assert.That(compPropertyTypeCount, Is.EqualTo(10)); + } + + [Test] + public void Can_Rename_PropertyGroup_On_Parent_Without_Causing_Duplicate_PropertyGroups() + { + // Arrange + var service = ServiceContext.ContentTypeService; + var page = MockedContentTypes.CreateSimpleContentType("page", "Page", null, true, "Content_"); + service.Save(page); + var contentPage = MockedContentTypes.CreateSimpleContentType("contentPage", "Content Page", page, true, "Contentx"); + service.Save(contentPage); + var advancedPage = MockedContentTypes.CreateSimpleContentType("advancedPage", "Advanced Page", contentPage, true, "Contenty"); + service.Save(advancedPage); + + var contentMetaComposition = MockedContentTypes.CreateContentMetaContentType(); + service.Save(contentMetaComposition); + var compositionAdded = contentPage.AddContentType(contentMetaComposition); + service.Save(contentPage); + + // Act + var bodyTextPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "bodyText", Name = "Body Text", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var subtitlePropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "subtitle", Name = "Subtitle", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var bodyTextAdded = contentPage.AddPropertyType(bodyTextPropertyType, "Content_");//Will be added to the parent tab + var subtitleAdded = contentPage.AddPropertyType(subtitlePropertyType, "Content");//Will be added to the "Content Meta" composition + service.Save(contentPage); + + var authorPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "author", Name = "Author", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var descriptionPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "description", Name = "Description", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var keywordsPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "keywords", Name = "Keywords", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var authorAdded = advancedPage.AddPropertyType(authorPropertyType, "Content_");//Will be added to an ancestor tab + var descriptionAdded = advancedPage.AddPropertyType(descriptionPropertyType, "Contentx");//Will be added to a parent tab + var keywordsAdded = advancedPage.AddPropertyType(keywordsPropertyType, "Content");//Will be added to the "Content Meta" composition + service.Save(advancedPage); + + //Change the name of the tab on the "root" content type 'page'. + var propertyGroup = page.PropertyGroups["Content_"]; + page.PropertyGroups.Add(new PropertyGroup { Id = propertyGroup.Id, Name = "Content", SortOrder = 0 }); + service.Save(page); + + // Assert + Assert.That(compositionAdded, Is.True); + Assert.That(bodyTextAdded, Is.True); + Assert.That(subtitleAdded, Is.True); + Assert.That(authorAdded, Is.True); + Assert.That(descriptionAdded, Is.True); + Assert.That(keywordsAdded, Is.True); + + Assert.DoesNotThrow(() => service.GetContentType("contentPage")); + Assert.DoesNotThrow(() => service.GetContentType("advancedPage")); + + var advancedPageReloaded = service.GetContentType("advancedPage"); + var contentUnderscoreTabExists = advancedPageReloaded.CompositionPropertyGroups.Any(x => x.Name.Equals("Content_")); + Assert.That(contentUnderscoreTabExists, Is.False); + + var numberOfContentTabs = advancedPageReloaded.CompositionPropertyGroups.Count(x => x.Name.Equals("Content")); + Assert.That(numberOfContentTabs, Is.EqualTo(4)); + } + + [Test] + public void Can_Rename_PropertyGroup_On_Parent_Without_Causing_Duplicate_PropertyGroups_v2() + { + // Arrange + var service = ServiceContext.ContentTypeService; + var page = MockedContentTypes.CreateSimpleContentType("page", "Page", null, true, "Content_"); + service.Save(page); + var contentPage = MockedContentTypes.CreateSimpleContentType("contentPage", "Content Page", page, true, "Content"); + service.Save(contentPage); + + var contentMetaComposition = MockedContentTypes.CreateContentMetaContentType(); + service.Save(contentMetaComposition); + + // Act + var bodyTextPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "bodyText", Name = "Body Text", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var subtitlePropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "subtitle", Name = "Subtitle", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var authorPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "author", Name = "Author", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var bodyTextAdded = page.AddPropertyType(bodyTextPropertyType, "Content_"); + var subtitleAdded = contentPage.AddPropertyType(subtitlePropertyType, "Content"); + var authorAdded = contentPage.AddPropertyType(authorPropertyType, "Content_"); + service.Save(page); + service.Save(contentPage); + + var compositionAdded = contentPage.AddContentType(contentMetaComposition); + service.Save(contentPage); + + //Change the name of the tab on the "root" content type 'page'. + var propertyGroup = page.PropertyGroups["Content_"]; + page.PropertyGroups.Add(new PropertyGroup { Id = propertyGroup.Id, Name = "Content", SortOrder = 0 }); + service.Save(page); + + // Assert + Assert.That(compositionAdded, Is.True); + Assert.That(bodyTextAdded, Is.True); + Assert.That(subtitleAdded, Is.True); + Assert.That(authorAdded, Is.True); + + Assert.DoesNotThrow(() => service.GetContentType("contentPage")); + } + + [Test] + public void Can_Remove_PropertyGroup_On_Parent_Without_Causing_Duplicate_PropertyGroups() + { + // Arrange + var service = ServiceContext.ContentTypeService; + var basePage = MockedContentTypes.CreateBasicContentType(); + service.Save(basePage); + + var contentPage = MockedContentTypes.CreateBasicContentType("contentPage", "Content Page", basePage); + service.Save(contentPage); + + var advancedPage = MockedContentTypes.CreateBasicContentType("advancedPage", "Advanced Page", contentPage); + service.Save(advancedPage); + + var contentMetaComposition = MockedContentTypes.CreateContentMetaContentType(); + service.Save(contentMetaComposition); + + // Act + var bodyTextPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "bodyText", Name = "Body Text", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var bodyTextAdded = basePage.AddPropertyType(bodyTextPropertyType, "Content"); + service.Save(basePage); + + var authorPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "author", Name = "Author", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var authorAdded = contentPage.AddPropertyType(authorPropertyType, "Content"); + service.Save(contentPage); + + var compositionAdded = contentPage.AddContentType(contentMetaComposition); + service.Save(contentPage); + + basePage.RemovePropertyGroup("Content"); + service.Save(basePage); + + // Assert + Assert.That(bodyTextAdded, Is.True); + Assert.That(authorAdded, Is.True); + Assert.That(compositionAdded, Is.True); + + Assert.DoesNotThrow(() => service.GetContentType("contentPage")); + Assert.DoesNotThrow(() => service.GetContentType("advancedPage")); + + var contentType = service.GetContentType("contentPage"); + var propertyGroup = contentType.PropertyGroups["Content"]; + Assert.That(propertyGroup.ParentId.HasValue, Is.False); + } + + [Test] + public void Can_Add_PropertyGroup_With_Same_Name_On_Parent_and_Child() + { + /* + * BasePage + * - Content Page + * -- Advanced Page + * Content Meta :: Composition + */ + + // Arrange + var service = ServiceContext.ContentTypeService; + var basePage = MockedContentTypes.CreateBasicContentType(); + service.Save(basePage); + + var contentPage = MockedContentTypes.CreateBasicContentType("contentPage", "Content Page", basePage); + service.Save(contentPage); + + var advancedPage = MockedContentTypes.CreateBasicContentType("advancedPage", "Advanced Page", contentPage); + service.Save(advancedPage); + + var contentMetaComposition = MockedContentTypes.CreateContentMetaContentType(); + service.Save(contentMetaComposition); + + // Act + var authorPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "author", Name = "Author", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var authorAdded = contentPage.AddPropertyType(authorPropertyType, "Content"); + service.Save(contentPage); + + var bodyTextPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "bodyText", Name = "Body Text", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + }; + var bodyTextAdded = basePage.AddPropertyType(bodyTextPropertyType, "Content"); + service.Save(basePage); + + var compositionAdded = contentPage.AddContentType(contentMetaComposition); + service.Save(contentPage); + + // Assert + Assert.That(bodyTextAdded, Is.True); + Assert.That(authorAdded, Is.True); + Assert.That(compositionAdded, Is.True); + + Assert.DoesNotThrow(() => service.GetContentType("contentPage")); + Assert.DoesNotThrow(() => service.GetContentType("advancedPage")); + + var contentType = service.GetContentType("contentPage"); + var propertyGroup = contentType.PropertyGroups["Content"]; + Assert.That(propertyGroup.ParentId.HasValue, Is.False); + + var numberOfContentTabs = contentType.CompositionPropertyGroups.Count(x => x.Name.Equals("Content")); + Assert.That(numberOfContentTabs, Is.EqualTo(3)); + + //Ensure that adding a new PropertyType to the "Content"-tab also adds it to the right group + + var descriptionPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "description", Name = "Description", Description = "", HelpText = "", Mandatory = false, SortOrder = 1,DataTypeDefinitionId = -88 + }; + var descriptionAdded = contentType.AddPropertyType(descriptionPropertyType, "Content"); + service.Save(contentType); + Assert.That(descriptionAdded, Is.True); + + var contentPageReloaded = service.GetContentType("contentPage"); + var propertyGroupReloaded = contentPageReloaded.PropertyGroups["Content"]; + var hasDescriptionPropertyType = propertyGroupReloaded.PropertyTypes.Contains("description"); + Assert.That(hasDescriptionPropertyType, Is.True); + Assert.That(propertyGroupReloaded.ParentId.HasValue, Is.False); + + var descriptionPropertyTypeReloaded = propertyGroupReloaded.PropertyTypes["description"]; + Assert.That(descriptionPropertyTypeReloaded.PropertyGroupId.IsValueCreated, Is.False); } private ContentType CreateComponent() @@ -487,9 +1251,10 @@ namespace Umbraco.Tests.Services private ContentType CreateBannerComponent(ContentType parent) { - var banner = new ContentType(parent) + const string contentTypeAlias = "banner"; + var banner = new ContentType(parent, contentTypeAlias) { - Alias = "banner", + Alias = contentTypeAlias, Name = "Banner Component", Description = "ContentType used for Banner Component", Icon = ".sprTreeDoc3", @@ -535,9 +1300,10 @@ namespace Umbraco.Tests.Services private ContentType CreateHomepage(ContentType parent) { - var contentType = new ContentType(parent) + const string contentTypeAlias = "homepage"; + var contentType = new ContentType(parent, contentTypeAlias) { - Alias = "homepage", + Alias = contentTypeAlias, Name = "Homepage", Description = "ContentType used for the Homepage", Icon = ".sprTreeDoc3", @@ -571,7 +1337,7 @@ namespace Umbraco.Tests.Services { var contentType = MockedContentTypes.CreateSimpleContentType("childType" + i, "ChildType" + i, //make the last entry in the list, this one's parent - list.Last()); + list.Last(), true); list.Add(contentType); } diff --git a/src/Umbraco.Tests/Services/Importing/CompositionsTestPackage-Random.xml b/src/Umbraco.Tests/Services/Importing/CompositionsTestPackage-Random.xml new file mode 100644 index 0000000000..517db639a0 --- /dev/null +++ b/src/Umbraco.Tests/Services/Importing/CompositionsTestPackage-Random.xml @@ -0,0 +1,175 @@ + + + + + + Composite Test + dfsfd + MIT License + ddsff + + 3 + 0 + 0 + + + + fsdfds + sfdf + + + + + + + + + + Hello World

]]>
+
+
+
+ + + + Composite Test + CompositeTest + .sprTreeFolder + folder.png + + False + False + + Meta + Seo + Content + + + + + CompositeTest + + + + + + + + Content + Content + .sprTreeFolder + folder.png + + False + False + + + + + + + + Content + content + Umbraco.TinyMCEv3 + ca90c950-0aff-4e72-b976-a30b1ac57dad + Content + False + + + + + + + 21 + Content + 0 + + + + + + Meta + Meta + .sprTreeFolder + folder.png + + False + False + + + + + + + + Description + description + Umbraco.TextboxMultiple + c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3 + Meta + True + + + + + Tags + tags + Umbraco.Tags + b6b73142-b9c1-4bf8-a16d-e1c23320b549 + Meta + False + + + + + + + 19 + Meta + 0 + + + + + + SEO + Seo + .sprTreeFolder + folder.png + + False + False + + + + + + + + Fancy + fancy + Umbraco.Textbox + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + SEO + False + + + + + + + 20 + SEO + 0 + + + + + + + + + + +
\ No newline at end of file diff --git a/src/Umbraco.Tests/Services/Importing/CompositionsTestPackage.xml b/src/Umbraco.Tests/Services/Importing/CompositionsTestPackage.xml new file mode 100644 index 0000000000..f6b4e2c160 --- /dev/null +++ b/src/Umbraco.Tests/Services/Importing/CompositionsTestPackage.xml @@ -0,0 +1,924 @@ + + + + + + Compositions Packaged + 1.0 + MIT License + http://blog.sitereactor.dk + + 3 + 0 + 0 + + + + Morten Christensen + http://blog.sitereactor.dk + + + + + + + 0 + <![CDATA[Txt: A responsive starter kit for Umbraco]]> + + + + + 0 + + + + 1054 + + + This is TXT, a free site template designed by n33 for HTML5 UP. It's built on the skelJS framework, uses well-thought out HTML5 and CSS3, and is fully responsive making it a great starting point for your own projects. And, like everything else we make, it's also free to use for pretty much any personal or commercial work under the Creative Commons Attribution license, so go ahead and use it – just don't forget to credit us!

]]>
+ + + + + 0 + 1 + + + You've installed Umbraco and the "Txt" Starter Kit.

+

Edit the text on the homepage and create a site structure by adding new texpages to your site. This is all done in the Content section.

+

If you find the editing options provided by the Txt site too limited for you needs, simply add more properties to the page by going to the Settings section, expanding the Document Types item and adding new properties on the Generic Properties tab. You can find more information about document types and properties at the Umbraco website.

+

You'll probably want to personalize your site by changing the current design. This is also done in the Settings section, by editing the CSS styles and HTML templates. Umbraco uses master templates, so the main, common markup is placed in the Starterkit Master template, while the Homeage and Textpage have separate templates for their unique layouts. You can find more information about templates and css in umbraco at the umbraco website.

+

Once you're happy with your site's design, you might want to add more functionality, such as maps, image galleries or forms. This is done by installing Umbraco modules.

]]> +
+
+ + 0 + 1 + + + The Txt Starter Kit only scratches the surface of what's possible with Umbraco. Below the Txt Starter Kit and its modules lies a great architecture that lets you implement whatever you need.

+

With Umbraco you've finally got a solid, open and reliable platform for websites as basic as the Txt site, and Umbraco can be rapidly expanded to support multi-language websites, collaboration platforms and intra/extranets, to name just a few.

+

Advanced functionality is created with Umbraco macros, built with Umbraco's award-winning .NET integration, including full support for any .NET User or Custom control and ASP.NET MVC. Create and integrate your own .NET macros in mere minutes with point and click simplicity. Simply copy your controls to the Umbraco website, go to the Developer section and create a new macro, selecting your control from the list.

+

You can also use Microsoft's Razor syntax to quickly add dynamic functionality to your site.

+

We've also gathered the best community macros into a repository that's also accessed from the Developer section, in the Packages area. You can find more information about creating macros, on the Umbraco website.

+

The sky is the limit with Umbraco, and you have the benefit a friendly community, training, and guaranteed support. Find out how to get help.

]]> +
+
+ + 0 + 1 + + + Umbraco modules encapsulate specific bits of advanced functionality that are easily added to your website.

+

Once installed, Umbraco modules are open source and easy to customize if you want to modify the behavior for your specific needs.

+

Because Umbraco modules are provided under the MIT license you are free to use and modify them any way you want, with no strings attached.

+

To add Umbraco modules to your website, go to the Settings section, expand the Templates item, select the Starterkit Master template, then click the Customize Skin button on the toolbar.

+

Umbraco modules are available for various kinds of navigation, a sitemap, social media feeds, and a contact form. The list of available Umbraco modules is growing rapidly and is automatically updated from a central source, always fresh and current.

+

Get more information about the umbraco way.

]]> +
+
+ + 0 + 1 + + + The Txt Starter Kit gives you a small website that introduces you to a set of well-defined conventions for building an Umbraco website.

+

Now that you know what the Txt Starter Kit is, it is time to get started using Umbraco.

]]> +
+
+ + 0 + <![CDATA[Adventure log]]> + + 0 + + Ita prorsus, inquam; Hanc ergo intuens debet institutum illud quasi signum absolvere. Ergo adhuc, quantum equidem intellego, causa non videtur fuisse mutandi nominis. Quia dolori non voluptas contraria est, sed doloris privatio. Nos autem non solum beatae vitae istam esse oblectationem videmus, sed etiam levamentum miseriarum. Quodsi ipsam honestatem undique pertectam atque absolutam. Nos cum te, M. Quod vestri non item.

+

Cum id quoque, ut cupiebat, audivisset, evelli iussit eam, qua erat transfixus, hastam. Quarum ambarum rerum cum medicinam pollicetur, luxuriae licentiam pollicetur. Quid iudicant sensus? Quo tandem modo?

]]> +
+
+ + 0 + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Commoda autem et incommoda in eo genere sunt, quae praeposita et reiecta diximus; Bestiarum vero nullum iudicium puto. Est enim effectrix multarum et magnarum voluptatum. Duo Reges: constructio interrete. Claudii libidini, qui tum erat summo ne imperio, dederetur. Quarum ambarum rerum cum medicinam pollicetur, luxuriae licentiam pollicetur. Sed virtutem ipsam inchoavit, nihil amplius.

+

Ita redarguitur ipse a sese, convincunturque scripta eius probitate ipsius ac moribus. Istam voluptatem, inquit, Epicurus ignorat? Sed venio ad inconstantiae crimen, ne saepius dicas me aberrare; Sic, et quidem diligentius saepiusque ista loquemur inter nos agemusque communiter. Primum in nostrane potestate est, quid meminerimus? Consequens enim est et post oritur, ut dixi. Hoc mihi cum tuo fratre convenit. Immo videri fortasse. Itaque in rebus minime obscuris non multus est apud eos disserendi labor. Aliud igitur esse censet gaudere, aliud non dolere.

]]> +
+
+ + 0 + + Ut aliquid scire se gaudeant? Hanc ergo intuens debet institutum illud quasi signum absolvere. Vestri haec verecundius, illi fortasse constantius. Itaque sensibus rationem adiunxit et ratione effecta sensus non reliquit. Sed ea mala virtuti magnitudine obruebantur. Quasi ego id curem, quid ille aiat aut neget. Verum tamen cum de rebus grandioribus dicas, ipsae res verba rapiunt; Apparet statim, quae sint officia, quae actiones.

+

Virtutibus igitur rectissime mihi videris et ad consuetudinem nostrae orationis vitia posuisse contraria. Nonne videmus quanta perturbatio rerum omnium consequatur, quanta confusio? Sed eum qui audiebant, quoad poterant, defendebant sententiam suam. Ut necesse sit omnium rerum, quae natura vigeant, similem esse finem, non eundem.

]]> +
+
+ + 0 + 2021-09-20T00:00:00 + + + + Sed ad haec, nisi molestum est, habeo quae velim. Neque enim disputari sine reprehensione nec cum iracundia aut pertinacia recte disputari potest. Nec vero alia sunt quaerenda contra Carneadeam illam sententiam. Deinde disputat, quod cuiusque generis animantium statui deceat extremum. Facit igitur Lucius noster prudenter, qui audire de summo bono potissimum velit; Quis non odit sordidos, vanos, leves, futtiles?

+

Negat enim summo bono afferre incrementum diem. Haec mihi videtur delicatior, ut ita dicam, molliorque ratio, quam virtutis vis gravitasque postulat. Haec quo modo conveniant, non sane intellego. Quis enim confidit semper sibi illud stabile et firmum permansurum, quod fragile et caducum sit? Multoque hoc melius nos veriusque quam Stoici. Igitur neque stultorum quisquam beatus neque sapientium non beatus. De hominibus dici non necesse est. Non enim iam stirpis bonum quaeret, sed animalis. Nobis Heracleotes ille Dionysius flagitiose descivisse videtur a Stoicis propter oculorum dolorem. At, illa, ut vobis placet, partem quandam tuetur, reliquam deserit.

]]> +
+
+
+
+
+
+ + + + Master + umbMaster + folder.gif + folder.png + + False + False + + + + + + + + Hide in navigation? + umbracoNaviHide + Umbraco.TrueFalse + 92897bc6-a5f3-4ffe-ae27-f2e7e33dda49 + + False + + + + + Page title + title + Umbraco.Textbox + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + Content + False + + + + + + + 12 + Content + 0 + + + + + + Home + umbHomePage + .sprTreeSettingDomain + docWithImage.png + + False + False + umbMaster + + umbMaster + + + + + umbHomePage + + + umbTextPage + + + + Facebook link + facebookLink + Umbraco.Textbox + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + Social + False + + + + + Twitter link + twitterLink + Umbraco.Textbox + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + Social + False + + + + + Rss link + rssLink + Umbraco.Textbox + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + Social + False + + + + + Dribbble link + dribbbleLink + Umbraco.Textbox + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + Social + False + + + + + LinkedIn link + linkedInLink + Umbraco.Textbox + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + Social + False + + + + + Google+ link + googleLink + Umbraco.Textbox + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + Social + False + + + + + Hide banner? + hideBanner + Umbraco.TrueFalse + 92897bc6-a5f3-4ffe-ae27-f2e7e33dda49 + Banner + False + + + + + Banner Header + bannerHeader + Umbraco.Textbox + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + Banner + False + + + + + Banner Subheader + bannerSubheader + Umbraco.Textbox + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + Banner + False + + + + + Banner link text + bannerLinkText + Umbraco.Textbox + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + Banner + False + + + + + Banner link + bannerLink + Umbraco.ContentPickerAlias + a6857c73-d6e9-480c-b6e6-f15f6ad11125 + Banner + False + + + + + Banner background image + bannerBackgroundImage + Umbraco.UploadField + 84c6b441-31df-4ffe-b67e-67d5bc3ae65a + Banner + False + + + + + About Title + aboutTitle + Umbraco.Textbox + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + About + False + + + + + AboutText + aboutText + Umbraco.TinyMCEv3 + ca90c950-0aff-4e72-b976-a30b1ac57dad + About + False + + + + + Site Name + siteName + Umbraco.Textbox + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + Content + False + + + + + Byline + byline + Umbraco.Textbox + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + Content + False + + + + + Copyright + copyright + Umbraco.Textbox + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + Content + False + + + + + + + 13 + Social + 0 + + + 14 + Banner + 1 + + + 15 + About + 2 + + + 16 + Content + 3 + + + + + + Meta + Meta + icon-truck + folder.png + + False + False + + + + + + + + Meta Keywords + metaKeywords + Umbraco.Textbox + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + Meta + False + + + + + + + 19 + Meta + 9 + + + + + + News Item + umbNewsItem + .sprTreeDocPic + docWithImage.png + + False + False + umbMaster + + umbMaster + + + + + umbNewsItem + + + + + Publish date + publishDate + Umbraco.DateTime + e4d66c0f-b935-4200-81f0-025f7256b89a + + False + + + + + Image + image + Umbraco.UploadField + 84c6b441-31df-4ffe-b67e-67d5bc3ae65a + Content + False + + + + + Subheader + subheader + Umbraco.Textbox + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + Content + False + + + + + Content + bodyText + Umbraco.TinyMCEv3 + ca90c950-0aff-4e72-b976-a30b1ac57dad + Content + False + + + + + + + 17 + Content + 0 + + + + + + News Overview + umbNewsOverview + package.png + folder_media.png + + False + False + umbMaster + + umbMaster + + + + + umbNewsOverview + + + umbNewsItem + + + + + + + Seo + Seo + icon-wifi + folder.png + + False + False + + + + + + + + Seo Pimp Title + seoPimpTitle + Umbraco.Textbox + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + Seo + False + + + + + + + 20 + Seo + 10 + + + + + + Text Page + umbTextyPage + .sprTreeDoc + doc.png + + False + False + umbMaster + + umbMaster + Meta + Seo + + + + + umbTextyPage + + + umbTextyPage + + + + Featured Page? + featuredPage + Umbraco.TrueFalse + 92897bc6-a5f3-4ffe-ae27-f2e7e33dda49 + Content + False + + + + + Image + image + Umbraco.UploadField + 84c6b441-31df-4ffe-b67e-67d5bc3ae65a + Content + False + + + + + Content + bodyText + Umbraco.TinyMCEv3 + ca90c950-0aff-4e72-b976-a30b1ac57dad + Content + False + + + + + + + 18 + Content + 0 + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/src/Umbraco.Tests/Services/Importing/Fanoe-Package.xml b/src/Umbraco.Tests/Services/Importing/Fanoe-Package.xml new file mode 100644 index 0000000000..5f6371bba1 --- /dev/null +++ b/src/Umbraco.Tests/Services/Importing/Fanoe-Package.xml @@ -0,0 +1,5001 @@ + + + + + bootstrap.min.js + /js + bootstrap.min.js + + + jquery.min.js + /js + jquery.min.js + + + top-image.jpg + /Media/1001 + top-image.jpg + + + top-image_big-thumb.jpg + /Media/1001 + top-image_big-thumb.jpg + + + top-image_thumb.jpg + /Media/1001 + top-image_thumb.jpg + + + 128.jpg + /Media/1002 + 128.jpg + + + 128_thumb.jpg + /Media/1002 + 128_thumb.jpg + + + mid-image.jpg + /Media/1003 + mid-image.jpg + + + mid-image_thumb.jpg + /Media/1003 + mid-image_thumb.jpg + + + milkbath.jpg + /Media/1004 + milkbath.jpg + + + milkbath_thumb.jpg + /Media/1004 + milkbath_thumb.jpg + + + 54619133.jpg + /Media/1005 + 54619133.jpg + + + 54619133_thumb.jpg + /Media/1005 + 54619133_thumb.jpg + + + girls-1000.png + /Media/1006 + girls-1000.png + + + girls-1000_big-thumb.jpg + /Media/1006 + girls-1000_big-thumb.jpg + + + girls-1000_thumb.jpg + /Media/1006 + girls-1000_thumb.jpg + + + boat.jpg + /Media/1007 + boat.jpg + + + boat_big-thumb.jpg + /Media/1007 + boat_big-thumb.jpg + + + boat_thumb.jpg + /Media/1007 + boat_thumb.jpg + + + boat_2.jpg + /Media/1008 + boat_2.jpg + + + boat_2_big-thumb.jpg + /Media/1008 + boat_2_big-thumb.jpg + + + boat_2_thumb.jpg + /Media/1008 + boat_2_thumb.jpg + + + logo.svg + /Media/1009 + logo.svg + + + 591407c7-165f-462c-9546-4b55621da7ce_128.jpg + /Media/1d1fa309-e97e-4243-b58a-6f9ced1ccb0f + 128.jpg + + + b9d7afcb-d025-43fc-88cb-6922d3e7a788_128_thumb.jpg + /Media/1d1fa309-e97e-4243-b58a-6f9ced1ccb0f + 128_thumb.jpg + + + 70ec1293-6e21-4a71-bef1-38a0005788ef_54619133.jpg + /Media/1d1fa309-e97e-4243-b58a-6f9ced1ccb0f + 54619133.jpg + + + def11b2b-48ec-4c8e-bca6-6b07117d28b7_54619133_thumb.jpg + /Media/1d1fa309-e97e-4243-b58a-6f9ced1ccb0f + 54619133_thumb.jpg + + + 2710e8d0-fc69-4fc2-a5d7-4e78353f6124_boat.jpg + /Media/1d1fa309-e97e-4243-b58a-6f9ced1ccb0f + boat.jpg + + + 61d37ca2-433f-4f0f-ab04-3f297def9e1d_boat_2.jpg + /Media/1d1fa309-e97e-4243-b58a-6f9ced1ccb0f + boat_2.jpg + + + a07ee698-17ff-4daf-9583-80e02ba9bbfe_boat_2_big-thumb.jpg + /Media/1d1fa309-e97e-4243-b58a-6f9ced1ccb0f + boat_2_big-thumb.jpg + + + c17da1af-ae34-42ea-8aaa-ebebcc616607_boat_2_thumb.jpg + /Media/1d1fa309-e97e-4243-b58a-6f9ced1ccb0f + boat_2_thumb.jpg + + + 15ec6ab7-ee74-401a-89ae-33ef21428f6b_boat_big-thumb.jpg + /Media/1d1fa309-e97e-4243-b58a-6f9ced1ccb0f + boat_big-thumb.jpg + + + af7ef385-f973-4883-8fcd-6125a2b652fd_boat_thumb.jpg + /Media/1d1fa309-e97e-4243-b58a-6f9ced1ccb0f + boat_thumb.jpg + + + ed078f42-d7fd-4c4c-a6b1-c1fff3497403_bootstrap.min.js + /Media/1d1fa309-e97e-4243-b58a-6f9ced1ccb0f + bootstrap.min.js + + + d16e43db-1b49-4fd6-8e38-072ae77e4ca1_girls-1000.png + /Media/1d1fa309-e97e-4243-b58a-6f9ced1ccb0f + girls-1000.png + + + 6442ec82-0055-4741-82a1-2c1b4a29ca5f_girls-1000_big-thumb.jpg + /Media/1d1fa309-e97e-4243-b58a-6f9ced1ccb0f + girls-1000_big-thumb.jpg + + + a1641fdd-1aac-4516-bab0-8541e823023e_girls-1000_thumb.jpg + /Media/1d1fa309-e97e-4243-b58a-6f9ced1ccb0f + girls-1000_thumb.jpg + + + f3423fde-9765-4b42-8059-34458e2f21cf_jquery.min.js + /Media/1d1fa309-e97e-4243-b58a-6f9ced1ccb0f + jquery.min.js + + + 9557317e-2194-4b2f-a4a9-4345bea5fa40_logo.svg + /Media/1d1fa309-e97e-4243-b58a-6f9ced1ccb0f + logo.svg + + + a8457e59-4f99-43e8-bac1-12e9ff18696e_mid-image.jpg + /Media/1d1fa309-e97e-4243-b58a-6f9ced1ccb0f + mid-image.jpg + + + 73225caf-fe9c-494e-a8df-14604922eae1_mid-image_thumb.jpg + /Media/1d1fa309-e97e-4243-b58a-6f9ced1ccb0f + mid-image_thumb.jpg + + + 2cf84124-8540-4dfd-947e-ef20401638fc_milkbath.jpg + /Media/1d1fa309-e97e-4243-b58a-6f9ced1ccb0f + milkbath.jpg + + + 24ce629e-0748-4524-8d08-077940270f93_milkbath_thumb.jpg + /Media/1d1fa309-e97e-4243-b58a-6f9ced1ccb0f + milkbath_thumb.jpg + + + 15554ca1-3cbc-47b5-a40f-29712ce4d4ec_top-image.jpg + /Media/1d1fa309-e97e-4243-b58a-6f9ced1ccb0f + top-image.jpg + + + 7de54e8a-d0eb-4439-a445-0b5067c660e2_top-image_big-thumb.jpg + /Media/1d1fa309-e97e-4243-b58a-6f9ced1ccb0f + top-image_big-thumb.jpg + + + 778cecbc-63a9-4b2f-b443-677fba525a03_top-image_thumb.jpg + /Media/1d1fa309-e97e-4243-b58a-6f9ced1ccb0f + top-image_thumb.jpg + + + Blogpost.cshtml + /Views + Blogpost.cshtml + + + Home.cshtml + /Views + Home.cshtml + + + Master.cshtml + /Views + Master.cshtml + + + TextPage.cshtml + /Views + TextPage.cshtml + + + Web.config + /Views + Web.config + + + InsertUmbracoForm.cshtml + /Views/MacroPartials + InsertUmbracoForm.cshtml + + + RenderUmbracoFormScripts.cshtml + /Views/MacroPartials + RenderUmbracoFormScripts.cshtml + + + mainNavPartial.cshtml + /Views/Partials + mainNavPartial.cshtml + + + yada.cshtml + /Views/Partials + yada.cshtml + + + Form.cshtml + /Views/Partials/Forms + Form.cshtml + + + Script.cshtml + /Views/Partials/Forms + Script.cshtml + + + csv.cshtml + /Views/Partials/Forms/Export + csv.cshtml + + + html.cshtml + /Views/Partials/Forms/Export + html.cshtml + + + FieldType.CheckBox.cshtml + /Views/Partials/Forms/Fieldtypes + FieldType.CheckBox.cshtml + + + FieldType.CheckBoxList.cshtml + /Views/Partials/Forms/Fieldtypes + FieldType.CheckBoxList.cshtml + + + FieldType.DatePicker.cshtml + /Views/Partials/Forms/Fieldtypes + FieldType.DatePicker.cshtml + + + FieldType.DropDownList.cshtml + /Views/Partials/Forms/Fieldtypes + FieldType.DropDownList.cshtml + + + FieldType.FileUpload.cshtml + /Views/Partials/Forms/Fieldtypes + FieldType.FileUpload.cshtml + + + FieldType.HiddenField.cshtml + /Views/Partials/Forms/Fieldtypes + FieldType.HiddenField.cshtml + + + FieldType.PasswordField.cshtml + /Views/Partials/Forms/Fieldtypes + FieldType.PasswordField.cshtml + + + FieldType.RadioButtonList.cshtml + /Views/Partials/Forms/Fieldtypes + FieldType.RadioButtonList.cshtml + + + Fieldtype.Recaptcha.cshtml + /Views/Partials/Forms/Fieldtypes + Fieldtype.Recaptcha.cshtml + + + FieldType.Text.cshtml + /Views/Partials/Forms/Fieldtypes + FieldType.Text.cshtml + + + FieldType.Textarea.cshtml + /Views/Partials/Forms/Fieldtypes + FieldType.Textarea.cshtml + + + FieldType.Textfield.cshtml + /Views/Partials/Forms/Fieldtypes + FieldType.Textfield.cshtml + + + d0c3e59e-702b-45f2-a111-b6a24dab4563_blogpost.cshtml + /Views/Partials/Grid + blogpost.cshtml + + + Bootstrap2.cshtml + /Views/Partials/Grid + Bootstrap2.cshtml + + + Bootstrap3.cshtml + /Views/Partials/Grid + Bootstrap3.cshtml + + + Base.cshtml + /Views/Partials/Grid/Editors + Base.cshtml + + + Embed.cshtml + /Views/Partials/Grid/Editors + Embed.cshtml + + + Macro.cshtml + /Views/Partials/Grid/Editors + Macro.cshtml + + + Media.cshtml + /Views/Partials/Grid/Editors + Media.cshtml + + + Rte.cshtml + /Views/Partials/Grid/Editors + Rte.cshtml + + + Textstring.cshtml + /Views/Partials/Grid/Editors + Textstring.cshtml + + + ._embed_videowrapper.cshtml + /App_Plugins/Grid/Editors/Render + ._embed_videowrapper.cshtml + + + embed_videowrapper.cshtml + /App_Plugins/Grid/Editors/Render + embed_videowrapper.cshtml + + + media_round.cshtml + /App_Plugins/Grid/Editors/Render + media_round.cshtml + + + media_text_right.cshtml + /App_Plugins/Grid/Editors/Render + media_text_right.cshtml + + + media_wide.cshtml + /App_Plugins/Grid/Editors/Render + media_wide.cshtml + + + quote_with_description.cshtml + /App_Plugins/Grid/Editors/Render + quote_with_description.cshtml + + + media_with_description.html + /App_Plugins/Grid/Editors/View + media_with_description.html + + + quote_with_description.html + /App_Plugins/Grid/Editors/View + quote_with_description.html + + + installed + /App_Plugins/UmbracoForms + installed + + + package.manifest + /App_Plugins/UmbracoForms + package.manifest + + + UmbracoForms.config + /App_Plugins/UmbracoForms + UmbracoForms.config + + + version + /App_Plugins/UmbracoForms + version + + + defaultform.css + /App_Plugins/UmbracoForms/Assets + defaultform.css + + + jquery-ui-1.8.18.custom.css + /App_Plugins/UmbracoForms/Assets + jquery-ui-1.8.18.custom.css + + + umbracoforms.js + /App_Plugins/UmbracoForms/Assets + umbracoforms.js + + + daterangepicker.css + /App_Plugins/UmbracoForms/Assets/daterangepicker + daterangepicker.css + + + daterangepicker.js + /App_Plugins/UmbracoForms/Assets/daterangepicker + daterangepicker.js + + + moment.min.js + /App_Plugins/UmbracoForms/Assets/moment + moment.min.js + + + pikaday.css + /App_Plugins/UmbracoForms/Assets/pikaday + pikaday.css + + + pikaday.js + /App_Plugins/UmbracoForms/Assets/pikaday + pikaday.js + + + checkbox.html + /App_Plugins/UmbracoForms/Backoffice/Common/FieldTypes + checkbox.html + + + checkboxlist.html + /App_Plugins/UmbracoForms/Backoffice/Common/FieldTypes + checkboxlist.html + + + datepicker.html + /App_Plugins/UmbracoForms/Backoffice/Common/FieldTypes + datepicker.html + + + dropdownlist.html + /App_Plugins/UmbracoForms/Backoffice/Common/FieldTypes + dropdownlist.html + + + fileupload.html + /App_Plugins/UmbracoForms/Backoffice/Common/FieldTypes + fileupload.html + + + hiddenfield.html + /App_Plugins/UmbracoForms/Backoffice/Common/FieldTypes + hiddenfield.html + + + password.html + /App_Plugins/UmbracoForms/Backoffice/Common/FieldTypes + password.html + + + radiobuttonlist.html + /App_Plugins/UmbracoForms/Backoffice/Common/FieldTypes + radiobuttonlist.html + + + recaptcha.html + /App_Plugins/UmbracoForms/Backoffice/Common/FieldTypes + recaptcha.html + + + text.html + /App_Plugins/UmbracoForms/Backoffice/Common/FieldTypes + text.html + + + textarea.html + /App_Plugins/UmbracoForms/Backoffice/Common/FieldTypes + textarea.html + + + textfield.html + /App_Plugins/UmbracoForms/Backoffice/Common/FieldTypes + textfield.html + + + date.html + /App_Plugins/UmbracoForms/Backoffice/Common/RenderTypes + date.html + + + file.html + /App_Plugins/UmbracoForms/Backoffice/Common/RenderTypes + file.html + + + file.controller.js + /App_Plugins/UmbracoForms/Backoffice/Common/RenderTypes/Controllers + file.controller.js + + + 592e910e-fd49-4237-b466-6f4eaabaf0a3_checkbox.html + /App_Plugins/UmbracoForms/Backoffice/Common/SettingTypes + checkbox.html + + + documentmapper.controller.js + /App_Plugins/UmbracoForms/Backoffice/Common/SettingTypes + documentmapper.controller.js + + + documentmapper.html + /App_Plugins/UmbracoForms/Backoffice/Common/SettingTypes + documentmapper.html + + + b3661117-72d3-4d8a-9faa-10833b1641c2_dropdownlist.html + /App_Plugins/UmbracoForms/Backoffice/Common/SettingTypes + dropdownlist.html + + + fieldmapper.controller.js + /App_Plugins/UmbracoForms/Backoffice/Common/SettingTypes + fieldmapper.controller.js + + + fieldmapper.html + /App_Plugins/UmbracoForms/Backoffice/Common/SettingTypes + fieldmapper.html + + + 5222ea6b-3c45-4c6a-a349-2d3998958e53_file.controller.js + /App_Plugins/UmbracoForms/Backoffice/Common/SettingTypes + file.controller.js + + + 43f7e18f-2c10-4281-a217-418bd085730b_file.html + /App_Plugins/UmbracoForms/Backoffice/Common/SettingTypes + file.html + + + 43285028-e2d1-4b2b-b609-b317ad28b26a_password.html + /App_Plugins/UmbracoForms/Backoffice/Common/SettingTypes + password.html + + + pickers.connectionstring.controller.js + /App_Plugins/UmbracoForms/Backoffice/Common/SettingTypes + pickers.connectionstring.controller.js + + + pickers.connectionstring.html + /App_Plugins/UmbracoForms/Backoffice/Common/SettingTypes + pickers.connectionstring.html + + + pickers.contentwithxpath.controller.js + /App_Plugins/UmbracoForms/Backoffice/Common/SettingTypes + pickers.contentwithxpath.controller.js + + + pickers.contentwithxpath.html + /App_Plugins/UmbracoForms/Backoffice/Common/SettingTypes + pickers.contentwithxpath.html + + + pickers.datatype.controller.js + /App_Plugins/UmbracoForms/Backoffice/Common/SettingTypes + pickers.datatype.controller.js + + + pickers.datatype.html + /App_Plugins/UmbracoForms/Backoffice/Common/SettingTypes + pickers.datatype.html + + + pickers.documenttype.controller.js + /App_Plugins/UmbracoForms/Backoffice/Common/SettingTypes + pickers.documenttype.controller.js + + + pickers.documenttype.html + /App_Plugins/UmbracoForms/Backoffice/Common/SettingTypes + pickers.documenttype.html + + + 9914ab3f-255e-44e8-9d52-e60e22d1db6d_textarea.html + /App_Plugins/UmbracoForms/Backoffice/Common/SettingTypes + textarea.html + + + 56b20f99-e751-4d40-bade-1edf7ff5f983_textfield.html + /App_Plugins/UmbracoForms/Backoffice/Common/SettingTypes + textfield.html + + + textstring.html + /App_Plugins/UmbracoForms/Backoffice/Common/SettingTypes + textstring.html + + + Activity.html + /App_Plugins/UmbracoForms/Backoffice/Dashboards + Activity.html + + + Licensing.html + /App_Plugins/UmbracoForms/Backoffice/Dashboards + Licensing.html + + + YourForms.html + /App_Plugins/UmbracoForms/Backoffice/Dashboards + YourForms.html + + + activity.controller.js + /App_Plugins/UmbracoForms/Backoffice/Dashboards/Controllers + activity.controller.js + + + licensing.controller.js + /App_Plugins/UmbracoForms/Backoffice/Dashboards/Controllers + licensing.controller.js + + + yourforms.controller.js + /App_Plugins/UmbracoForms/Backoffice/Dashboards/Controllers + yourforms.controller.js + + + delete.html + /App_Plugins/UmbracoForms/Backoffice/DataSource + delete.html + + + edit.html + /App_Plugins/UmbracoForms/Backoffice/DataSource + edit.html + + + delete.controller.js + /App_Plugins/UmbracoForms/Backoffice/DataSource/controllers + delete.controller.js + + + edit.controller.js + /App_Plugins/UmbracoForms/Backoffice/DataSource/controllers + edit.controller.js + + + wizard.controller.js + /App_Plugins/UmbracoForms/Backoffice/DataSource/controllers + wizard.controller.js + + + wizard.html + /App_Plugins/UmbracoForms/Backoffice/DataSource/dialogs + wizard.html + + + create.html + /App_Plugins/UmbracoForms/Backoffice/Form + create.html + + + c0d2f039-3acb-47fb-9068-8d2050098db0_delete.html + /App_Plugins/UmbracoForms/Backoffice/Form + delete.html + + + a4fd64d8-aa3a-4753-b9a7-90378fa6b3df_edit.html + /App_Plugins/UmbracoForms/Backoffice/Form + edit.html + + + entries.html + /App_Plugins/UmbracoForms/Backoffice/Form + entries.html + + + workflows.html + /App_Plugins/UmbracoForms/Backoffice/Form + workflows.html + + + create.controller.js + /App_Plugins/UmbracoForms/Backoffice/Form/controllers + create.controller.js + + + b838c1b8-ef83-435f-9d91-09240d8f70a7_delete.controller.js + /App_Plugins/UmbracoForms/Backoffice/Form/controllers + delete.controller.js + + + 777a833e-1023-4dfe-9c6a-e8c372ea38a4_edit.controller.js + /App_Plugins/UmbracoForms/Backoffice/Form/controllers + edit.controller.js + + + entries.controller.js + /App_Plugins/UmbracoForms/Backoffice/Form/controllers + entries.controller.js + + + entriessettings.controller.js + /App_Plugins/UmbracoForms/Backoffice/Form/controllers + entriessettings.controller.js + + + fieldsettings.controller.js + /App_Plugins/UmbracoForms/Backoffice/Form/controllers + fieldsettings.controller.js + + + workflows.controller.js + /App_Plugins/UmbracoForms/Backoffice/Form/controllers + workflows.controller.js + + + workflows.dialog.controller.js + /App_Plugins/UmbracoForms/Backoffice/Form/controllers + workflows.dialog.controller.js + + + additem.html + /App_Plugins/UmbracoForms/Backoffice/Form/dialogs + additem.html + + + entriesdetail.html + /App_Plugins/UmbracoForms/Backoffice/Form/dialogs + entriesdetail.html + + + entriessettings.html + /App_Plugins/UmbracoForms/Backoffice/Form/dialogs + entriessettings.html + + + fieldsettings.html + /App_Plugins/UmbracoForms/Backoffice/Form/dialogs + fieldsettings.html + + + formsettings.html + /App_Plugins/UmbracoForms/Backoffice/Form/dialogs + formsettings.html + + + workflow.html + /App_Plugins/UmbracoForms/Backoffice/Form/dialogs + workflow.html + + + 753759e0-8867-4d52-a58d-1bac828d76c5_delete.html + /App_Plugins/UmbracoForms/Backoffice/PreValueSource + delete.html + + + a69b3d96-ed63-4bf3-b94e-0e74f351abe0_edit.html + /App_Plugins/UmbracoForms/Backoffice/PreValueSource + edit.html + + + 677731bc-3ecc-43d9-b949-dbb1eedf56d7_delete.controller.js + /App_Plugins/UmbracoForms/Backoffice/PreValueSource/controllers + delete.controller.js + + + 70d127e8-f15b-4afd-935d-a55754d22424_edit.controller.js + /App_Plugins/UmbracoForms/Backoffice/PreValueSource/controllers + edit.controller.js + + + formpicker.controller.js + /App_Plugins/UmbracoForms/Backoffice/PropertyEditors + formpicker.controller.js + + + formpicker.html + /App_Plugins/UmbracoForms/Backoffice/PropertyEditors + formpicker.html + + + umbraco.forms.css + /App_Plugins/UmbracoForms/css + umbraco.forms.css + + + 385baff5-cb75-441a-86e5-5121168a4fac.json + /App_Plugins/UmbracoForms/Data/forms + 385baff5-cb75-441a-86e5-5121168a4fac.json + + + commentform.json + /App_Plugins/UmbracoForms/Data/Templates + commentform.json + + + contactform.json + /App_Plugins/UmbracoForms/Data/Templates + contactform.json + + + 065b5cad-b43d-4390-aecb-166bdc677139.json + /App_Plugins/UmbracoForms/Data/Workflows + 065b5cad-b43d-4390-aecb-166bdc677139.json + + + 706acfaf-6f15-4a20-af43-f7eeb1f2761d.json + /App_Plugins/UmbracoForms/Data/Workflows + 706acfaf-6f15-4a20-af43-f7eeb1f2761d.json + + + 87895f0c-656a-4232-b41e-00fddb4840a0.json + /App_Plugins/UmbracoForms/Data/Workflows + 87895f0c-656a-4232-b41e-00fddb4840a0.json + + + umb-forms-content-picker.html + /App_Plugins/UmbracoForms/Directives + umb-forms-content-picker.html + + + umb-forms-designer.html + /App_Plugins/UmbracoForms/Directives + umb-forms-designer.html + + + umb-forms-prevalue-editor.html + /App_Plugins/UmbracoForms/Directives + umb-forms-prevalue-editor.html + + + umb-forms-regexpicker.html + /App_Plugins/UmbracoForms/Directives + umb-forms-regexpicker.html + + + icomoon.eot + /App_Plugins/UmbracoForms/Fonts + icomoon.eot + + + icomoon.svg + /App_Plugins/UmbracoForms/Fonts + icomoon.svg + + + icomoon.ttf + /App_Plugins/UmbracoForms/Fonts + icomoon.ttf + + + icomoon.woff + /App_Plugins/UmbracoForms/Fonts + icomoon.woff + + + close.png + /App_Plugins/UmbracoForms/Images + close.png + + + recaptcha.png + /App_Plugins/UmbracoForms/Images + recaptcha.png + + + succes-green.png + /App_Plugins/UmbracoForms/Images + succes-green.png + + + umbraco.forms.js + /App_Plugins/UmbracoForms/js + umbraco.forms.js + + + HtmlTable.cshtml + /App_Plugins/UmbracoForms/RazorTemplates + HtmlTable.cshtml + + + umbraco.forms.dashboards.less + /App_Plugins/UmbracoForms/Styles + umbraco.forms.dashboards.less + + + umbraco.forms.entries.less + /App_Plugins/UmbracoForms/Styles + umbraco.forms.entries.less + + + umbraco.forms.fonts.less + /App_Plugins/UmbracoForms/Styles + umbraco.forms.fonts.less + + + umbraco.forms.less + /App_Plugins/UmbracoForms/Styles + umbraco.forms.less + + + umbraco.forms.workflows.less + /App_Plugins/UmbracoForms/Styles + umbraco.forms.workflows.less + + + umbraco.gridview.less + /App_Plugins/UmbracoForms/Styles + umbraco.gridview.less + + + DataTables_json.xslt + /App_Plugins/UmbracoForms/Xslt + DataTables_json.xslt + + + DataTables_json_medtrust.xslt + /App_Plugins/UmbracoForms/Xslt + DataTables_json_medtrust.xslt + + + excel.xslt + /App_Plugins/UmbracoForms/Xslt + excel.xslt + + + Html.xslt + /App_Plugins/UmbracoForms/Xslt + Html.xslt + + + postAsXmlSample.xslt + /App_Plugins/UmbracoForms/Xslt + postAsXmlSample.xslt + + + sendXsltEmailSample.xslt + /App_Plugins/UmbracoForms/Xslt + sendXsltEmailSample.xslt + + + xml.xslt + /App_Plugins/UmbracoForms/Xslt + xml.xslt + + + UmbracoContourListComments.xslt + /App_Plugins/UmbracoForms/Xslt/Templates + UmbracoContourListComments.xslt + + + d337dddb-dd69-4fc1-a10d-7062a562596e_UmbracoContourListComments.xslt + /App_Plugins/UmbracoForms/Xslt/Templates/Schema2 + UmbracoContourListComments.xslt + + + grid.editors.config.js + /Config + grid.editors.config.js + + + + + Fanoe + 1.0.0 + MIT License + http://umbraco.com + + 3 + 0 + 0 + + + + Umbraco HQ + http://umbraco.com + + + + + + + + + + + + + + + #value#" + } + } + } + ] + }, + { + "grid": 12, + "allowAll": false, + "allowed": [ + "banner_tagline" + ], + "controls": [ + { + "value": "You're making great stuff with this thing", + "editor": { + "name": "Banner Tagline", + "alias": "banner_tagline", + "view": "textstring", + "icon": "icon-coin", + "config": { + "style": "font-size: 25px; line-height: 35px; font-weight: normal; text-align:center", + "markup": "

#value#

" + } + } + } + ] + } + ], + "styles": { + "background-image": "url(/media/1001/top-image.jpg)" + }, + "config": { + "class": "dark" + }, + "id": "2bd04b6a-0561-05c8-aa48-7ee47fde287f" + }, + { + "name": "Article full width", + "areas": [ + { + "grid": 12, + "allowAll": false, + "allowed": [ + "media_text_right", + "headline", + "abstract", + "paragraph", + "quote", + "media", + "code" + ], + "controls": [ + { + "value": "Something", + "editor": { + "name": "Headline", + "alias": "headline", + "view": "textstring", + "icon": "icon-coin", + "config": { + "style": "font-size: 36px; line-height: 45px; font-weight: bold", + "markup": "

#value#

" + } + } + }, + { + "value": "Sed porttitor lectus nibh. Nulla porttitor accumsan tincidunt. Curabitur non nulla sit amet nisl tempus convallis quis ac lectus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec velit neque, auctor sit amet aliquam vel, ullamcorper sit amet ligula. Cras ultricies ligula sed magna dictum porta. Pellentesque in ipsum id orci porta dapibus. Pellentesque in ipsum id orci porta dapibus.", + "editor": { + "name": "Abstract", + "alias": "abstract", + "view": "textstring", + "icon": "icon-coin", + "config": { + "style": "font-size: 20px; line-height: 45px; font-weight: light", + "markup": "

#value#

" + } + } + }, + { + "value": "Mauris blandit aliquet elit, eget tincidunt nibh pulvinar a. Quisque velit nisi, pretium ut lacinia in, elementum id enim. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec velit neque, auctor sit amet aliquam vel, ullamcorper sit amet ligula. Sed porttitor lectus nibh. Curabitur non nulla sit amet nisl tempus convallis quis ac lectus. Pellentesque in ipsum id orci porta dapibus. Vivamus suscipit tortor eget felis porttitor volutpat. Vivamus suscipit tortor eget felis porttitor volutpat. Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Curabitur aliquet quam id dui posuere blandit.", + "editor": { + "name": "Paragraph", + "alias": "paragraph", + "view": "textstring", + "icon": "icon-font", + "config": { + "style": "font-size: 16px; line-height: 20px; font-weight: light;", + "markup": "

#value#

" + } + } + }, + { + "value": "Mauris blandit aliquet elit, eget tincidunt nibh pulvinar a. Quisque velit nisi, pretium ut lacinia in, elementum id enim. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec velit neque, auctor sit amet aliquam vel, ullamcorper sit amet ligula. Sed porttitor lectus nibh. Curabitur non nulla sit amet nisl tempus convallis quis ac lectus. Pellentesque in ipsum id orci porta dapibus. Vivamus suscipit tortor eget felis porttitor volutpat. Vivamus suscipit tortor eget felis porttitor volutpat. Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Curabitur aliquet quam id dui posuere blandit.", + "editor": { + "name": "Paragraph", + "alias": "paragraph", + "view": "textstring", + "icon": "icon-font", + "config": { + "style": "font-size: 16px; line-height: 20px; font-weight: light;", + "markup": "

#value#

" + } + } + }, + { + "value": { + "focalPoint": { + "left": 0.5, + "top": 0.5 + }, + "id": 1072, + "image": "/media/1004/milkbath.jpg", + "thumbnail": "/umbraco/backoffice/UmbracoApi/Images/GetBigThumbnail?originalImagePath=%2Fmedia%2F1004%2Fmilkbath.jpg", + "headline": "Creepy dude in milk", + "paragraph": "Curabitur aliquet quam id dui posuere blandit. Curabitur non nulla sit amet nisl tempus convallis quis ac lectus. Curabitur aliquet quam id dui posuere blandit. Quisque velit nisi, pretium ut lacinia in, elementum id enim. Curabitu" + } + } + ] + } + ], + "styles": {}, + "config": { + "class": "light" + }, + "id": "7a2e4652-8523-3ade-053d-f7873c9c8020" + }, + { + "name": "Image wide", + "areas": [ + { + "grid": 12, + "allowAll": false, + "allowed": [ + "media_wide_cropped", + "media_wide" + ], + "controls": [ + { + "value": { + "focalPoint": { + "left": 0.5, + "top": 0.5 + }, + "id": 1073, + "image": "/media/1005/54619133.jpg", + "thumbnail": "/umbraco/backoffice/UmbracoApi/Images/GetBigThumbnail?originalImagePath=%2Fmedia%2F1005%2F54619133.jpg" + }, + "editor": { + "name": "Image wide", + "alias": "media_wide", + "view": "media", + "render": "media_wide", + "icon": "icon-picture" + } + } + ] + } + ], + "styles": {}, + "config": { + "class": "full" + }, + "id": "e6c0d256-ccec-5411-ca94-e584cb7f941d" + } + ] + } + ] +}]]> +
+
+ + + #value#" + } + } + } + ] + }, + { + "grid": 12, + "allowAll": false, + "allowed": [ + "banner_tagline" + ], + "controls": [ + { + "value": "ukgfs7 9 yfiugh fi7", + "editor": { + "name": "Banner Tagline", + "alias": "banner_tagline", + "view": "textstring", + "icon": "icon-coin", + "config": { + "style": "font-size: 25px; line-height: 35px; font-weight: normal; text-align:center", + "markup": "

#value#

" + } + } + } + ] + } + ], + "styles": {}, + "config": { + "class": "blue" + }, + "id": "63d1f52a-a6be-2459-a337-22f97cdb15bb" + }, + { + "name": "Article", + "areas": [ + { + "grid": 3, + "allowAll": false, + "allowed": [ + "media_round", + "paragraph" + ], + "controls": [ + { + "value": { + "focalPoint": { + "left": 0.5, + "top": 0.5 + }, + "id": 1069, + "image": "/media/1002/128.jpg", + "thumbnail": "/umbraco/backoffice/UmbracoApi/Images/GetBigThumbnail?originalImagePath=%2Fmedia%2F1002%2F128.jpg" + }, + "editor": { + "name": "Image rounded", + "alias": "media_round", + "view": "media", + "render": "/App_Plugins/Grid/Editors/Render/media_round.cshtml", + "icon": "icon-picture" + } + } + ] + }, + { + "grid": 1, + "allowAll": false, + "allowed": [], + "controls": [] + }, + { + "grid": 8, + "allowAll": false, + "allowed": [ + "headline", + "abstract", + "paragraph" + ], + "controls": [ + { + "value": "gdfsghdfgsfd", + "editor": { + "name": "Headline", + "alias": "headline", + "view": "textstring", + "icon": "icon-coin", + "config": { + "style": "font-size: 36px; line-height: 45px; font-weight: bold", + "markup": "

#value#

" + } + } + }, + { + "value": "Mauris blandit aliquet elit, eget tincidunt nibh pulvinar a. Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. Vivamus suscipit tortor eget felis porttitor volutpat. Curabitur non nulla sit amet nisl tempus convallis quis ac lectus. Donec rutrum congue leo eget malesuada. Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. Mauris blandit aliquet elit, eget tincidunt nibh pulvinar a. Donec sollicitudin molestie malesuada. Nulla quis lorem ut libero malesuada feugiat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec velit neque, auctor sit amet aliquam vel, ullamcorper sit amet ligula.", + "editor": { + "name": "Abstract", + "alias": "abstract", + "view": "textstring", + "icon": "icon-coin", + "config": { + "style": "font-size: 20px; line-height: 45px; font-weight: light", + "markup": "

#value#

" + } + } + }, + { + "value": "Mauris blandit aliquet elit, eget tincidunt nibh pulvinar a. Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. Vivamus suscipit tortor eget felis porttitor volutpat. Curabitur non nulla sit amet nisl tempus convallis quis ac lectus. Donec rutrum congue leo eget malesuada. Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. Mauris blandit aliquet elit, eget tincidunt nibh pulvinar a. Donec sollicitudin molestie malesuada. Nulla quis lorem ut libero malesuada feugiat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec velit neque, auctor sit amet aliquam vel, ullamcorper sit amet ligula.", + "editor": { + "name": "Paragraph", + "alias": "paragraph", + "view": "textstring", + "icon": "icon-font", + "config": { + "style": "font-size: 16px; line-height: 20px; font-weight: light;", + "markup": "

#value#

" + } + } + }, + { + "value": "Mauris blandit aliquet elit, eget tincidunt nibh pulvinar a. Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. Vivamus suscipit tortor eget felis porttitor volutpat. Curabitur non nulla sit amet nisl tempus convallis quis ac lectus. Donec rutrum congue leo eget malesuada. Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. Mauris blandit aliquet elit, eget tincidunt nibh pulvinar a. Donec sollicitudin molestie malesuada. Nulla quis lorem ut libero malesuada feugiat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec velit neque, auctor sit amet aliquam vel, ullamcorper sit amet ligula.", + "editor": { + "name": "Paragraph", + "alias": "paragraph", + "view": "textstring", + "icon": "icon-font", + "config": { + "style": "font-size: 16px; line-height: 20px; font-weight: light;", + "markup": "

#value#

" + } + } + } + ] + } + ], + "styles": {}, + "config": { + "class": "dark" + }, + "id": "651b07fa-77eb-9f44-5633-fabbf5e365eb" + }, + { + "name": "Playground", + "areas": [ + { + "grid": 12, + "editors": [ + "headline" + ], + "allowAll": false, + "controls": [ + { + "value": "Donec rutrum congue leo eget malesuada. Sed porttitor lectus nibh. Curabitur arcu erat, accumsan id imperdiet et, porttitor at sem. Pellentesque in ipsum id orci porta dapibus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque in ipsum id orci porta dapibus.", + "editor": { + "name": "Quote", + "alias": "quote", + "view": "textstring", + "icon": "icon-quote", + "config": { + "style": "border-left: 3px solid #ccc; padding: 10px; color: #ccc; font-family: serif; font-variant: italic; font-size: 18px", + "markup": "
#value#
" + } + } + } + ] + } + ], + "styles": {}, + "config": { + "class": "yellow" + }, + "id": "c9aabb6d-7787-ef69-f71c-a85c93c2f973" + }, + { + "name": "Image wide", + "areas": [ + { + "grid": 12, + "allowAll": false, + "allowed": [ + "media_wide_cropped", + "media_wide" + ], + "controls": [ + { + "value": { + "focalPoint": { + "left": 0.5, + "top": 0.5 + }, + "id": 1073, + "image": "/media/1005/54619133.jpg", + "thumbnail": "/umbraco/backoffice/UmbracoApi/Images/GetBigThumbnail?originalImagePath=%2Fmedia%2F1005%2F54619133.jpg" + }, + "editor": { + "name": "Image wide", + "alias": "media_wide", + "view": "media", + "render": "/App_Plugins/Grid/Editors/Render/media_wide.cshtml", + "icon": "icon-picture" + } + } + ] + } + ], + "styles": {}, + "config": { + "class": "full" + }, + "id": "73807b0e-a282-c391-7e40-ccc7eb9a3472" + } + ] + } + ] +}]]> +
+
+ + + #value#" + } + } + } + ] + }, + { + "grid": 6, + "allowAll": false, + "allowed": [ + "abstract", + "media", + "code", + "quote" + ], + "controls": [ + { + "value": "What ever happens this should be the best thing you have ever encountered. We are not even beginning to scratch the surface. The comming months will prove that our path is the righteous one and all who oppose will burn on the bonfires of vanity.", + "editor": { + "name": "Abstract", + "alias": "abstract", + "view": "textstring", + "icon": "icon-coin", + "config": { + "style": "font-size: 20px; line-height: 45px; font-weight: light", + "markup": "

#value#

" + } + } + } + ] + }, + { + "grid": 6, + "allowAll": false, + "allowed": [ + "quote", + "code", + "media" + ], + "controls": [] + } + ], + "id": "d8a63df0-4458-5e9d-778b-f30c8ce0c4a4" + } + ] + } + ] +}]]> +
+
+ + + #value#" + } + } + } + ] + } + ], + "id": "a3a2fa03-bdc2-fa37-44bf-4b23406e00a1" + }, + { + "name": "Image wide", + "areas": [ + { + "grid": 12, + "allowAll": false, + "allowed": [ + "media_wide_cropped", + "media_wide" + ], + "config": { + "class": "full" + }, + "styles": {}, + "controls": [ + { + "value": { + "focalPoint": { + "left": 0.5, + "top": 0.5 + }, + "id": 1073, + "image": "/media/1005/54619133.jpg", + "thumbnail": "/umbraco/backoffice/UmbracoApi/Images/GetBigThumbnail?originalImagePath=%2Fmedia%2F1005%2F54619133.jpg" + }, + "editor": { + "name": "Image wide", + "alias": "media_wide", + "view": "media", + "render": "/App_Plugins/Grid/Editors/Render/media_wide.cshtml", + "icon": "icon-picture" + } + } + ] + } + ], + "styles": {}, + "config": { + "class": "full" + }, + "id": "bc94a446-dd1a-2aa7-b7ed-77b1a4591618" + }, + { + "name": "Article two col", + "areas": [ + { + "grid": 12, + "allowAll": false, + "allowed": [ + "headline", + "quote" + ], + "controls": [ + { + "value": "This is the headline", + "editor": { + "name": "Headline", + "alias": "headline", + "view": "textstring", + "icon": "icon-coin", + "config": { + "style": "font-size: 36px; line-height: 45px; font-weight: bold", + "markup": "

#value#

" + } + } + } + ] + }, + { + "grid": 6, + "allowAll": false, + "allowed": [ + "abstract", + "media", + "code", + "quote", + "paragraph" + ], + "controls": [ + { + "value": "Proin eget tortor risus. Sed porttitor lectus nibh. Proin eget tortor risus. Curabitur arcu erat, accumsan id imperdiet et, porttitor at sem. Vivamus suscipit tortor eget felis porttitor volutpat. Donec rutrum congue leo eget malesuada. Praesent sapien massa, convallis a pellentesque nec, egestas non nisi.", + "editor": { + "name": "Abstract", + "alias": "abstract", + "view": "textstring", + "icon": "icon-coin", + "config": { + "style": "font-size: 16px; line-height: 20px; font-weight: bold;", + "markup": "

#value#

" + } + } + }, + { + "value": "Curabitur arcu erat, accumsan id imperdiet et, porttitor at sem. Donec rutrum congue leo eget malesuada. Curabitur arcu erat, accumsan id imperdiet et, porttitor at sem. Curabitur arcu erat, accumsan id imperdiet et, porttitor at sem. Vivamus suscipit tortor eget felis porttitor volutpat. Sed porttitor lectus nibh. Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. Nulla porttitor accumsan tincidunt. Donec rutrum congue leo eget malesuada.", + "editor": { + "name": "Paragraph", + "alias": "paragraph", + "view": "textstring", + "icon": "icon-font", + "config": { + "style": "font-size: 16px; line-height: 20px; font-weight: light;", + "markup": "

#value#

" + } + } + } + ] + }, + { + "grid": 6, + "allowAll": false, + "allowed": [ + "quote", + "code", + "media", + "paragraph" + ], + "controls": [ + { + "value": "Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec velit neque, auctor sit amet aliquam vel, ullamcorper sit amet ligula. Nulla porttitor accumsan tincidunt. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec velit neque, auctor sit amet aliquam vel, ullamcorper sit amet ligula. Quisque velit nisi, pretium ut lacinia in, elementum id enim. Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Mauris blandit aliquet elit, eget tincidunt nibh pulvinar a. Proin eget tortor risus. Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. Quisque velit nisi, pretium ut lacinia in, elementum id enim.", + "editor": { + "name": "Paragraph", + "alias": "paragraph", + "view": "textstring", + "icon": "icon-font", + "config": { + "style": "font-size: 16px; line-height: 20px; font-weight: light;", + "markup": "

#value#

" + } + } + }, + { + "value": { + "focalPoint": { + "left": 0.5, + "top": 0.5 + }, + "id": 1073, + "image": "/media/1005/54619133.jpg", + "thumbnail": "/umbraco/backoffice/UmbracoApi/Images/GetBigThumbnail?originalImagePath=%2Fmedia%2F1005%2F54619133.jpg" + }, + "editor": { + "name": "Image", + "alias": "media", + "view": "media", + "icon": "icon-picture" + } + } + ] + } + ], + "styles": {}, + "config": { + "class": "light" + }, + "id": "b6697e42-57de-0052-f592-8a688de00877" + } + ] + } + ] +}]]> +
+
+ + + #value#" + } + } + } + ] + }, + { + "grid": 12, + "allowAll": false, + "allowed": [ + "banner_tagline" + ], + "controls": [ + { + "value": "fdggds adegdgs dg sdsggds dgs dgs sgge egwagerwa gaewgwaearghef", + "editor": { + "name": "Banner Tagline", + "alias": "banner_tagline", + "view": "textstring", + "icon": "icon-coin", + "config": { + "style": "font-size: 25px; line-height: 35px; font-weight: normal; text-align:center", + "markup": "

#value#

" + } + } + } + ] + } + ], + "styles": {}, + "config": { + "class": "light" + }, + "id": "e051882a-7c75-c14c-747f-9304f0ff329c" + }, + { + "name": "Playground", + "areas": [ + { + "grid": 12, + "editors": [ + "headline" + ], + "allowAll": false, + "controls": [ + { + "value": "Curabitur arcu erat, accumsan id imperdiet et, porttitor at sem. Mauris blandit aliquet elit, eget tincidunt nibh pulvinar a. Vivamus suscipit tortor eget felis porttitor volutpat. Mauris blandit aliquet elit, eget tincidunt nibh pulvinar a. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent sapien massa, convallis a pellentesque nec, egestas non nisi. Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Donec rutrum congue leo eget malesuada.", + "editor": { + "name": "Quote", + "alias": "quote", + "view": "textstring", + "icon": "icon-quote", + "config": { + "style": "border-left: 3px solid #ccc; padding: 10px; color: #ccc; font-family: serif; font-variant: italic; font-size: 18px", + "markup": "
#value#
" + } + } + } + ] + } + ], + "styles": {}, + "config": { + "class": "yellow" + }, + "id": "a8c2e77f-1f2c-4438-69e9-d34ed015e5df" + }, + { + "name": "Playground", + "areas": [ + { + "grid": 12, + "editors": [ + "headline" + ], + "allowAll": false, + "controls": [ + { + "value": { + "focalPoint": { + "left": 0.5, + "top": 0.5 + }, + "id": 1104, + "image": "/media/1008/boat_2.jpg", + "thumbnail": "/umbraco/backoffice/UmbracoApi/Images/GetBigThumbnail?originalImagePath=%2Fmedia%2F1008%2Fboat_2.jpg" + }, + "editor": { + "name": "Image wide", + "alias": "media_wide", + "view": "media", + "render": "/App_Plugins/Grid/Editors/Render/media_wide.cshtml", + "icon": "icon-picture" + } + } + ] + } + ], + "styles": {}, + "config": { + "class": "full triangle" + }, + "id": "60ae1388-397d-78cf-3f76-e765c1f6f8ba" + }, + { + "name": "Playground", + "areas": [ + { + "grid": 12, + "editors": [ + "headline" + ], + "allowAll": false, + "controls": [ + { + "value": "Curabitur arcu erat, accumsan id imperdiet et, porttitor at sem. Mauris blandit aliquet elit, eget tincidunt nibh pulvinar a. Vivamus suscipit tortor eget felis porttitor volutpat. Mauris blandit aliquet elit, eget tincidunt nibh pulvinar a. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent sapien massa, convallis a pellentesque nec, egestas non nisi. Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Donec rutrum congue leo eget malesuada.", + "editor": { + "name": "Quote", + "alias": "quote", + "view": "textstring", + "icon": "icon-quote", + "config": { + "style": "border-left: 3px solid #ccc; padding: 10px; color: #ccc; font-family: serif; font-variant: italic; font-size: 18px", + "markup": "
#value#
" + } + } + } + ] + } + ], + "styles": {}, + "config": { + "class": "blue" + }, + "id": "14da9201-9f31-1146-21e9-7d1aa64ec86f" + }, + { + "name": "Playground", + "areas": [ + { + "grid": 12, + "editors": [ + "headline" + ], + "allowAll": false, + "controls": [ + { + "value": "", + "editor": { + "name": "Embed", + "alias": "embed", + "view": "embed", + "icon": "icon-movie-alt" + } + } + ] + } + ], + "id": "2f19df8b-4dcc-4ca0-7e51-690ed3866d10" + } + ] + } + ] +}]]> +
+
+ + + #value#" + } + } + }, + { + "value": "This is a standard thingie that works for all conweågdopsjv", + "editor": { + "name": "Banner Tagline", + "alias": "banner_tagline", + "view": "textstring", + "icon": "icon-coin", + "config": { + "style": "font-size: 25px; line-height: 35px; font-weight: normal; text-align:center", + "markup": "

#value#

" + } + } + } + ] + } + ], + "styles": {}, + "config": { + "class": "light" + }, + "id": "62749619-e9ec-4045-be5e-45898cfdaa40" + }, + { + "name": "Article", + "areas": [ + { + "grid": 3, + "allowAll": false, + "allowed": [ + "media_round", + "paragraph" + ], + "controls": [ + { + "value": { + "focalPoint": { + "left": 0.61691542288557211, + "top": 0.84333333333333338 + }, + "id": 1072, + "image": "/media/1004/milkbath.jpg", + "thumbnail": "/umbraco/backoffice/UmbracoApi/Images/GetBigThumbnail?originalImagePath=%2Fmedia%2F1004%2Fmilkbath.jpg" + }, + "editor": { + "name": "Image rounded", + "alias": "media_round", + "view": "media", + "render": "/App_Plugins/Grid/Editors/Render/media_round.cshtml", + "icon": "icon-picture" + } + } + ] + }, + { + "grid": 1, + "allowAll": false, + "allowed": [], + "controls": [] + }, + { + "grid": 8, + "allowAll": false, + "allowed": [ + "headline", + "abstract", + "paragraph" + ], + "controls": [ + { + "value": "dsgrefhbs rb fshbh", + "editor": { + "name": "Headline", + "alias": "headline", + "view": "textstring", + "icon": "icon-coin", + "config": { + "style": "font-size: 36px; line-height: 45px; font-weight: bold", + "markup": "

#value#

" + } + } + }, + { + "value": "Nulla porttitor accumsan tincidunt. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sollicitudin molestie malesuada. Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + "editor": { + "name": "Abstract", + "alias": "abstract", + "view": "textstring", + "icon": "icon-coin", + "config": { + "style": "font-size: 16px; line-height: 20px; font-weight: bold;", + "markup": "

#value#

" + } + } + }, + { + "value": "Nulla quis lorem ut libero malesuada feugiat. Cras ultricies ligula sed magna dictum porta. Curabitur arcu erat, accumsan id imperdiet et, porttitor at sem. Curabitur non nulla sit amet nisl tempus convallis quis ac lectus. Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Quisque velit nisi, pretium ut lacinia in, elementum id enim. Donec sollicitudin molestie malesuada. Mauris blandit aliquet elit, eget tincidunt nibh pulvinar a. Praesent sapien massa, convallis a pellentesque nec, egestas non nisi. Nulla porttitor accumsan tincidunt.", + "editor": { + "name": "Paragraph", + "alias": "paragraph", + "view": "textstring", + "icon": "icon-font", + "config": { + "style": "font-size: 16px; line-height: 20px; font-weight: light;", + "markup": "

#value#

" + } + } + }, + { + "value": "Pellentesque in ipsum id orci porta dapibus. Quisque velit nisi, pretium ut lacinia in, elementum id enim. Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. Praesent sapien massa, convallis a pellentesque nec, egestas non nisi. Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Nulla quis lorem ut libero malesuada feugiat. Nulla quis lorem ut libero malesuada feugiat.", + "editor": { + "name": "Paragraph", + "alias": "paragraph", + "view": "textstring", + "icon": "icon-font", + "config": { + "style": "font-size: 16px; line-height: 20px; font-weight: light;", + "markup": "

#value#

" + } + } + } + ] + } + ], + "styles": {}, + "config": { + "class": "light" + }, + "id": "ac4f7103-d72e-69f0-efef-501843fe4b93" + }, + { + "name": "Image wide", + "areas": [ + { + "grid": 12, + "allowAll": false, + "allowed": [ + "media_wide_cropped", + "media_wide" + ], + "controls": [ + { + "value": { + "focalPoint": { + "left": 0.5, + "top": 0.5 + }, + "id": 1068, + "image": "/media/1001/top-image.jpg", + "thumbnail": "/umbraco/backoffice/UmbracoApi/Images/GetBigThumbnail?originalImagePath=%2Fmedia%2F1001%2Ftop-image.jpg" + }, + "editor": { + "name": "Image wide", + "alias": "media_wide", + "view": "media", + "render": "/App_Plugins/Grid/Editors/Render/media_wide.cshtml", + "icon": "icon-picture" + } + } + ] + } + ], + "styles": {}, + "config": { + "class": "full triangle" + }, + "id": "b8cfa16d-98d4-e156-6632-d5c606844f9f" + }, + { + "name": "Playground", + "areas": [ + { + "grid": 12, + "editors": [ + "headline" + ], + "allowAll": false, + "controls": [ + { + "value": "Pellentesque in ipsum id orci porta dapibus. Quisque velit nisi, pretium ut lacinia in, elementum id enim. Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. Praesent sapien massa, convallis a pellentesque nec, egestas non nisi. Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Nulla quis lorem ut libero malesuada feugiat. Nulla quis lorem ut libero malesuada feugiat.", + "editor": { + "name": "Quote", + "alias": "quote", + "view": "textstring", + "icon": "icon-quote", + "config": { + "style": "border-left: 3px solid #ccc; padding: 10px; color: #ccc; font-family: serif; font-variant: italic; font-size: 18px", + "markup": "
#value#
" + } + } + } + ] + } + ], + "styles": {}, + "config": { + "class": "yellow" + }, + "id": "2e342d39-fe7b-9636-2b6a-030c87a7b820" + } + ] + } + ] +}]]> +
+
+
+ + + + + + + + + + + + +
+
+
+ + + + Master + Master + icon-desktop + folder.png + + False + False + + + + Master + + + Home + + + + Site title + siteTitle + Umbraco.Textbox + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + + False + + + + + Site Description + siteDescription + Umbraco.Textbox + 0cc0eba1-9960-42c9-bf9b-60e150b429ae + + False + + + + + Logo + logo + Umbraco.ImageCropper + 1df9f033-e6d4-451f-b8d2-e0cbc50a836f + + False + + + + + + + + + Blogpost + Blogpost + icon-edit + folder.png + + False + False + Master + + + + Blogpost + + + + + Blogpost + blogpost + Umbraco.Grid + a3785f08-73d5-406b-855f-8e52805c22e2 + Blogpost + False + + + + + + + 13 + Blogpost + 0 + + + + + + Blogpost Repository + BlogpostRepository + .sprTreeFolder + folder.png + + False + True + + + + + Blogpost + + + + + + + Home + Home + icon-home + folder.png + + True + False + Master + + + + Home + + + LandingPage + BlogpostRepository + + + + + + + Landing page + LandingPage + icon-stamp + folder.png + + False + False + Master + + + + TextPage + + + TextPage + + + + Grid Blogpost + gridBlogpost + Umbraco.Grid + a3785f08-73d5-406b-855f-8e52805c22e2 + Post + False + + + + + + + 12 + Post + 0 + + + + + + Text page + TextPage + icon-document + folder.png + + False + False + Master + + + + TextPage + + + + + + + + + + + + + + + bootstrap.min + bootstrap.min + + + + + + fanoe + fanoe + + ul { + padding: 20px; + position: relative; + display: inline-table; + width: 100%; +} + +nav > ul li { + position: relative; + font-family: "Lato", sans-serif; +} + +nav > ul li a { + display: inline-block; + padding: 25px; + font-size: 20px; + font-weight: 500; + color: rgba(255, 255, 255, 0.75); +} + +nav > ul li a:hover { + color: #fff; +} + +nav > ul li:after { + content: ""; + clear: both; + display: block; +} + +nav li > ul { + position: absolute; +} + +nav li > ul li { + float: none; + display: block; + position: relative; +} + +nav li > ul li a { + font-size: 17px; + padding: 15px 15px; + font-weight: 500; + color: rgba(255, 255, 255, 0.8); +} + +nav li > ul li a:hover { + color: #fff; +} + +nav li > ul > li > ul { + left: 100%; + top: 0; + display: none; +} + +.dark, +.light, +.yellow, +.blue, +.purple { + display: block; + margin: 0 auto; + padding: 50px 0; + background-size: cover; + background-repeat: no-repeat; + background-position: center center; + color: #000; +} + +.dark .row, +.light .row, +.yellow .row, +.blue .row, +.purple .row { + margin: 0; +} + +.dark .row div[class^="col-"], +.light .row div[class^="col-"], +.yellow .row div[class^="col-"], +.blue .row div[class^="col-"], +.purple .row div[class^="col-"] { + margin-bottom: 50px; +} + +.dark p, +.light p, +.yellow p, +.blue p, +.purple p, +.dark a, +.light a, +.yellow a, +.blue a, +.purple a, +.dark blockquote, +.light blockquote, +.yellow blockquote, +.blue blockquote, +.purple blockquote, +.dark li, +.light li, +.yellow li, +.blue li, +.purple li { + font-family: "Merriweather", serif; +} + +.dark h1, +.light h1, +.yellow h1, +.blue h1, +.purple h1 { + margin-bottom: 10px; +} + +.dark h2, +.light h2, +.yellow h2, +.blue h2, +.purple h2, +.dark h3, +.light h3, +.yellow h3, +.blue h3, +.purple h3 { + margin: 0 0 15px; +} + +.dark p, +.light p, +.yellow p, +.blue p, +.purple p { + letter-spacing: 0.2px; + margin: 0 auto; +} + +.dark p + *, +.light p + *, +.yellow p + *, +.blue p + *, +.purple p + * { + margin-top: 20px; +} + +.dark a, +.light a, +.yellow a, +.blue a, +.purple a { + position: relative; + display: inline-block; +} + +.dark img, +.light img, +.yellow img, +.blue img, +.purple img { + padding: 20px 0; + max-width: 100%; + width: auto; + height: auto; + margin: 30px 0; +} + +.dark li, +.light li, +.yellow li, +.blue li, +.purple li { + padding: 2px 0 0; +} + +footer ul { + margin: 15px auto 0; + text-align: center; +} + +footer ul li { + position: relative; + display: block; + text-align: left; + padding: 0 0 5px 0; + margin: 0; + line-height: 1; +} + +footer.dark a { + color: rgba(255, 255, 255, 0.6) !important; + font-size: 1rem; + border-bottom: 1px solid transparent; + padding-bottom: 3px; + font-family: "Lato", sans-serif; + font-weight: 100; +} + +footer.dark a:hover { + color: rgba(255, 255, 255, 0.9) !important; + border-bottom: 1px solid #fff !important; + padding-bottom: 3px; + text-decoration: none; +} + +ul { + margin-left: 20px; +} + +img { + max-width: 100%; + height: auto; +} + +blockquote { + padding: 30px 10px; + text-align: center; + position: relative; + font-size: 1.3rem; +} + +* + ul { + margin-bottom: 30px; +} + +.equal { + display: table; + table-layout: fixed; +} + +.equal .vcenter { + display: table-cell; + vertical-align: middle; +} + +.full { + margin: 0; + padding: 0 !important; + overflow: hidden; +} + +.full * { + max-width: 100%; + width: 100%; + height: auto; + margin: 0 !important; + padding: 0 !important; + line-height: 0; +} + +.full .wide { + width: 100%; + height: auto; +} + +.small { + font-size: .85rem; +} + +code, +pre { + overflow: auto; + padding: 6px 10px; + border-radius: 3px; + background-color: rgba(248, 248, 248, 0.3); + font-size: .9rem; + font-family: "Courier 10 Pitch", Courier, monospace; + line-height: 2.3; +} + +.gallery .row > div { + margin-bottom: 2%; +} + +.gallery img { + margin: 0; +} + +.video-wrapper { + margin-top: 25px; + position: relative; + padding-bottom: 56.25%; + padding-top: 25px; + height: 0; +} + +.video-wrapper iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +@media (min-width: 768px) { + +.dark, +.light, +.yellow, +.blue, +.purple { + padding: 80px 0; +} + +.dark img, +.light img, +.yellow img, +.blue img, +.purple img { + padding: 0; +} + +} + +@media (min-width: 992px) { + +.triangle + .dark:before { + border-color: transparent transparent transparent #1c1c1e; +} + +.triangle + .light:before { + border-color: transparent transparent transparent #fff; +} + +.triangle + .yellow:before { + border-color: transparent transparent transparent #FFCC01; +} + +.triangle + .blue:before { + border-color: transparent transparent transparent #51c1f5; +} + +.triangle + .purple:before { + border-color: transparent transparent transparent #C2357B; +} + +header { + padding: 15px 0; +} + +header .container { + max-width: 960px; + padding: 0 20px; +} + +#toggle { + display: none; +} + +nav { + margin: 0 auto; + background: none; + width: 100%; +} + +nav > ul { + padding-right: 0; + width: auto; +} + +nav > ul li { + float: left; +} + +nav > ul li a { + font-size: 15px; +} + +nav > ul li:last-child a { + padding-right: 0; +} + +nav li > ul { + left: 0; + top: 75%; + display: none; + white-space: nowrap; + height: auto; + margin-bottom: 0; + margin-left: 10px; + text-align: left; + background: rgba(0, 0, 0, 0.8); + padding: 5px 8px 5px 0; +} + +nav li > ul li a { + padding: 8px 15px; + font-size: 15px; + color: rgba(255, 255, 255, 0.8); +} + +nav .has-child:hover > .sublevel { + display: block; +} + +.dark .row div[class^="col-"], +.light .row div[class^="col-"], +.yellow .row div[class^="col-"], +.blue .row div[class^="col-"], +.purple .row div[class^="col-"] { + margin-bottom: 20px; +} + +.triangle { + position: relative; +} + +.triangle img { + position: relative; + z-index: 100; +} + +.triangle:before { + content: ""; + display: block; + width: 0; + height: 0; + border-style: solid; + border-width: 0 0 150px 100vw; + border-color: inherit; + position: absolute; + z-index: 120; +} + +.triangle + .light, +.triangle + .dark, +.triangle + .blue, +.triangle + .yellow, +.triangle + .purple { + position: relative; +} + +.triangle + .light:before, +.triangle + .dark:before, +.triangle + .blue:before, +.triangle + .yellow:before, +.triangle + .purple:before { + content: ""; + display: block; + width: 0; + height: 0; + border-style: solid; + border-width: 150px 0 0 100vw; + border-color: none; + position: absolute; + z-index: 120; + top: -150px; +} + +} + +@media (max-width: 992px) { + +body { + width: 100vw; + overflow-x: hidden; +} + +nav { + -webkit-transform: translateX(100%); + -ms-transform: translateX(100%); + transform: translateX(100%); + opacity: 1; + transition: -webkit-transform 300ms ease-in-out, opacity 300ms ease-in-out; + transition: transform 300ms ease-in-out, opacity 300ms ease-in-out; + text-align: center; + position: absolute; + top: 144px; +} + +nav.open { + opacity: 1; + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); +} + +nav > ul { + margin: 0; +} + +nav > ul li:first-child a { + padding: 10px 25px; +} + +nav > ul li a { + padding: 25px 25px 10px 25px; + color: rgba(255, 255, 255, 0.6); +} + +nav > ul li.active > a { + color: #fff; + font-weight: 700; +} + +nav > ul li.active ul { + -webkit-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + height: 120%; +} + +nav li > ul { + -webkit-transform: scale(0); + -ms-transform: scale(0); + transform: scale(0); + position: relative; + text-align: center; + transition: all 300ms ease-in-out; + height: 0; + margin: 0; +} + +} + +@media (max-width: 992px) and (max-width: 992px) { + +nav > ul li.active ul li a { + color: #fff; + font-weight: 500; +} + +} +]]> + + + + style + style + + + + + + + + Insert form + umbracoforms.RenderForm + + + + + + + True + 0 + ~/Views/MacroPartials/InsertUmbracoForm.cshtml + + + + + + Render Forms Scripts + umbracoforms.RenderScripts + + + + + + + False + 0 + ~/Views/MacroPartials/RenderUmbracoFormScripts.cshtml + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/src/Umbraco.Tests/Services/Importing/ImportResources.Designer.cs b/src/Umbraco.Tests/Services/Importing/ImportResources.Designer.cs index c840f08842..bcc287e2ae 100644 --- a/src/Umbraco.Tests/Services/Importing/ImportResources.Designer.cs +++ b/src/Umbraco.Tests/Services/Importing/ImportResources.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.18051 +// Runtime Version:4.0.30319.34209 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -88,6 +88,88 @@ namespace Umbraco.Tests.Services.Importing { } } + /// + /// Looks up a localized string similar to <?xml version="1.0" encoding="UTF-8" standalone="no"?> + ///<umbPackage> + /// <files /> + /// <info> + /// <package> + /// <name>CheckboxListTest</name> + /// <version>1</version> + /// <license url="http://www.opensource.org/licenses/mit-license.php">MIT license</license> + /// <url>1</url> + /// <requirements> + /// <major>3</major> + /// <minor>0</minor> + /// <patch>0</patch> + /// </requirements> + /// </package> + /// <author> + /// <name>1</name> + /// <website>1</website> + /// </author> + /// <r [rest of string was truncated]";. + /// + internal static string CheckboxList_Content_Package_LegacyIds { + get { + return ResourceManager.GetString("CheckboxList_Content_Package_LegacyIds", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to <?xml version="1.0" encoding="UTF-8" standalone="no"?> + ///<umbPackage> + /// <files /> + /// <info> + /// <package> + /// <name>Compositions Packaged</name> + /// <version>1.0</version> + /// <license url="http://opensource.org/licenses/MIT">MIT License</license> + /// <url>http://blog.sitereactor.dk</url> + /// <requirements> + /// <major>3</major> + /// <minor>0</minor> + /// <patch>0</patch> + /// </requirements> + /// </package> + /// <author> + /// <name>Morten Christensen</name> + /// <website>h [rest of string was truncated]";. + /// + internal static string CompositionsTestPackage { + get { + return ResourceManager.GetString("CompositionsTestPackage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to <?xml version="1.0" encoding="UTF-8" standalone="no"?> + ///<umbPackage> + /// <files /> + /// <info> + /// <package> + /// <name>Composite Test</name> + /// <version>dfsfd</version> + /// <license url="http://opensource.org/licenses/MIT">MIT License</license> + /// <url>ddsff</url> + /// <requirements> + /// <major>3</major> + /// <minor>0</minor> + /// <patch>0</patch> + /// </requirements> + /// </package> + /// <author> + /// <name>fsdfds</name> + /// <website>sfdf</website> + /// </author> + /// <rea [rest of string was truncated]";. + /// + internal static string CompositionsTestPackage_Random { + get { + return ResourceManager.GetString("CompositionsTestPackage_Random", resourceCulture); + } + } + /// /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8" ?> ///<umbPackage> @@ -117,28 +199,28 @@ namespace Umbraco.Tests.Services.Importing { /// /// Looks up a localized string similar to <?xml version="1.0" encoding="UTF-8" standalone="no"?> ///<umbPackage> - /// <files /> - /// <info> - /// <package> - /// <name>CheckboxListTest</name> - /// <version>1</version> - /// <license url="http://www.opensource.org/licenses/mit-license.php">MIT license</license> - /// <url>1</url> - /// <requirements> - /// <major>3</major> - /// <minor>0</minor> - /// <patch>0</patch> - /// </requirements> - /// </package> - /// <author> - /// <name>1</name> - /// <website>1</website> - /// </author> - /// <r [rest of string was truncated]";. + /// <files> + /// <file> + /// <guid>bootstrap.min.js</guid> + /// <orgPath>/js</orgPath> + /// <orgName>bootstrap.min.js</orgName> + /// </file> + /// <file> + /// <guid>jquery.min.js</guid> + /// <orgPath>/js</orgPath> + /// <orgName>jquery.min.js</orgName> + /// </file> + /// <file> + /// <guid>top-image.jpg</guid> + /// <orgPath>/Media/1001</orgPath> + /// <orgName>top-image.jpg</orgName> + /// </file> + /// <file> + /// <guid>top-im [rest of string was truncated]";. /// - internal static string CheckboxList_Content_Package_LegacyIds { + internal static string Fanoe_Package { get { - return ResourceManager.GetString("CheckboxList_Content_Package_LegacyIds", resourceCulture); + return ResourceManager.GetString("Fanoe_Package", resourceCulture); } } diff --git a/src/Umbraco.Tests/Services/Importing/ImportResources.resx b/src/Umbraco.Tests/Services/Importing/ImportResources.resx index dd47151b73..f6a447531a 100644 --- a/src/Umbraco.Tests/Services/Importing/ImportResources.resx +++ b/src/Umbraco.Tests/Services/Importing/ImportResources.resx @@ -148,4 +148,13 @@ dictionary-package.xml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + compositionstestpackage.xml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + + compositionstestpackage-random.xml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + + fanoe-package.xml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + \ No newline at end of file diff --git a/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs b/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs index bec18c23c3..215f24e375 100644 --- a/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs +++ b/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Xml.Linq; using NUnit.Framework; @@ -210,6 +211,30 @@ namespace Umbraco.Tests.Services.Importing Assert.That(templates.Any(), Is.True); } + [Test] + public void PackagingService_Can_Import_Fanoe_Starterkit_ContentTypes_And_Templates_Xml() + { + // Arrange + string strXml = ImportResources.Fanoe_Package; + var xml = XElement.Parse(strXml); + var dataTypeElement = xml.Descendants("DataTypes").First(); + var templateElement = xml.Descendants("Templates").First(); + var docTypeElement = xml.Descendants("DocumentTypes").First(); + + // Act + var dataTypeDefinitions = ServiceContext.PackagingService.ImportDataTypeDefinitions(dataTypeElement); + var templates = ServiceContext.PackagingService.ImportTemplates(templateElement); + var contentTypes = ServiceContext.PackagingService.ImportContentTypes(docTypeElement); + var numberOfDocTypes = (from doc in docTypeElement.Elements("DocumentType") select doc).Count(); + + //Assert - Re-Import contenttypes doesn't throw + Assert.DoesNotThrow(() => ServiceContext.PackagingService.ImportContentTypes(docTypeElement)); + Assert.That(contentTypes.Count(), Is.EqualTo(numberOfDocTypes)); + Assert.That(dataTypeDefinitions, Is.Not.Null); + Assert.That(dataTypeDefinitions.Any(), Is.True); + Assert.That(templates.Any(), Is.True); + } + [Test] public void PackagingService_Can_Import_Content_Package_Xml() { @@ -554,6 +579,60 @@ namespace Umbraco.Tests.Services.Importing } } + [Test] + public void PackagingService_Can_Import_Package_With_Compositions() + { + // Arrange + string strXml = ImportResources.CompositionsTestPackage; + var xml = XElement.Parse(strXml); + var templateElement = xml.Descendants("Templates").First(); + var docTypeElement = xml.Descendants("DocumentTypes").First(); + var packagingService = ServiceContext.PackagingService; + + // Act + var templates = packagingService.ImportTemplates(templateElement); + var contentTypes = packagingService.ImportContentTypes(docTypeElement); + var numberOfDocTypes = (from doc in docTypeElement.Elements("DocumentType") select doc).Count(); + + // Assert + Assert.That(contentTypes, Is.Not.Null); + Assert.That(contentTypes.Any(), Is.True); + Assert.That(contentTypes.Count(), Is.EqualTo(numberOfDocTypes)); + Assert.That(contentTypes.Count(x => x.ParentId == -1), Is.EqualTo(3)); + + var textpage = contentTypes.First(x => x.Alias.Equals("umbTextyPage")); + Assert.That(textpage.ParentId, Is.Not.EqualTo(-1)); + Assert.That(textpage.ContentTypeComposition.Count(), Is.EqualTo(3)); + Assert.That(textpage.ContentTypeCompositionExists("umbMaster"), Is.True); + Assert.That(textpage.ContentTypeCompositionExists("Meta"), Is.True); + Assert.That(textpage.ContentTypeCompositionExists("Seo"), Is.True); + } + + [Test] + public void PackagingService_Can_Import_Package_With_Compositions_Ordered() + { + // Arrange + string strXml = ImportResources.CompositionsTestPackage_Random; + var xml = XElement.Parse(strXml); + var docTypeElement = xml.Descendants("DocumentTypes").First(); + var packagingService = ServiceContext.PackagingService; + + // Act + var contentTypes = packagingService.ImportContentTypes(docTypeElement); + var numberOfDocTypes = (from doc in docTypeElement.Elements("DocumentType") select doc).Count(); + + // Assert + Assert.That(contentTypes, Is.Not.Null); + Assert.That(contentTypes.Any(), Is.True); + Assert.That(contentTypes.Count(), Is.EqualTo(numberOfDocTypes)); + + var testContentType = contentTypes.First(x => x.Alias.Equals("CompositeTest")); + Assert.That(testContentType.ContentTypeComposition.Count(), Is.EqualTo(3)); + Assert.That(testContentType.ContentTypeCompositionExists("Content"), Is.True); + Assert.That(testContentType.ContentTypeCompositionExists("Meta"), Is.True); + Assert.That(testContentType.ContentTypeCompositionExists("Seo"), Is.True); + } + private void AddLanguages() { var norwegian = new Core.Models.Language("nb-NO"); diff --git a/src/Umbraco.Tests/Services/LocalizedTextServiceTests.cs b/src/Umbraco.Tests/Services/LocalizedTextServiceTests.cs new file mode 100644 index 0000000000..97ed516846 --- /dev/null +++ b/src/Umbraco.Tests/Services/LocalizedTextServiceTests.cs @@ -0,0 +1,384 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; +using NUnit.Framework; +using Umbraco.Core.Services; + +namespace Umbraco.Tests.Services +{ + [TestFixture] + public class LocalizedTextServiceTests + { + [Test] + public void Using_Dictionary_Gets_All_Stored_Values() + { + var culture = CultureInfo.GetCultureInfo("en-US"); + var txtService = new LocalizedTextService( + new Dictionary>> + { + { + culture, new Dictionary> + { + { + "testArea1", new Dictionary + { + {"testKey1", "testValue1"}, + {"testKey2", "testValue2"} + } + }, + { + "testArea2", new Dictionary + { + {"blah1", "blahValue1"}, + {"blah2", "blahValue2"} + } + }, + } + } + }); + + var result = txtService.GetAllStoredValues(culture); + + Assert.AreEqual(4, result.Count()); + Assert.AreEqual("testArea1/testKey1", result.ElementAt(0).Key); + Assert.AreEqual("testArea1/testKey2", result.ElementAt(1).Key); + Assert.AreEqual("testArea2/blah1", result.ElementAt(2).Key); + Assert.AreEqual("testArea2/blah2", result.ElementAt(3).Key); + Assert.AreEqual("testValue1", result["testArea1/testKey1"]); + Assert.AreEqual("testValue2", result["testArea1/testKey2"]); + Assert.AreEqual("blahValue1", result["testArea2/blah1"]); + Assert.AreEqual("blahValue2", result["testArea2/blah2"]); + + } + + [Test] + public void Using_XDocument_Gets_All_Stored_Values() + { + var culture = CultureInfo.GetCultureInfo("en-US"); + var txtService = new LocalizedTextService( + new Dictionary> + { + { + culture, new Lazy(() => new XDocument( + new XElement("language", + new XElement("area", new XAttribute("alias", "testArea1"), + new XElement("key", new XAttribute("alias", "testKey1"), "testValue1"), + new XElement("key", new XAttribute("alias", "testKey2"), "testValue2")), + new XElement("area", new XAttribute("alias", "testArea2"), + new XElement("key", new XAttribute("alias", "blah1"), "blahValue1"), + new XElement("key", new XAttribute("alias", "blah2"), "blahValue2"))))) + } + }); + + var result = txtService.GetAllStoredValues(culture); + + Assert.AreEqual(4, result.Count()); + Assert.AreEqual("testArea1/testKey1", result.ElementAt(0).Key); + Assert.AreEqual("testArea1/testKey2", result.ElementAt(1).Key); + Assert.AreEqual("testArea2/blah1", result.ElementAt(2).Key); + Assert.AreEqual("testArea2/blah2", result.ElementAt(3).Key); + Assert.AreEqual("testValue1", result["testArea1/testKey1"]); + Assert.AreEqual("testValue2", result["testArea1/testKey2"]); + Assert.AreEqual("blahValue1", result["testArea2/blah1"]); + Assert.AreEqual("blahValue2", result["testArea2/blah2"]); + + } + + + [Test] + public void Using_XDocument_Gets_All_Stored_Values_With_Duplicates() + { + var culture = CultureInfo.GetCultureInfo("en-US"); + var txtService = new LocalizedTextService( + new Dictionary> + { + { + culture, new Lazy(() => new XDocument( + new XElement("language", + new XElement("area", new XAttribute("alias", "testArea1"), + new XElement("key", new XAttribute("alias", "testKey1"), "testValue1"), + new XElement("key", new XAttribute("alias", "testKey1"), "testValue1"))))) + } + }); + + var result = txtService.GetAllStoredValues(culture); + + Assert.AreEqual(1, result.Count()); + + } + + [Test] + public void Using_Dictionary_Returns_Text_With_Area() + { + var culture = CultureInfo.GetCultureInfo("en-US"); + var txtService = new LocalizedTextService( + new Dictionary>> + { + { + culture, new Dictionary> + { + { + "testArea", new Dictionary + { + {"testKey", "testValue"} + } + } + } + } + }); + + var result = txtService.Localize("testArea/testKey", culture); + + Assert.AreEqual("testValue", result); + } + + [Test] + public void Using_Dictionary_Returns_Text_Without_Area() + { + var culture = CultureInfo.GetCultureInfo("en-US"); + var txtService = new LocalizedTextService( + new Dictionary>> + { + { + culture, new Dictionary> + { + { + "testArea", new Dictionary + { + {"testKey", "testValue"} + } + } + } + } + }); + + var result = txtService.Localize("testKey", culture); + + Assert.AreEqual("testValue", result); + } + + [Test] + public void Using_Dictionary_Returns_Default_Text_When_Not_Found_With_Area() + { + var culture = CultureInfo.GetCultureInfo("en-US"); + var txtService = new LocalizedTextService( + new Dictionary>> + { + { + culture, new Dictionary> + { + { + "testArea", new Dictionary + { + {"testKey", "testValue"} + } + } + } + } + }); + + var result = txtService.Localize("testArea/doNotFind", culture); + + //NOTE: Based on how legacy works, the default text does not contain the area, just the key + Assert.AreEqual("[doNotFind]", result); + } + + [Test] + public void Using_Dictionary_Returns_Default_Text_When_Not_Found_Without_Area() + { + var culture = CultureInfo.GetCultureInfo("en-US"); + var txtService = new LocalizedTextService( + new Dictionary>> + { + { + culture, new Dictionary> + { + { + "testArea", new Dictionary + { + {"testKey", "testValue"} + } + } + } + } + }); + + var result = txtService.Localize("doNotFind", culture); + + Assert.AreEqual("[doNotFind]", result); + } + + [Test] + public void Using_Dictionary_Returns_Tokenized_Text() + { + var culture = CultureInfo.GetCultureInfo("en-US"); + var txtService = new LocalizedTextService( + new Dictionary>> + { + { + culture, new Dictionary> + { + { + "testArea", new Dictionary + { + {"testKey", "Hello %0%, you are such a %1% %2%"} + } + } + } + } + }); + + var result = txtService.Localize("testKey", culture, + new Dictionary { { "0", "world" }, { "1", "great" }, { "2", "planet" } }); + + Assert.AreEqual("Hello world, you are such a great planet", result); + } + + [Test] + public void Using_XDocument_Returns_Text_With_Area() + { + var culture = CultureInfo.GetCultureInfo("en-US"); + var txtService = new LocalizedTextService( + new Dictionary> + { + { + culture, new Lazy(() => new XDocument( + new XElement("area", new XAttribute("alias", "testArea"), + new XElement("key", new XAttribute("alias", "testKey"), + "testValue")))) + } + }); + + var result = txtService.Localize("testArea/testKey", culture); + + Assert.AreEqual("testValue", result); + } + + [Test] + public void Using_XDocument_Returns_Text_Without_Area() + { + var culture = CultureInfo.GetCultureInfo("en-US"); + var txtService = new LocalizedTextService( + new Dictionary> + { + { + culture, new Lazy(() => new XDocument( + new XElement("area", new XAttribute("alias", "testArea"), + new XElement("key", new XAttribute("alias", "testKey"), + "testValue")))) + } + }); + + var result = txtService.Localize("testKey", culture); + + Assert.AreEqual("testValue", result); + } + + [Test] + public void Using_XDocument_Returns_Default_Text_When_Not_Found_With_Area() + { + var culture = CultureInfo.GetCultureInfo("en-US"); + var txtService = new LocalizedTextService( + new Dictionary> + { + { + culture, new Lazy(() => new XDocument( + new XElement("area", new XAttribute("alias", "testArea"), + new XElement("key", new XAttribute("alias", "testKey"), + "testValue")))) + } + }); + + var result = txtService.Localize("testArea/doNotFind", culture); + + //NOTE: Based on how legacy works, the default text does not contain the area, just the key + Assert.AreEqual("[doNotFind]", result); + } + + [Test] + public void Using_XDocument_Returns_Default_Text_When_Not_Found_Without_Area() + { + var culture = CultureInfo.GetCultureInfo("en-US"); + var txtService = new LocalizedTextService( + new Dictionary> + { + { + culture, new Lazy(() => new XDocument( + new XElement("area", new XAttribute("alias", "testArea"), + new XElement("key", new XAttribute("alias", "testKey"), + "testValue")))) + } + }); + + var result = txtService.Localize("doNotFind", culture); + + Assert.AreEqual("[doNotFind]", result); + } + + [Test] + public void Using_XDocument_Returns_Tokenized_Text() + { + var culture = CultureInfo.GetCultureInfo("en-US"); + var txtService = new LocalizedTextService( + new Dictionary> + { + { + culture, new Lazy(() => new XDocument( + new XElement("area", new XAttribute("alias", "testArea"), + new XElement("key", new XAttribute("alias", "testKey"), + "Hello %0%, you are such a %1% %2%")))) + } + }); + + var result = txtService.Localize("testKey", culture, + new Dictionary { { "0", "world" }, { "1", "great" }, { "2", "planet" } }); + + Assert.AreEqual("Hello world, you are such a great planet", result); + } + + [Test] + public void Using_Dictionary_Throws_When_No_Culture_Found() + { + var culture = CultureInfo.GetCultureInfo("en-US"); + var txtService = new LocalizedTextService( + new Dictionary>> + { + { + culture, new Dictionary> + { + { + "testArea", new Dictionary + { + {"testKey", "testValue"} + } + } + } + } + }); + + Assert.Throws(() => txtService.Localize("testArea/testKey", CultureInfo.GetCultureInfo("en-AU"))); + } + + [Test] + public void Using_XDocument_Throws_When_No_Culture_Found() + { + var culture = CultureInfo.GetCultureInfo("en-US"); + var txtService = new LocalizedTextService( + new Dictionary> + { + { + culture, new Lazy(() => new XDocument( + new XElement("area", new XAttribute("alias", "testArea"), + new XElement("key", new XAttribute("alias", "testKey"), + "testValue")))) + } + }); + + Assert.Throws(() => txtService.Localize("testArea/testKey", CultureInfo.GetCultureInfo("en-AU"))); + } + } +} diff --git a/src/Umbraco.Tests/Services/UserServiceTests.cs b/src/Umbraco.Tests/Services/UserServiceTests.cs index 2a099c9001..f3100aa133 100644 --- a/src/Umbraco.Tests/Services/UserServiceTests.cs +++ b/src/Umbraco.Tests/Services/UserServiceTests.cs @@ -451,6 +451,43 @@ namespace Umbraco.Tests.Services } + [Test] + public void Can_Add_Section_To_All_Users() + { + var userType = ServiceContext.UserService.GetUserTypeByAlias("admin"); + + var user1 = ServiceContext.UserService.CreateUserWithIdentity("test1", "test1@test.com", userType); + var user2 = ServiceContext.UserService.CreateUserWithIdentity("test2", "test2@test.com", userType); + var user3 = ServiceContext.UserService.CreateUserWithIdentity("test3", "test3@test.com", userType); + var user4 = ServiceContext.UserService.CreateUserWithIdentity("test4", "test4@test.com", userType); + + //now add the section to specific users + ServiceContext.UserService.AddSectionToAllUsers("test", (int)user1.Id, (int)user2.Id); + + //assert + var result1 = ServiceContext.UserService.GetUserById((int)user1.Id); + var result2 = ServiceContext.UserService.GetUserById((int)user2.Id); + var result3 = ServiceContext.UserService.GetUserById((int)user3.Id); + var result4 = ServiceContext.UserService.GetUserById((int)user4.Id); + Assert.IsTrue(result1.AllowedSections.Contains("test")); + Assert.IsTrue(result2.AllowedSections.Contains("test")); + Assert.IsFalse(result3.AllowedSections.Contains("test")); + Assert.IsFalse(result4.AllowedSections.Contains("test")); + + //now add the section to all users + ServiceContext.UserService.AddSectionToAllUsers("test"); + + //assert + result1 = ServiceContext.UserService.GetUserById((int)user1.Id); + result2 = ServiceContext.UserService.GetUserById((int)user2.Id); + result3 = ServiceContext.UserService.GetUserById((int)user3.Id); + result4 = ServiceContext.UserService.GetUserById((int)user4.Id); + Assert.IsTrue(result1.AllowedSections.Contains("test")); + Assert.IsTrue(result2.AllowedSections.Contains("test")); + Assert.IsTrue(result3.AllowedSections.Contains("test")); + Assert.IsTrue(result4.AllowedSections.Contains("test")); + } + [Test] public void Get_By_Profile_Username() { @@ -512,4 +549,4 @@ namespace Umbraco.Tests.Services Assert.That(updatedItem.AllowedSections.Count(), Is.EqualTo(0)); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index 85e46394a4..4618888883 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -354,7 +354,7 @@ namespace Umbraco.Tests.TestHelpers var ctx = new UmbracoContext( httpContext, ApplicationContext, - new PublishedCaches(cache, new PublishedMediaCache()), + new PublishedCaches(cache, new PublishedMediaCache(ApplicationContext)), new WebSecurity(httpContext, ApplicationContext)); if (setSingleton) diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoConfigurationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoConfigurationTest.cs index db4b87ab11..bf5f0516bd 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoConfigurationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoConfigurationTest.cs @@ -19,10 +19,7 @@ namespace Umbraco.Tests.TestHelpers [SetUp] public virtual void Initialize() { - using (DisposableTimer.TraceDuration("init")) - { - SettingsForTests.Reset(); - } + SettingsForTests.Reset(); } @@ -30,10 +27,7 @@ namespace Umbraco.Tests.TestHelpers public virtual void TearDown() { //reset settings - using (DisposableTimer.TraceDuration("teardown")) - { - SettingsForTests.Reset(); - } + SettingsForTests.Reset(); } } diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs index 7551d222a8..e72290c163 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs @@ -69,7 +69,7 @@ namespace Umbraco.Tests.TestHelpers.Entities title = name + " textpage", bodyText = string.Format("This is a textpage based on the {0} ContentType", contentType.Alias), keywords = "text,page,meta", - metaDescription = "This is the meta description for a textpage" + description = "This is the meta description for a textpage" }; content.PropertyValues(obj); @@ -121,7 +121,7 @@ namespace Umbraco.Tests.TestHelpers.Entities title = name + " title", bodyText = string.Format("This is a textpage based on the {0} ContentType", contentType.Alias), keywords = "text,page,meta", - metaDescription = "This is the meta description for a textpage" + description = "This is the meta description for a textpage" }; content.PropertyValues(obj); diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs index fc107fcdbc..39916453dc 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs @@ -1,13 +1,30 @@ using System; using Umbraco.Core; using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; namespace Umbraco.Tests.TestHelpers.Entities { public class MockedContentTypes { - + public static ContentType CreateBasicContentType(string alias = "basePage", string name = "Base Page", + ContentType parent = null) + { + var contentType = parent == null ? new ContentType(-1) : new ContentType(parent, alias); + + contentType.Alias = alias; + contentType.Name = name; + contentType.Description = "ContentType used for basic pages"; + contentType.Icon = ".sprTreeDoc3"; + contentType.Thumbnail = "doc2.png"; + contentType.SortOrder = 1; + contentType.CreatorId = 0; + contentType.Trashed = false; + + //ensure that nothing is marked as dirty + contentType.ResetDirtyProperties(false); + + return contentType; + } public static ContentType CreateTextpageContentType(string alias = "textPage", string name = "Text Page") { @@ -29,7 +46,7 @@ namespace Umbraco.Tests.TestHelpers.Entities var metaCollection = new PropertyTypeCollection(); metaCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "keywords", Name = "Meta Keywords", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); - metaCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "metaDescription", Name = "Meta Description", Description = "", HelpText = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -89 }); + metaCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "description", Name = "Meta Description", Description = "", HelpText = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -89 }); contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); contentType.PropertyGroups.Add(new PropertyGroup(metaCollection) { Name = "Meta", SortOrder = 2 }); @@ -66,6 +83,57 @@ namespace Umbraco.Tests.TestHelpers.Entities return contentType; } + public static ContentType CreateContentMetaContentType() + { + var contentType = new ContentType(-1) + { + Alias = "contentMeta", + Name = "Content Meta", + Description = "ContentType used for Content Meta", + Icon = ".sprTreeDoc3", + Thumbnail = "doc.png", + SortOrder = 1, + CreatorId = 0, + Trashed = false + }; + + var metaCollection = new PropertyTypeCollection(); + metaCollection.Add(new PropertyType("test", DataTypeDatabaseType.Ntext) { Alias = "title", Name = "Title", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); + + contentType.PropertyGroups.Add(new PropertyGroup(metaCollection) { Name = "Content", SortOrder = 2 }); + + //ensure that nothing is marked as dirty + contentType.ResetDirtyProperties(false); + + return contentType; + } + + public static ContentType CreateSeoContentType() + { + var contentType = new ContentType(-1) + { + Alias = "seo", + Name = "Seo", + Description = "ContentType used for Seo", + Icon = ".sprTreeDoc3", + Thumbnail = "doc.png", + SortOrder = 1, + CreatorId = 0, + Trashed = false + }; + + var metaCollection = new PropertyTypeCollection(); + metaCollection.Add(new PropertyType("seotest", DataTypeDatabaseType.Ntext) { Alias = "seokeywords", Name = "Seo Keywords", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); + metaCollection.Add(new PropertyType("seotest", DataTypeDatabaseType.Ntext) { Alias = "seodescription", Name = "Seo Description", Description = "", HelpText = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -89 }); + + contentType.PropertyGroups.Add(new PropertyGroup(metaCollection) { Name = "Seo", SortOrder = 5 }); + + //ensure that nothing is marked as dirty + contentType.ResetDirtyProperties(false); + + return contentType; + } + public static ContentType CreateSimpleContentType() { var contentType = new ContentType(-1) @@ -93,9 +161,9 @@ namespace Umbraco.Tests.TestHelpers.Entities return contentType; } - public static ContentType CreateSimpleContentType(string alias, string name, IContentType parent = null) + public static ContentType CreateSimpleContentType(string alias, string name, IContentType parent = null, bool randomizeAliases = false, string propertyGroupName = "Content") { - var contentType = parent == null ? new ContentType(-1) : new ContentType(parent); + var contentType = parent == null ? new ContentType(-1) : new ContentType(parent, alias); contentType.Alias = alias; contentType.Name = name; @@ -107,11 +175,11 @@ namespace Umbraco.Tests.TestHelpers.Entities contentType.Trashed = false; var contentCollection = new PropertyTypeCollection(); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { Alias = "title", Name = "Title", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.TinyMCEAlias, DataTypeDatabaseType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", HelpText = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -87 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { Alias = "author", Name = "Author", Description = "Name of the author", HelpText = "", Mandatory = false, SortOrder = 3, DataTypeDefinitionId = -88 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { Alias = RandomAlias("title", randomizeAliases), Name = "Title", Description = "", HelpText = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.TinyMCEAlias, DataTypeDatabaseType.Ntext) { Alias = RandomAlias("bodyText", randomizeAliases), Name = "Body Text", Description = "", HelpText = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -87 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) { Alias = RandomAlias("author", randomizeAliases) , Name = "Author", Description = "Name of the author", HelpText = "", Mandatory = false, SortOrder = 3, DataTypeDefinitionId = -88 }); - contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = propertyGroupName, SortOrder = 1 }); //ensure that nothing is marked as dirty contentType.ResetDirtyProperties(false); @@ -168,6 +236,27 @@ namespace Umbraco.Tests.TestHelpers.Entities return contentType; } + public static ContentType CreateSimpleContentType(string alias, string name, PropertyTypeCollection collection, string propertyGroupName, IContentType parent = null) + { + var contentType = parent == null ? new ContentType(-1) : new ContentType(parent, alias); + + contentType.Alias = alias; + contentType.Name = name; + contentType.Description = "ContentType used for simple text pages"; + contentType.Icon = ".sprTreeDoc3"; + contentType.Thumbnail = "doc2.png"; + contentType.SortOrder = 1; + contentType.CreatorId = 0; + contentType.Trashed = false; + + contentType.PropertyGroups.Add(new PropertyGroup(collection) { Name = propertyGroupName, SortOrder = 1 }); + + //ensure that nothing is marked as dirty + contentType.ResetDirtyProperties(false); + + return contentType; + } + public static ContentType CreateSimpleContentType(string alias, string name, PropertyTypeCollection groupedCollection, PropertyTypeCollection nonGroupedCollection) { var contentType = CreateSimpleContentType(alias, name, groupedCollection); @@ -307,5 +396,15 @@ namespace Umbraco.Tests.TestHelpers.Entities return contentType; } + + private static string RandomAlias(string alias, bool randomizeAliases) + { + if (randomizeAliases) + { + return string.Concat(alias, Guid.NewGuid().ToString("N")); + } + + return alias; + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index d2aaa7a440..c4096216ca 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -58,9 +58,9 @@ ..\packages\AutoMapper.3.0.0\lib\net40\AutoMapper.Net4.dll - + False - ..\packages\Examine.0.1.57.2941\lib\Examine.dll + ..\packages\Examine.0.1.59.2941\lib\Examine.dll False @@ -132,9 +132,9 @@ ..\packages\Microsoft.AspNet.WebApi.WebHost.4.0.30506.0\lib\net40\System.Web.Http.WebHost.dll - + True - ..\packages\Microsoft.AspNet.Mvc.4.0.40804.0\lib\net40\System.Web.Mvc.dll + ..\packages\Microsoft.AspNet.Mvc.4.0.30506.0\lib\net40\System.Web.Mvc.dll True @@ -168,6 +168,8 @@ + + @@ -280,6 +282,7 @@ + @@ -638,7 +641,10 @@ Designer + + + diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs index 736f6c3eb0..62c94902d9 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs @@ -142,12 +142,15 @@ namespace Umbraco.Tests.UmbracoExamine { var s = (IndexSearcher)_searcher.GetSearcher(); + + //first delete all 'Content' (not media). This is done by directly manipulating the index with the Lucene API, not examine! - var r = IndexReader.Open(s.GetIndexReader().Directory(), false); + var contentTerm = new Term(LuceneIndexer.IndexTypeFieldName, IndexTypes.Content); - var delCount = r.DeleteDocuments(contentTerm); - r.Commit(); - r.Close(); + var writer = _indexer.GetIndexWriter(); + writer.DeleteDocuments(contentTerm); + writer.Commit(); + //make sure the content is gone. This is done with lucene APIs, not examine! var collector = new AllHitsCollector(false, true); diff --git a/src/Umbraco.Tests/packages.config b/src/Umbraco.Tests/packages.config index 008f4e5624..d548c63204 100644 --- a/src/Umbraco.Tests/packages.config +++ b/src/Umbraco.Tests/packages.config @@ -2,10 +2,10 @@ - + - - + + @@ -21,6 +21,6 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/bower.json b/src/Umbraco.Web.UI.Client/bower.json index 9a48d14d7a..d1e0efd476 100644 --- a/src/Umbraco.Web.UI.Client/bower.json +++ b/src/Umbraco.Web.UI.Client/bower.json @@ -1,22 +1,34 @@ { - "name": "Umbraco", - "version": "7", - "homepage": "https://github.com/umbraco/Umbraco-CMS", - "authors": [ - "Shannon " - ], - "description": "Umbraco CMS", - "license": "MIT", - "private": true, - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test", - "tests" - ], - "dependencies": { - "typeahead.js": "~0.10.5", - "rgrove-lazyload": "*" - } + "name": "Umbraco", + "version": "7", + "homepage": "https://github.com/umbraco/Umbraco-CMS", + "authors": [ + "Shannon " + ], + "description": "Umbraco CMS", + "license": "MIT", + "private": true, + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "dependencies": { + "typeahead.js": "~0.10.5", + "underscore": "~1.7.0", + "rgrove-lazyload": "*" + }, + "exportsOverride": { + "rgrove-lazyload": { + "": "lazyload.js" + }, + "typeahead.js": { + "": "dist/typeahead.bundle.min.js" + }, + "underscore": { + "": "underscore-min.{js,map}" + } + } } diff --git a/src/Umbraco.Web.UI.Client/gruntFile.js b/src/Umbraco.Web.UI.Client/gruntFile.js index 76028f1f04..ef636d2ada 100644 --- a/src/Umbraco.Web.UI.Client/gruntFile.js +++ b/src/Umbraco.Web.UI.Client/gruntFile.js @@ -1,463 +1,445 @@ module.exports = function (grunt) { + - // Default task. - grunt.registerTask('default', ['jshint:dev','build','karma:unit']); + // Default task. + grunt.registerTask('default', ['jshint:dev', 'build', 'karma:unit']); grunt.registerTask('dev', ['jshint:dev', 'build-dev', 'webserver', 'open:dev', 'watch']); grunt.registerTask('docserve', ['docs:api', 'connect:docserver', 'open:docs', 'watch:docs']); grunt.registerTask('vs', ['jshint:dev', 'build-dev', 'watch']); //TODO: Too much watching, this brings windows to it's knees when in dev mode - //run by the watch task - grunt.registerTask('watch-js', ['jshint:dev','concat','copy:app','copy:mocks','copy:canvasdesigner','copy:vs', 'karma:unit']); - grunt.registerTask('watch-less', ['recess:build', 'recess:installer', 'recess:canvasdesigner','copy:canvasdesigner', 'copy:assets', 'copy:vs']); - grunt.registerTask('watch-html', ['copy:views', 'copy:vs']); - grunt.registerTask('watch-installer', ['concat:install', 'concat:installJs', 'copy:installer', 'copy:vs']); - grunt.registerTask('watch-canvasdesigner', ['copy:canvasdesigner', 'concat:canvasdesignerJs', 'copy:vs']); - grunt.registerTask('watch-test', ['jshint:dev', 'karma:unit']); + //run by the watch task + grunt.registerTask('watch-js', ['jshint:dev', 'concat', 'copy:app', 'copy:mocks', 'copy:canvasdesigner', 'copy:vs', 'karma:unit']); + grunt.registerTask('watch-less', ['recess:build', 'recess:installer', 'recess:canvasdesigner', 'copy:canvasdesigner', 'copy:assets', 'copy:vs']); + grunt.registerTask('watch-html', ['copy:views', 'copy:vs']); + grunt.registerTask('watch-installer', ['concat:install', 'concat:installJs', 'copy:installer', 'copy:vs']); + grunt.registerTask('watch-canvasdesigner', ['copy:canvasdesigner', 'concat:canvasdesignerJs', 'copy:vs']); + grunt.registerTask('watch-test', ['jshint:dev', 'karma:unit']); - //triggered from grunt dev or grunt - grunt.registerTask('build', ['clean', 'concat', 'recess:min', 'recess:installer', 'recess:canvasdesigner', 'bower', 'copy']); - - //build-dev doesn't min - we are trying to speed this up and we don't want minified stuff when we are in dev mode - grunt.registerTask('build-dev', ['clean', 'concat', 'recess:build', 'recess:installer', 'copy']); + //triggered from grunt dev or grunt + grunt.registerTask('build', ['clean', 'concat', 'recess:min', 'recess:installer', 'recess:canvasdesigner', 'bower', 'copy']); - //utillity tasks - grunt.registerTask('docs', ['ngdocs']); - grunt.registerTask('webserver', ['connect:devserver']); + //build-dev doesn't min - we are trying to speed this up and we don't want minified stuff when we are in dev mode + grunt.registerTask('build-dev', ['clean', 'concat', 'recess:build', 'recess:installer', 'bower', 'copy']); + + //utillity tasks + grunt.registerTask('docs', ['ngdocs']); + grunt.registerTask('webserver', ['connect:devserver']); - // Print a timestamp (useful for when watching) - grunt.registerTask('timestamp', function() { - grunt.log.subhead(Date()); - }); + // Print a timestamp (useful for when watching) + grunt.registerTask('timestamp', function () { + grunt.log.subhead(Date()); + }); - // Custom task to run the bower dependency installer - // tried, a few other things but this seems to work the best. - // https://coderwall.com/p/xnkdqw - grunt.registerTask('bower', 'Get js packages listed in bower.json', - function () { - var bower = require('bower'); - var done = this.async(); - - bower.commands.install(undefined, { }, { interactive: false }) - .on('log', function (data) { - grunt.log.write(data.message + "\n"); - }) - .on('error', function (data) { - grunt.log.write(data.message + "\n"); - done(false); - }) - .on('end', function (data) { - done(); - }); - } - ); - - - // Project configuration. - grunt.initConfig({ - buildVersion: grunt.option('buildversion') || '7', - connect: { - devserver: { - options: { - port: 9990, - hostname: '0.0.0.0', - base: './build', - middleware: function(connect, options){ - return [ - //uncomment to enable CSP - // util.csp(), - //util.rewrite(), - connect.favicon('images/favicon.ico'), - connect.static(options.base), - connect.directory(options.base) - ]; - } - } - }, - testserver: {}, - docserver: { - options: { - port: 8880, - hostname: '0.0.0.0', - base: './docs/api', - middleware: function(connect, options){ - return [ - //uncomment to enable CSP - // util.csp(), - //util.rewrite(), - connect.static(options.base), - connect.directory(options.base) - ]; - } - } - }, - }, - - open : { - dev : { - path: 'http://localhost:9990/belle/' - }, - docs : { - path: 'http://localhost:8880/index.html' - } - }, - - distdir: 'build/belle', - bowerfiles: 'bower_components', - vsdir: '../Umbraco.Web.UI/umbraco', - pkg: grunt.file.readJSON('package.json'), - banner: - '/*! <%= pkg.title || pkg.name %>\n' + - '<%= pkg.homepage ? " * " + pkg.homepage + "\\n" : "" %>' + - ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>;\n' + - ' * Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %>\n */\n', - src: { - js: ['src/**/*.js', 'src/*.js'], - common: ['src/common/**/*.js'], - specs: ['test/**/*.spec.js'], - scenarios: ['test/**/*.scenario.js'], - samples: ['sample files/*.js'], - html: ['src/index.html','src/install.html'], - - everything:['src/**/*.*', 'test/**/*.*', 'docs/**/*.*'], - - tpl: { - app: ['src/views/**/*.html'], - common: ['src/common/**/*.tpl.html'] - }, - less: ['src/less/belle.less'], // recess:build doesn't accept ** in its file patterns - prod: ['<%= distdir %>/js/*.js'] - }, - - clean: ['<%= distdir %>/*'], - - copy: { - assets: { - files: [{ dest: '<%= distdir %>/assets', src : '**', expand: true, cwd: 'src/assets/' }] - }, - - config: { - files: [{ dest: '<%= distdir %>/../config', src : '**', expand: true, cwd: 'src/config/' }] - }, - - // Copies over the files downloaded by bower - bower: { - files: [ - { - dest: '<%= distdir %>/lib/typeahead/typeahead.bundle.min.js', - src: 'bower_components/typeahead.js/dist/typeahead.bundle.min.js' - }, - { - dest: '<%= distdir %>/lib/lazyload/lazyload.min.js', - src: 'bower_components/rgrove-lazyload/lazyload.js' + // Project configuration. + grunt.initConfig({ + buildVersion: grunt.option('buildversion') || '7', + connect: { + devserver: { + options: { + port: 9990, + hostname: '0.0.0.0', + base: './build', + middleware: function (connect, options) { + return [ + //uncomment to enable CSP + // util.csp(), + //util.rewrite(), + connect.favicon('images/favicon.ico'), + connect.static(options.base), + connect.directory(options.base) + ]; + } } - ] - }, - - - installer: { - files: [{ dest: '<%= distdir %>/views/install', src : '**/*.html', expand: true, cwd: 'src/installer/steps' }] - }, - - canvasdesigner: { - files: [ - { dest: '<%= distdir %>/preview', src: '**/*.html', expand: true, cwd: 'src/canvasdesigner' }, - { dest: '<%= distdir %>/preview/editors', src: '**/*.html', expand: true, cwd: 'src/canvasdesigner/editors' }, - { dest: '<%= distdir %>/assets/less', src: '**/*.less', expand: true, cwd: 'src/canvasdesigner/editors' }, - { dest: '<%= distdir %>/js', src: 'canvasdesigner.config.js', expand: true, cwd: 'src/canvasdesigner/config' }, - { dest: '<%= distdir %>/js', src: 'canvasdesigner.palettes.js', expand: true, cwd: 'src/canvasdesigner/config' }, - { dest: '<%= distdir %>/js', src: 'canvasdesigner.front.js', expand: true, cwd: 'src/canvasdesigner' } - ] - }, - - vendor: { - files: [{ dest: '<%= distdir %>/lib', src : '**', expand: true, cwd: 'lib/' }] - }, - views: { - files: [{ dest: '<%= distdir %>/views', src : ['**/*.*', '!**/*.controller.js'], expand: true, cwd: 'src/views/' }] - }, - app: { - files: [ - { dest: '<%= distdir %>/js', src : '*.js', expand: true, cwd: 'src/' } - ] - }, - mocks: { - files: [{ dest: '<%= distdir %>/js', src : '*.js', expand: true, cwd: 'src/common/mocks/' }] - }, - vs: { - files: [ - //everything except the index.html root file! - //then we need to figure out how to not copy all the test stuff either!? - { dest: '<%= vsdir %>/assets', src: '**', expand: true, cwd: '<%= distdir %>/assets' }, - { dest: '<%= vsdir %>/js', src: '**', expand: true, cwd: '<%= distdir %>/js' }, - { dest: '<%= vsdir %>/lib', src: '**', expand: true, cwd: '<%= distdir %>/lib' }, - { dest: '<%= vsdir %>/views', src: '**', expand: true, cwd: '<%= distdir %>/views' }, - { dest: '<%= vsdir %>/preview', src: '**', expand: true, cwd: '<%= distdir %>/preview' } - ] - } - }, - - karma: { - unit: { configFile: 'test/config/karma.conf.js', keepalive: true }, - e2e: { configFile: 'test/config/e2e.js', keepalive: true }, - watch: { configFile: 'test/config/unit.js', singleRun:false, autoWatch: true, keepalive: true } - }, - - concat:{ - index: { - src: ['src/index.html'], - dest: '<%= distdir %>/index.html', - options: { - process: true - } - }, - install: { - src: ['src/installer/installer.html'], - dest: '<%= distdir %>/installer.html', - options: { - process: true - } + }, + testserver: {}, + docserver: { + options: { + port: 8880, + hostname: '0.0.0.0', + base: './docs/api', + middleware: function (connect, options) { + return [ + //uncomment to enable CSP + // util.csp(), + //util.rewrite(), + connect.static(options.base), + connect.directory(options.base) + ]; + } + } + }, }, - installJs: { - src: ['src/installer/**/*.js'], - dest: '<%= distdir %>/js/umbraco.installer.js', - options: { - banner: "<%= banner %>\n(function() { \n\n angular.module('umbraco.install', []); \n", - footer: "\n\n})();" - } + open: { + dev: { + path: 'http://localhost:9990/belle/' + }, + docs: { + path: 'http://localhost:8880/index.html' + } }, - canvasdesignerJs: { - src: ['src/canvasdesigner/canvasdesigner.global.js', 'src/canvasdesigner/canvasdesigner.controller.js', 'src/canvasdesigner/editors/*.js', 'src/canvasdesigner/lib/*.js'], - dest: '<%= distdir %>/js/canvasdesigner.panel.js' + + distdir: 'build/belle', + vsdir: '../Umbraco.Web.UI/umbraco', + pkg: grunt.file.readJSON('package.json'), + banner: + '/*! <%= pkg.title || pkg.name %>\n' + + '<%= pkg.homepage ? " * " + pkg.homepage + "\\n" : "" %>' + + ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>;\n' + + ' * Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %>\n */\n', + src: { + js: ['src/**/*.js', 'src/*.js'], + common: ['src/common/**/*.js'], + specs: ['test/**/*.spec.js'], + scenarios: ['test/**/*.scenario.js'], + samples: ['sample files/*.js'], + html: ['src/index.html', 'src/install.html'], + + everything: ['src/**/*.*', 'test/**/*.*', 'docs/**/*.*'], + + tpl: { + app: ['src/views/**/*.html'], + common: ['src/common/**/*.tpl.html'] + }, + less: ['src/less/belle.less'], // recess:build doesn't accept ** in its file patterns + prod: ['<%= distdir %>/js/*.js'] }, - controllers: { - src:['src/controllers/**/*.controller.js','src/views/**/*.controller.js'], - dest: '<%= distdir %>/js/umbraco.controllers.js', - options: { - banner: "<%= banner %>\n(function() { \n\n", - footer: "\n\n})();" - } + + clean: ['<%= distdir %>/*'], + + copy: { + assets: { + files: [{ dest: '<%= distdir %>/assets', src: '**', expand: true, cwd: 'src/assets/' }] + }, + + config: { + files: [{ dest: '<%= distdir %>/../config', src: '**', expand: true, cwd: 'src/config/' }] + }, + + installer: { + files: [{ dest: '<%= distdir %>/views/install', src: '**/*.html', expand: true, cwd: 'src/installer/steps' }] + }, + + canvasdesigner: { + files: [ + { dest: '<%= distdir %>/preview', src: '**/*.html', expand: true, cwd: 'src/canvasdesigner' }, + { dest: '<%= distdir %>/preview/editors', src: '**/*.html', expand: true, cwd: 'src/canvasdesigner/editors' }, + { dest: '<%= distdir %>/assets/less', src: '**/*.less', expand: true, cwd: 'src/canvasdesigner/editors' }, + { dest: '<%= distdir %>/js', src: 'canvasdesigner.config.js', expand: true, cwd: 'src/canvasdesigner/config' }, + { dest: '<%= distdir %>/js', src: 'canvasdesigner.palettes.js', expand: true, cwd: 'src/canvasdesigner/config' }, + { dest: '<%= distdir %>/js', src: 'canvasdesigner.front.js', expand: true, cwd: 'src/canvasdesigner' } + ] + }, + + vendor: { + files: [{ dest: '<%= distdir %>/lib', src: '**', expand: true, cwd: 'lib/' }] + }, + views: { + files: [{ dest: '<%= distdir %>/views', src: ['**/*.*', '!**/*.controller.js'], expand: true, cwd: 'src/views/' }] + }, + app: { + files: [ + { dest: '<%= distdir %>/js', src: '*.js', expand: true, cwd: 'src/' } + ] + }, + mocks: { + files: [{ dest: '<%= distdir %>/js', src: '*.js', expand: true, cwd: 'src/common/mocks/' }] + }, + vs: { + files: [ + //everything except the index.html root file! + //then we need to figure out how to not copy all the test stuff either!? + { dest: '<%= vsdir %>/assets', src: '**', expand: true, cwd: '<%= distdir %>/assets' }, + { dest: '<%= vsdir %>/js', src: '**', expand: true, cwd: '<%= distdir %>/js' }, + { dest: '<%= vsdir %>/views', src: '**', expand: true, cwd: '<%= distdir %>/views' }, + { dest: '<%= vsdir %>/preview', src: '**', expand: true, cwd: '<%= distdir %>/preview' }, + { dest: '<%= vsdir %>/lib', src: '**', expand: true, cwd: '<%= distdir %>/lib' } + ] + } }, - services: { - src:['src/common/services/*.js'], - dest: '<%= distdir %>/js/umbraco.services.js', - options: { - banner: "<%= banner %>\n(function() { \n\n", - footer: "\n\n})();" - } + + karma: { + unit: { configFile: 'test/config/karma.conf.js', keepalive: true }, + e2e: { configFile: 'test/config/e2e.js', keepalive: true }, + watch: { configFile: 'test/config/unit.js', singleRun: false, autoWatch: true, keepalive: true } }, - security: { - src:['src/common/security/*.js'], - dest: '<%= distdir %>/js/umbraco.security.js', - options: { - banner: "<%= banner %>\n(function() { \n\n", - footer: "\n\n})();" - } + + concat: { + index: { + src: ['src/index.html'], + dest: '<%= distdir %>/index.html', + options: { + process: true + } + }, + install: { + src: ['src/installer/installer.html'], + dest: '<%= distdir %>/installer.html', + options: { + process: true + } + }, + + installJs: { + src: ['src/installer/**/*.js'], + dest: '<%= distdir %>/js/umbraco.installer.js', + options: { + banner: "<%= banner %>\n(function() { \n\n angular.module('umbraco.install', []); \n", + footer: "\n\n})();" + } + }, + canvasdesignerJs: { + src: ['src/canvasdesigner/canvasdesigner.global.js', 'src/canvasdesigner/canvasdesigner.controller.js', 'src/canvasdesigner/editors/*.js', 'src/canvasdesigner/lib/*.js'], + dest: '<%= distdir %>/js/canvasdesigner.panel.js' + }, + controllers: { + src: ['src/controllers/**/*.controller.js', 'src/views/**/*.controller.js'], + dest: '<%= distdir %>/js/umbraco.controllers.js', + options: { + banner: "<%= banner %>\n(function() { \n\n", + footer: "\n\n})();" + } + }, + services: { + src: ['src/common/services/*.js'], + dest: '<%= distdir %>/js/umbraco.services.js', + options: { + banner: "<%= banner %>\n(function() { \n\n", + footer: "\n\n})();" + } + }, + security: { + src: ['src/common/security/*.js'], + dest: '<%= distdir %>/js/umbraco.security.js', + options: { + banner: "<%= banner %>\n(function() { \n\n", + footer: "\n\n})();" + } + }, + resources: { + src: ['src/common/resources/*.js'], + dest: '<%= distdir %>/js/umbraco.resources.js', + options: { + banner: "<%= banner %>\n(function() { \n\n", + footer: "\n\n})();" + } + }, + testing: { + src: ['src/common/mocks/*/*.js'], + dest: '<%= distdir %>/js/umbraco.testing.js', + options: { + banner: "<%= banner %>\n(function() { \n\n", + footer: "\n\n})();" + } + }, + directives: { + src: ['src/common/directives/**/*.js'], + dest: '<%= distdir %>/js/umbraco.directives.js', + options: { + banner: "<%= banner %>\n(function() { \n\n", + footer: "\n\n})();" + } + }, + filters: { + src: ['src/common/filters/*.js'], + dest: '<%= distdir %>/js/umbraco.filters.js', + options: { + banner: "<%= banner %>\n(function() { \n\n", + footer: "\n\n})();" + } + } }, - resources: { - src:['src/common/resources/*.js'], - dest: '<%= distdir %>/js/umbraco.resources.js', - options: { - banner: "<%= banner %>\n(function() { \n\n", - footer: "\n\n})();" - } + + uglify: { + options: { + mangle: true + }, + combine: { + files: { + '<%= distdir %>/js/umbraco.min.js': ['<%= distdir %>/js/umbraco.*.js'] + } + } }, - testing: { - src:['src/common/mocks/*/*.js'], - dest: '<%= distdir %>/js/umbraco.testing.js', - options: { - banner: "<%= banner %>\n(function() { \n\n", - footer: "\n\n})();" - } + + recess: { + build: { + files: { + '<%= distdir %>/assets/css/<%= pkg.name %>.css': + ['<%= src.less %>'] + }, + options: { + compile: true + } + }, + installer: { + files: { + '<%= distdir %>/assets/css/installer.css': + ['src/less/installer.less'] + }, + options: { + compile: true + } + }, + canvasdesigner: { + files: { + '<%= distdir %>/assets/css/canvasdesigner.css': + ['src/less/canvasdesigner.less', 'src/less/helveticons.less'] + }, + options: { + compile: true + } + }, + min: { + files: { + '<%= distdir %>/assets/css/<%= pkg.name %>.css': ['<%= src.less %>'] + }, + options: { + compile: true, + compress: true + } + } }, - directives: { - src:['src/common/directives/**/*.js'], - dest: '<%= distdir %>/js/umbraco.directives.js', - options: { - banner: "<%= banner %>\n(function() { \n\n", - footer: "\n\n})();" - } + + + watch: { + css: { + files: 'src/**/*.less', + tasks: ['watch-less', 'timestamp'], + options: { + livereload: true, + }, + }, + js: { + files: ['src/**/*.js', 'src/*.js'], + tasks: ['watch-js', 'timestamp'], + }, + test: { + files: ['test/**/*.js'], + tasks: ['watch-test', 'timestamp'], + }, + installer: { + files: ['src/installer/**/*.*'], + tasks: ['watch-installer', 'timestamp'], + }, + canvasdesigner: { + files: ['src/canvasdesigner/**/*.*'], + tasks: ['watch-canvasdesigner', 'timestamp'], + }, + html: { + files: ['src/views/**/*.html', 'src/*.html'], + tasks: ['watch-html', 'timestamp'] + } }, - filters: { - src:['src/common/filters/*.js'], - dest: '<%= distdir %>/js/umbraco.filters.js', - options: { - banner: "<%= banner %>\n(function() { \n\n", - footer: "\n\n})();" - } + + + ngdocs: { + options: { + dest: 'docs/api', + startPage: '/api', + title: "Umbraco 7", + html5Mode: false, + }, + api: { + src: ['src/common/**/*.js', 'docs/src/api/**/*.ngdoc'], + title: 'API Documentation' + }, + tutorials: { + src: ['docs/src/tutorials/**/*.ngdoc'], + title: 'Tutorials' + } + }, + + jshint: { + dev: { + files: { + src: ['<%= src.common %>', '<%= src.specs %>', '<%= src.scenarios %>', '<%= src.samples %>'] + }, + options: { + curly: true, + eqeqeq: true, + immed: true, + latedef: true, + newcap: true, + noarg: true, + sub: true, + boss: true, + //NOTE: This is required so it doesn't barf on reserved words like delete when doing $http.delete + es5: true, + eqnull: true, + //NOTE: we need to use eval sometimes so ignore it + evil: true, + //NOTE: we need to check for strings such as "javascript:" so don't throw errors regarding those + scripturl: true, + //NOTE: we ignore tabs vs spaces because enforcing that causes lots of errors depending on the text editor being used + smarttabs: true, + globals: {} + } + }, + build: { + files: { + src: ['<%= src.prod %>'] + }, + options: { + curly: true, + eqeqeq: true, + immed: true, + latedef: true, + newcap: true, + noarg: true, + sub: true, + boss: true, + //NOTE: This is required so it doesn't barf on reserved words like delete when doing $http.delete + es5: true, + eqnull: true, + //NOTE: we need to use eval sometimes so ignore it + evil: true, + //NOTE: we need to check for strings such as "javascript:" so don't throw errors regarding those + scripturl: true, + //NOTE: we ignore tabs vs spaces because enforcing that causes lots of errors depending on the text editor being used + smarttabs: true, + globalstrict: true, + globals: { $: false, jQuery: false, define: false, require: false, window: false } + } + } + }, + + bower: { + install: { + options: { + targetDir: "<%= distdir %>/lib", + cleanTargetDir: false, + layout: function (type, component, source) { + + var path = require('path'); + + //this is the same as 'byComponent', however we will not allow + // folders with '.' in them since the grunt copy task does not like that + var componentWithoutPeriod = component.replace(".", "-"); + return path.join(componentWithoutPeriod, type); + } + } + } } - }, - - uglify: { - options: { - mangle: true - }, - combine: { - files: { - '<%= distdir %>/js/umbraco.min.js': ['<%= distdir %>/js/umbraco.*.js'] - } - } - }, - - recess: { - build: { - files: { - '<%= distdir %>/assets/css/<%= pkg.name %>.css': - ['<%= src.less %>'] }, - options: { - compile: true - } - }, - installer: { - files: { - '<%= distdir %>/assets/css/installer.css': - ['src/less/installer.less'] }, - options: { - compile: true - } - }, - canvasdesigner: { - files: { - '<%= distdir %>/assets/css/canvasdesigner.css': - ['src/less/canvasdesigner.less', 'src/less/helveticons.less'] - }, - options: { - compile: true - } - }, - min: { - files: { - '<%= distdir %>/assets/css/<%= pkg.name %>.css': ['<%= src.less %>'] - }, - options: { - compile: true, - compress: true - } - } - }, - - - watch:{ - css: { - files: 'src/**/*.less', - tasks: ['watch-less', 'timestamp'], - options: { - livereload: true, - }, - }, - js: { - files: ['src/**/*.js', 'src/*.js'], - tasks: ['watch-js', 'timestamp'], - }, - test: { - files: ['test/**/*.js'], - tasks: ['watch-test', 'timestamp'], - }, - installer: { - files: ['src/installer/**/*.*'], - tasks: ['watch-installer', 'timestamp'], - }, - canvasdesigner: { - files: ['src/canvasdesigner/**/*.*'], - tasks: ['watch-canvasdesigner', 'timestamp'], - }, - html: { - files: ['src/views/**/*.html', 'src/*.html'], - tasks:['watch-html','timestamp'] - } - }, - - - ngdocs: { - options: { - dest: 'docs/api', - startPage: '/api', - title: "Umbraco 7", - html5Mode: false, - }, - api: { - src: ['src/common/**/*.js', 'docs/src/api/**/*.ngdoc'], - title: 'API Documentation' - }, - tutorials: { - src: ['docs/src/tutorials/**/*.ngdoc'], - title: 'Tutorials' - } - }, - - jshint:{ - dev:{ - files: { - src: ['<%= src.common %>', '<%= src.specs %>', '<%= src.scenarios %>', '<%= src.samples %>'] - }, - options:{ - curly:true, - eqeqeq:true, - immed:true, - latedef:true, - newcap:true, - noarg:true, - sub:true, - boss: true, - //NOTE: This is required so it doesn't barf on reserved words like delete when doing $http.delete - es5: true, - eqnull: true, - //NOTE: we need to use eval sometimes so ignore it - evil: true, - //NOTE: we need to check for strings such as "javascript:" so don't throw errors regarding those - scripturl: true, - //NOTE: we ignore tabs vs spaces because enforcing that causes lots of errors depending on the text editor being used - smarttabs: true, - globals:{} - } - }, - build:{ - files: { - src: ['<%= src.prod %>'] - }, - options:{ - curly:true, - eqeqeq:true, - immed:true, - latedef:true, - newcap:true, - noarg:true, - sub:true, - boss: true, - //NOTE: This is required so it doesn't barf on reserved words like delete when doing $http.delete - es5: true, - eqnull: true, - //NOTE: we need to use eval sometimes so ignore it - evil: true, - //NOTE: we need to check for strings such as "javascript:" so don't throw errors regarding those - scripturl: true, - //NOTE: we ignore tabs vs spaces because enforcing that causes lots of errors depending on the text editor being used - smarttabs: true, - globalstrict:true, - globals:{$:false, jQuery:false,define:false,require:false,window:false} - } - } - } - }); + }); - grunt.loadNpmTasks('grunt-contrib-concat'); - grunt.loadNpmTasks('grunt-contrib-jshint'); - grunt.loadNpmTasks('grunt-contrib-clean'); - grunt.loadNpmTasks('grunt-contrib-copy'); - grunt.loadNpmTasks('grunt-contrib-uglify'); - grunt.loadNpmTasks('grunt-contrib-watch'); - grunt.loadNpmTasks('grunt-recess'); + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-contrib-copy'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-recess'); - grunt.loadNpmTasks('grunt-karma'); + grunt.loadNpmTasks('grunt-karma'); - grunt.loadNpmTasks('grunt-open'); - grunt.loadNpmTasks('grunt-contrib-connect'); - - grunt.loadNpmTasks('grunt-ngdocs'); + grunt.loadNpmTasks('grunt-open'); + grunt.loadNpmTasks('grunt-contrib-connect'); + grunt.loadNpmTasks('grunt-bower-task'); + grunt.loadNpmTasks('grunt-ngdocs'); }; diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/code/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/code/plugin.min.js index 9e7e89895e..cfcef52bab 100755 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/code/plugin.min.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/code/plugin.min.js @@ -1 +1,7 @@ -tinymce.PluginManager.add("code",function(e){function o(){e.windowManager.open({title:"Source code",body:{type:"textbox",name:"code",multiline:!0,minWidth:e.getParam("code_dialog_width",600),minHeight:e.getParam("code_dialog_height",Math.min(tinymce.DOM.getViewPort().h-200,500)),value:e.getContent({source_view:!0}),spellcheck:!1,style:"direction: ltr; text-align: left"},onSubmit:function(o){e.focus(),e.undoManager.transact(function(){e.setContent(o.data.code)}),e.selection.setCursorLocation(),e.nodeChanged()}})}e.addCommand("mceCodeEditor",o),e.addButton("code",{icon:"code",tooltip:"Source code",onclick:o}),e.addMenuItem("code",{icon:"code",text:"Source code",context:"tools",onclick:o})}); \ No newline at end of file +tinymce.PluginManager.add("code", function(e) { + function o() { + e.windowManager.open({ title: "Source code", body: { type: "textbox", name: "code", multiline: !0, minWidth: e.getParam("code_dialog_width", 600), minHeight: e.getParam("code_dialog_height", Math.min(tinymce.DOM.getViewPort().h - 200, 500)), value: e.getContent({ source_view: !0 }), spellcheck: !1, style: "direction: ltr; text-align: left" }, onSubmit: function(o) { e.focus(), e.undoManager.transact(function() { e.setContent(o.data.code) }), e.selection.setCursorLocation(), e.nodeChanged() } }) + } + + e.addCommand("mceCodeEditor", o), e.addButton("code", { icon: "code", tooltip: "Source code", onclick: o }), e.addMenuItem("code", { icon: "code", text: "Source code", context: "tools", onclick: o }) +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/codemirror/plugin.min.js b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/codemirror/plugin.min.js index 425083626d..43938f52c9 100755 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/codemirror/plugin.min.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/plugins/codemirror/plugin.min.js @@ -1,3 +1,24 @@ tinymce.PluginManager.requireLangPack("codemirror"); -tinymce.PluginManager.add("codemirror",function(a,c){function b(){a.focus();a.selection.collapse(!0);a.selection.setContent('');var b=a.windowManager.open({title:"Source code",url:c+"/source.html",width: a.getParam("code_dialog_width", 600),height: a.getParam("code_dialog_height", Math.min(tinymce.DOM.getViewPort().h - 200, 500)),resizable:!0,maximizable:!0,buttons:[{text:"Ok",subtype:"primary",onclick:function(){document.querySelectorAll(".mce-container-body>iframe")[0].contentWindow.submit();b.close()}},{text:"Cancel",onclick:"close"}]})}a.addButton("code", -{title:"Source code",icon:"code",onclick:b});a.addMenuItem("code",{icon:"code",text:"Source code",context:"tools",onclick:b})}); +tinymce.PluginManager.add("codemirror", function(a, c) { + function b() { + a.focus(); + a.selection.collapse(!0); + a.selection.setContent(''); + var b = a.windowManager.open({ + title: "Source code", url: c + "/source.html", width: a.getParam("code_dialog_width", 600), height: a.getParam("code_dialog_height", Math.min(tinymce.DOM.getViewPort().h - 200, 500)), resizable: !0, maximizable: !0, + buttons: [ + { + text: "Ok", subtype: "primary", + onclick: function() { + document.querySelectorAll(".mce-container-body>iframe")[0].contentWindow.submit(); + b.close() + } + }, { text: "Cancel", onclick: "close" } + ] + }) + } + + a.addButton("codemirror", + { title: "Source code", icon: "code", onclick: b }); + a.addMenuItem("codemirror", { icon: "code", text: "Source code", context: "tools", onclick: b }) +}); diff --git a/src/Umbraco.Web.UI.Client/lib/underscore/underscore-min.js b/src/Umbraco.Web.UI.Client/lib/underscore/underscore-min.js deleted file mode 100644 index 83292f0902..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/underscore/underscore-min.js +++ /dev/null @@ -1,5 +0,0 @@ -// Underscore.js 1.4.1 -// http://underscorejs.org -// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. -// Underscore may be freely distributed under the MIT license. -(function(){var e=this,t=e._,n={},r=Array.prototype,i=Object.prototype,s=Function.prototype,o=r.push,u=r.slice,a=r.concat,f=r.unshift,l=i.toString,c=i.hasOwnProperty,h=r.forEach,p=r.map,d=r.reduce,v=r.reduceRight,m=r.filter,g=r.every,y=r.some,b=r.indexOf,w=r.lastIndexOf,E=Array.isArray,S=Object.keys,x=s.bind,T=function(e){if(e instanceof T)return e;if(!(this instanceof T))return new T(e);this._wrapped=e};typeof exports!="undefined"?(typeof module!="undefined"&&module.exports&&(exports=module.exports=T),exports._=T):e._=T,T.VERSION="1.4.1";var N=T.each=T.forEach=function(e,t,r){if(h&&e.forEach===h)e.forEach(t,r);else if(e.length===+e.length){for(var i=0,s=e.length;i2;if(d&&e.reduce===d)return r&&(t=T.bind(t,r)),i?e.reduce(t,n):e.reduce(t);N(e,function(e,s,o){i?n=t.call(r,n,e,s,o):(n=e,i=!0)});if(!i)throw new TypeError("Reduce of empty array with no initial value");return n},T.reduceRight=T.foldr=function(e,t,n,r){var i=arguments.length>2;if(v&&e.reduceRight===v)return r&&(t=T.bind(t,r)),arguments.length>2?e.reduceRight(t,n):e.reduceRight(t);var s=e.length;if(s!==+s){var o=T.keys(e);s=o.length}N(e,function(u,a,f){a=o?o[--s]:--s,i?n=t.call(r,n,e[a],a,f):(n=e[a],i=!0)});if(!i)throw new TypeError("Reduce of empty array with no initial value");return n},T.find=T.detect=function(e,t,n){var r;return C(e,function(e,i,s){if(t.call(n,e,i,s))return r=e,!0}),r},T.filter=T.select=function(e,t,n){var r=[];return m&&e.filter===m?e.filter(t,n):(N(e,function(e,i,s){t.call(n,e,i,s)&&(r[r.length]=e)}),r)},T.reject=function(e,t,n){var r=[];return N(e,function(e,i,s){t.call(n,e,i,s)||(r[r.length]=e)}),r},T.every=T.all=function(e,t,r){t||(t=T.identity);var i=!0;return g&&e.every===g?e.every(t,r):(N(e,function(e,s,o){if(!(i=i&&t.call(r,e,s,o)))return n}),!!i)};var C=T.some=T.any=function(e,t,r){t||(t=T.identity);var i=!1;return y&&e.some===y?e.some(t,r):(N(e,function(e,s,o){if(i||(i=t.call(r,e,s,o)))return n}),!!i)};T.contains=T.include=function(e,t){var n=!1;return b&&e.indexOf===b?e.indexOf(t)!=-1:(n=C(e,function(e){return e===t}),n)},T.invoke=function(e,t){var n=u.call(arguments,2);return T.map(e,function(e){return(T.isFunction(t)?t:e[t]).apply(e,n)})},T.pluck=function(e,t){return T.map(e,function(e){return e[t]})},T.where=function(e,t){return T.isEmpty(t)?[]:T.filter(e,function(e){for(var n in t)if(t[n]!==e[n])return!1;return!0})},T.max=function(e,t,n){if(!t&&T.isArray(e)&&e[0]===+e[0]&&e.length<65535)return Math.max.apply(Math,e);if(!t&&T.isEmpty(e))return-Infinity;var r={computed:-Infinity};return N(e,function(e,i,s){var o=t?t.call(n,e,i,s):e;o>=r.computed&&(r={value:e,computed:o})}),r.value},T.min=function(e,t,n){if(!t&&T.isArray(e)&&e[0]===+e[0]&&e.length<65535)return Math.min.apply(Math,e);if(!t&&T.isEmpty(e))return Infinity;var r={computed:Infinity};return N(e,function(e,i,s){var o=t?t.call(n,e,i,s):e;or||n===void 0)return 1;if(n>>1;n.call(r,e[u])=0})})},T.difference=function(e){var t=a.apply(r,u.call(arguments,1));return T.filter(e,function(e){return!T.contains(t,e)})},T.zip=function(){var e=u.call(arguments),t=T.max(T.pluck(e,"length")),n=new Array(t);for(var r=0;r=0;n--)t=[e[n].apply(this,t)];return t[0]}},T.after=function(e,t){return e<=0?t():function(){if(--e<1)return t.apply(this,arguments)}},T.keys=S||function(e){if(e!==Object(e))throw new TypeError("Invalid object");var t=[];for(var n in e)T.has(e,n)&&(t[t.length]=n);return t},T.values=function(e){var t=[];for(var n in e)T.has(e,n)&&t.push(e[n]);return t},T.pairs=function(e){var t=[];for(var n in e)T.has(e,n)&&t.push([n,e[n]]);return t},T.invert=function(e){var t={};for(var n in e)T.has(e,n)&&(t[e[n]]=n);return t},T.functions=T.methods=function(e){var t=[];for(var n in e)T.isFunction(e[n])&&t.push(n);return t.sort()},T.extend=function(e){return N(u.call(arguments,1),function(t){for(var n in t)e[n]=t[n]}),e},T.pick=function(e){var t={},n=a.apply(r,u.call(arguments,1));return N(n,function(n){n in e&&(t[n]=e[n])}),t},T.omit=function(e){var t={},n=a.apply(r,u.call(arguments,1));for(var i in e)T.contains(n,i)||(t[i]=e[i]);return t},T.defaults=function(e){return N(u.call(arguments,1),function(t){for(var n in t)e[n]==null&&(e[n]=t[n])}),e},T.clone=function(e){return T.isObject(e)?T.isArray(e)?e.slice():T.extend({},e):e},T.tap=function(e,t){return t(e),e};var M=function(e,t,n,r){if(e===t)return e!==0||1/e==1/t;if(e==null||t==null)return e===t;e instanceof T&&(e=e._wrapped),t instanceof T&&(t=t._wrapped);var i=l.call(e);if(i!=l.call(t))return!1;switch(i){case"[object String]":return e==String(t);case"[object Number]":return e!=+e?t!=+t:e==0?1/e==1/t:e==+t;case"[object Date]":case"[object Boolean]":return+e==+t;case"[object RegExp]":return e.source==t.source&&e.global==t.global&&e.multiline==t.multiline&&e.ignoreCase==t.ignoreCase}if(typeof e!="object"||typeof t!="object")return!1;var s=n.length;while(s--)if(n[s]==e)return r[s]==t;n.push(e),r.push(t);var o=0,u=!0;if(i=="[object Array]"){o=e.length,u=o==t.length;if(u)while(o--)if(!(u=M(e[o],t[o],n,r)))break}else{var a=e.constructor,f=t.constructor;if(a!==f&&!(T.isFunction(a)&&a instanceof a&&T.isFunction(f)&&f instanceof f))return!1;for(var c in e)if(T.has(e,c)){o++;if(!(u=T.has(t,c)&&M(e[c],t[c],n,r)))break}if(u){for(c in t)if(T.has(t,c)&&!(o--))break;u=!o}}return n.pop(),r.pop(),u};T.isEqual=function(e,t){return M(e,t,[],[])},T.isEmpty=function(e){if(e==null)return!0;if(T.isArray(e)||T.isString(e))return e.length===0;for(var t in e)if(T.has(e,t))return!1;return!0},T.isElement=function(e){return!!e&&e.nodeType===1},T.isArray=E||function(e){return l.call(e)=="[object Array]"},T.isObject=function(e){return e===Object(e)},N(["Arguments","Function","String","Number","Date","RegExp"],function(e){T["is"+e]=function(t){return l.call(t)=="[object "+e+"]"}}),T.isArguments(arguments)||(T.isArguments=function(e){return!!e&&!!T.has(e,"callee")}),typeof /./!="function"&&(T.isFunction=function(e){return typeof e=="function"}),T.isFinite=function(e){return T.isNumber(e)&&isFinite(e)},T.isNaN=function(e){return T.isNumber(e)&&e!=+e},T.isBoolean=function(e){return e===!0||e===!1||l.call(e)=="[object Boolean]"},T.isNull=function(e){return e===null},T.isUndefined=function(e){return e===void 0},T.has=function(e,t){return c.call(e,t)},T.noConflict=function(){return e._=t,this},T.identity=function(e){return e},T.times=function(e,t,n){for(var r=0;r":">",'"':""","'":"'","/":"/"}};_.unescape=T.invert(_.escape);var D={escape:new RegExp("["+T.keys(_.escape).join("")+"]","g"),unescape:new RegExp("("+T.keys(_.unescape).join("|")+")","g")};T.each(["escape","unescape"],function(e){T[e]=function(t){return t==null?"":(""+t).replace(D[e],function(t){return _[e][t]})}}),T.result=function(e,t){if(e==null)return null;var n=e[t];return T.isFunction(n)?n.call(e):n},T.mixin=function(e){N(T.functions(e),function(t){var n=T[t]=e[t];T.prototype[t]=function(){var e=[this._wrapped];return o.apply(e,arguments),F.call(this,n.apply(T,e))}})};var P=0;T.uniqueId=function(e){var t=P++;return e?e+t:t},T.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var H=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},j=/\\|'|\r|\n|\t|\u2028|\u2029/g;T.template=function(e,t,n){n=T.defaults({},n,T.templateSettings);var r=new RegExp([(n.escape||H).source,(n.interpolate||H).source,(n.evaluate||H).source].join("|")+"|$","g"),i=0,s="__p+='";e.replace(r,function(t,n,r,o,u){s+=e.slice(i,u).replace(j,function(e){return"\\"+B[e]}),s+=n?"'+\n((__t=("+n+"))==null?'':_.escape(__t))+\n'":r?"'+\n((__t=("+r+"))==null?'':__t)+\n'":o?"';\n"+o+"\n__p+='":"",i=u+t.length}),s+="';\n",n.variable||(s="with(obj||{}){\n"+s+"}\n"),s="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+s+"return __p;\n";try{var o=new Function(n.variable||"obj","_",s)}catch(u){throw u.source=s,u}if(t)return o(t,T);var a=function(e){return o.call(this,e,T)};return a.source="function("+(n.variable||"obj")+"){\n"+s+"}",a},T.chain=function(e){return T(e).chain()};var F=function(e){return this._chain?T(e).chain():e};T.mixin(T),N(["pop","push","reverse","shift","sort","splice","unshift"],function(e){var t=r[e];T.prototype[e]=function(){var n=this._wrapped;return t.apply(n,arguments),(e=="shift"||e=="splice")&&n.length===0&&delete n[0],F.call(this,n)}}),N(["concat","join","slice"],function(e){var t=r[e];T.prototype[e]=function(){return F.call(this,t.apply(this._wrapped,arguments))}}),T.extend(T.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/lib/underscore/underscore.js b/src/Umbraco.Web.UI.Client/lib/underscore/underscore.js deleted file mode 100644 index 446b5a08fe..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/underscore/underscore.js +++ /dev/null @@ -1,1189 +0,0 @@ -// Underscore.js 1.4.1 -// http://underscorejs.org -// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. -// Underscore may be freely distributed under the MIT license. - -(function() { - - // Baseline setup - // -------------- - - // Establish the root object, `window` in the browser, or `global` on the server. - var root = this; - - // Save the previous value of the `_` variable. - var previousUnderscore = root._; - - // Establish the object that gets returned to break out of a loop iteration. - var breaker = {}; - - // Save bytes in the minified (but not gzipped) version: - var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; - - // Create quick reference variables for speed access to core prototypes. - var push = ArrayProto.push, - slice = ArrayProto.slice, - concat = ArrayProto.concat, - unshift = ArrayProto.unshift, - toString = ObjProto.toString, - hasOwnProperty = ObjProto.hasOwnProperty; - - // All **ECMAScript 5** native function implementations that we hope to use - // are declared here. - var - nativeForEach = ArrayProto.forEach, - nativeMap = ArrayProto.map, - nativeReduce = ArrayProto.reduce, - nativeReduceRight = ArrayProto.reduceRight, - nativeFilter = ArrayProto.filter, - nativeEvery = ArrayProto.every, - nativeSome = ArrayProto.some, - nativeIndexOf = ArrayProto.indexOf, - nativeLastIndexOf = ArrayProto.lastIndexOf, - nativeIsArray = Array.isArray, - nativeKeys = Object.keys, - nativeBind = FuncProto.bind; - - // Create a safe reference to the Underscore object for use below. - var _ = function(obj) { - if (obj instanceof _) return obj; - if (!(this instanceof _)) return new _(obj); - this._wrapped = obj; - }; - - // Export the Underscore object for **Node.js**, with - // backwards-compatibility for the old `require()` API. If we're in - // the browser, add `_` as a global object via a string identifier, - // for Closure Compiler "advanced" mode. - if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) { - exports = module.exports = _; - } - exports._ = _; - } else { - root['_'] = _; - } - - // Current version. - _.VERSION = '1.4.1'; - - // Collection Functions - // -------------------- - - // The cornerstone, an `each` implementation, aka `forEach`. - // Handles objects with the built-in `forEach`, arrays, and raw objects. - // Delegates to **ECMAScript 5**'s native `forEach` if available. - var each = _.each = _.forEach = function(obj, iterator, context) { - if (nativeForEach && obj.forEach === nativeForEach) { - obj.forEach(iterator, context); - } else if (obj.length === +obj.length) { - for (var i = 0, l = obj.length; i < l; i++) { - if (iterator.call(context, obj[i], i, obj) === breaker) return; - } - } else { - for (var key in obj) { - if (_.has(obj, key)) { - if (iterator.call(context, obj[key], key, obj) === breaker) return; - } - } - } - }; - - // Return the results of applying the iterator to each element. - // Delegates to **ECMAScript 5**'s native `map` if available. - _.map = _.collect = function(obj, iterator, context) { - var results = []; - if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); - each(obj, function(value, index, list) { - results[results.length] = iterator.call(context, value, index, list); - }); - return results; - }; - - // **Reduce** builds up a single result from a list of values, aka `inject`, - // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. - _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { - var initial = arguments.length > 2; - if (nativeReduce && obj.reduce === nativeReduce) { - if (context) iterator = _.bind(iterator, context); - return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); - } - each(obj, function(value, index, list) { - if (!initial) { - memo = value; - initial = true; - } else { - memo = iterator.call(context, memo, value, index, list); - } - }); - if (!initial) throw new TypeError('Reduce of empty array with no initial value'); - return memo; - }; - - // The right-associative version of reduce, also known as `foldr`. - // Delegates to **ECMAScript 5**'s native `reduceRight` if available. - _.reduceRight = _.foldr = function(obj, iterator, memo, context) { - var initial = arguments.length > 2; - if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { - if (context) iterator = _.bind(iterator, context); - return arguments.length > 2 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); - } - var length = obj.length; - if (length !== +length) { - var keys = _.keys(obj); - length = keys.length; - } - each(obj, function(value, index, list) { - index = keys ? keys[--length] : --length; - if (!initial) { - memo = obj[index]; - initial = true; - } else { - memo = iterator.call(context, memo, obj[index], index, list); - } - }); - if (!initial) throw new TypeError('Reduce of empty array with no initial value'); - return memo; - }; - - // Return the first value which passes a truth test. Aliased as `detect`. - _.find = _.detect = function(obj, iterator, context) { - var result; - any(obj, function(value, index, list) { - if (iterator.call(context, value, index, list)) { - result = value; - return true; - } - }); - return result; - }; - - // Return all the elements that pass a truth test. - // Delegates to **ECMAScript 5**'s native `filter` if available. - // Aliased as `select`. - _.filter = _.select = function(obj, iterator, context) { - var results = []; - if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); - each(obj, function(value, index, list) { - if (iterator.call(context, value, index, list)) results[results.length] = value; - }); - return results; - }; - - // Return all the elements for which a truth test fails. - _.reject = function(obj, iterator, context) { - var results = []; - each(obj, function(value, index, list) { - if (!iterator.call(context, value, index, list)) results[results.length] = value; - }); - return results; - }; - - // Determine whether all of the elements match a truth test. - // Delegates to **ECMAScript 5**'s native `every` if available. - // Aliased as `all`. - _.every = _.all = function(obj, iterator, context) { - iterator || (iterator = _.identity); - var result = true; - if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); - each(obj, function(value, index, list) { - if (!(result = result && iterator.call(context, value, index, list))) return breaker; - }); - return !!result; - }; - - // Determine if at least one element in the object matches a truth test. - // Delegates to **ECMAScript 5**'s native `some` if available. - // Aliased as `any`. - var any = _.some = _.any = function(obj, iterator, context) { - iterator || (iterator = _.identity); - var result = false; - if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); - each(obj, function(value, index, list) { - if (result || (result = iterator.call(context, value, index, list))) return breaker; - }); - return !!result; - }; - - // Determine if the array or object contains a given value (using `===`). - // Aliased as `include`. - _.contains = _.include = function(obj, target) { - var found = false; - if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; - found = any(obj, function(value) { - return value === target; - }); - return found; - }; - - // Invoke a method (with arguments) on every item in a collection. - _.invoke = function(obj, method) { - var args = slice.call(arguments, 2); - return _.map(obj, function(value) { - return (_.isFunction(method) ? method : value[method]).apply(value, args); - }); - }; - - // Convenience version of a common use case of `map`: fetching a property. - _.pluck = function(obj, key) { - return _.map(obj, function(value){ return value[key]; }); - }; - - // Convenience version of a common use case of `filter`: selecting only objects - // with specific `key:value` pairs. - _.where = function(obj, attrs) { - if (_.isEmpty(attrs)) return []; - return _.filter(obj, function(value) { - for (var key in attrs) { - if (attrs[key] !== value[key]) return false; - } - return true; - }); - }; - - // Return the maximum element or (element-based computation). - // Can't optimize arrays of integers longer than 65,535 elements. - // See: https://bugs.webkit.org/show_bug.cgi?id=80797 - _.max = function(obj, iterator, context) { - if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { - return Math.max.apply(Math, obj); - } - if (!iterator && _.isEmpty(obj)) return -Infinity; - var result = {computed : -Infinity}; - each(obj, function(value, index, list) { - var computed = iterator ? iterator.call(context, value, index, list) : value; - computed >= result.computed && (result = {value : value, computed : computed}); - }); - return result.value; - }; - - // Return the minimum element (or element-based computation). - _.min = function(obj, iterator, context) { - if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { - return Math.min.apply(Math, obj); - } - if (!iterator && _.isEmpty(obj)) return Infinity; - var result = {computed : Infinity}; - each(obj, function(value, index, list) { - var computed = iterator ? iterator.call(context, value, index, list) : value; - computed < result.computed && (result = {value : value, computed : computed}); - }); - return result.value; - }; - - // Shuffle an array. - _.shuffle = function(obj) { - var rand; - var index = 0; - var shuffled = []; - each(obj, function(value) { - rand = _.random(index++); - shuffled[index - 1] = shuffled[rand]; - shuffled[rand] = value; - }); - return shuffled; - }; - - // An internal function to generate lookup iterators. - var lookupIterator = function(value) { - return _.isFunction(value) ? value : function(obj){ return obj[value]; }; - }; - - // Sort the object's values by a criterion produced by an iterator. - _.sortBy = function(obj, value, context) { - var iterator = lookupIterator(value); - return _.pluck(_.map(obj, function(value, index, list) { - return { - value : value, - index : index, - criteria : iterator.call(context, value, index, list) - }; - }).sort(function(left, right) { - var a = left.criteria; - var b = right.criteria; - if (a !== b) { - if (a > b || a === void 0) return 1; - if (a < b || b === void 0) return -1; - } - return left.index < right.index ? -1 : 1; - }), 'value'); - }; - - // An internal function used for aggregate "group by" operations. - var group = function(obj, value, context, behavior) { - var result = {}; - var iterator = lookupIterator(value); - each(obj, function(value, index) { - var key = iterator.call(context, value, index, obj); - behavior(result, key, value); - }); - return result; - }; - - // Groups the object's values by a criterion. Pass either a string attribute - // to group by, or a function that returns the criterion. - _.groupBy = function(obj, value, context) { - return group(obj, value, context, function(result, key, value) { - (_.has(result, key) ? result[key] : (result[key] = [])).push(value); - }); - }; - - // Counts instances of an object that group by a certain criterion. Pass - // either a string attribute to count by, or a function that returns the - // criterion. - _.countBy = function(obj, value, context) { - return group(obj, value, context, function(result, key, value) { - if (!_.has(result, key)) result[key] = 0; - result[key]++; - }); - }; - - // Use a comparator function to figure out the smallest index at which - // an object should be inserted so as to maintain order. Uses binary search. - _.sortedIndex = function(array, obj, iterator, context) { - iterator = iterator == null ? _.identity : lookupIterator(iterator); - var value = iterator.call(context, obj); - var low = 0, high = array.length; - while (low < high) { - var mid = (low + high) >>> 1; - iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; - } - return low; - }; - - // Safely convert anything iterable into a real, live array. - _.toArray = function(obj) { - if (!obj) return []; - if (obj.length === +obj.length) return slice.call(obj); - return _.values(obj); - }; - - // Return the number of elements in an object. - _.size = function(obj) { - return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; - }; - - // Array Functions - // --------------- - - // Get the first element of an array. Passing **n** will return the first N - // values in the array. Aliased as `head` and `take`. The **guard** check - // allows it to work with `_.map`. - _.first = _.head = _.take = function(array, n, guard) { - return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; - }; - - // Returns everything but the last entry of the array. Especially useful on - // the arguments object. Passing **n** will return all the values in - // the array, excluding the last N. The **guard** check allows it to work with - // `_.map`. - _.initial = function(array, n, guard) { - return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); - }; - - // Get the last element of an array. Passing **n** will return the last N - // values in the array. The **guard** check allows it to work with `_.map`. - _.last = function(array, n, guard) { - if ((n != null) && !guard) { - return slice.call(array, Math.max(array.length - n, 0)); - } else { - return array[array.length - 1]; - } - }; - - // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. - // Especially useful on the arguments object. Passing an **n** will return - // the rest N values in the array. The **guard** - // check allows it to work with `_.map`. - _.rest = _.tail = _.drop = function(array, n, guard) { - return slice.call(array, (n == null) || guard ? 1 : n); - }; - - // Trim out all falsy values from an array. - _.compact = function(array) { - return _.filter(array, function(value){ return !!value; }); - }; - - // Internal implementation of a recursive `flatten` function. - var flatten = function(input, shallow, output) { - each(input, function(value) { - if (_.isArray(value)) { - shallow ? push.apply(output, value) : flatten(value, shallow, output); - } else { - output.push(value); - } - }); - return output; - }; - - // Return a completely flattened version of an array. - _.flatten = function(array, shallow) { - return flatten(array, shallow, []); - }; - - // Return a version of the array that does not contain the specified value(s). - _.without = function(array) { - return _.difference(array, slice.call(arguments, 1)); - }; - - // Produce a duplicate-free version of the array. If the array has already - // been sorted, you have the option of using a faster algorithm. - // Aliased as `unique`. - _.uniq = _.unique = function(array, isSorted, iterator, context) { - var initial = iterator ? _.map(array, iterator, context) : array; - var results = []; - var seen = []; - each(initial, function(value, index) { - if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { - seen.push(value); - results.push(array[index]); - } - }); - return results; - }; - - // Produce an array that contains the union: each distinct element from all of - // the passed-in arrays. - _.union = function() { - return _.uniq(concat.apply(ArrayProto, arguments)); - }; - - // Produce an array that contains every item shared between all the - // passed-in arrays. - _.intersection = function(array) { - var rest = slice.call(arguments, 1); - return _.filter(_.uniq(array), function(item) { - return _.every(rest, function(other) { - return _.indexOf(other, item) >= 0; - }); - }); - }; - - // Take the difference between one array and a number of other arrays. - // Only the elements present in just the first array will remain. - _.difference = function(array) { - var rest = concat.apply(ArrayProto, slice.call(arguments, 1)); - return _.filter(array, function(value){ return !_.contains(rest, value); }); - }; - - // Zip together multiple lists into a single array -- elements that share - // an index go together. - _.zip = function() { - var args = slice.call(arguments); - var length = _.max(_.pluck(args, 'length')); - var results = new Array(length); - for (var i = 0; i < length; i++) { - results[i] = _.pluck(args, "" + i); - } - return results; - }; - - // Converts lists into objects. Pass either a single array of `[key, value]` - // pairs, or two parallel arrays of the same length -- one of keys, and one of - // the corresponding values. - _.object = function(list, values) { - var result = {}; - for (var i = 0, l = list.length; i < l; i++) { - if (values) { - result[list[i]] = values[i]; - } else { - result[list[i][0]] = list[i][1]; - } - } - return result; - }; - - // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), - // we need this function. Return the position of the first occurrence of an - // item in an array, or -1 if the item is not included in the array. - // Delegates to **ECMAScript 5**'s native `indexOf` if available. - // If the array is large and already in sort order, pass `true` - // for **isSorted** to use binary search. - _.indexOf = function(array, item, isSorted) { - var i = 0, l = array.length; - if (isSorted) { - if (typeof isSorted == 'number') { - i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted); - } else { - i = _.sortedIndex(array, item); - return array[i] === item ? i : -1; - } - } - if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted); - for (; i < l; i++) if (array[i] === item) return i; - return -1; - }; - - // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. - _.lastIndexOf = function(array, item, from) { - var hasIndex = from != null; - if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) { - return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item); - } - var i = (hasIndex ? from : array.length); - while (i--) if (array[i] === item) return i; - return -1; - }; - - // Generate an integer Array containing an arithmetic progression. A port of - // the native Python `range()` function. See - // [the Python documentation](http://docs.python.org/library/functions.html#range). - _.range = function(start, stop, step) { - if (arguments.length <= 1) { - stop = start || 0; - start = 0; - } - step = arguments[2] || 1; - - var len = Math.max(Math.ceil((stop - start) / step), 0); - var idx = 0; - var range = new Array(len); - - while(idx < len) { - range[idx++] = start; - start += step; - } - - return range; - }; - - // Function (ahem) Functions - // ------------------ - - // Reusable constructor function for prototype setting. - var ctor = function(){}; - - // Create a function bound to a given object (assigning `this`, and arguments, - // optionally). Binding with arguments is also known as `curry`. - // Delegates to **ECMAScript 5**'s native `Function.bind` if available. - // We check for `func.bind` first, to fail fast when `func` is undefined. - _.bind = function bind(func, context) { - var bound, args; - if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); - if (!_.isFunction(func)) throw new TypeError; - args = slice.call(arguments, 2); - return bound = function() { - if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); - ctor.prototype = func.prototype; - var self = new ctor; - var result = func.apply(self, args.concat(slice.call(arguments))); - if (Object(result) === result) return result; - return self; - }; - }; - - // Bind all of an object's methods to that object. Useful for ensuring that - // all callbacks defined on an object belong to it. - _.bindAll = function(obj) { - var funcs = slice.call(arguments, 1); - if (funcs.length == 0) funcs = _.functions(obj); - each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); - return obj; - }; - - // Memoize an expensive function by storing its results. - _.memoize = function(func, hasher) { - var memo = {}; - hasher || (hasher = _.identity); - return function() { - var key = hasher.apply(this, arguments); - return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); - }; - }; - - // Delays a function for the given number of milliseconds, and then calls - // it with the arguments supplied. - _.delay = function(func, wait) { - var args = slice.call(arguments, 2); - return setTimeout(function(){ return func.apply(null, args); }, wait); - }; - - // Defers a function, scheduling it to run after the current call stack has - // cleared. - _.defer = function(func) { - return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); - }; - - // Returns a function, that, when invoked, will only be triggered at most once - // during a given window of time. - _.throttle = function(func, wait) { - var context, args, timeout, throttling, more, result; - var whenDone = _.debounce(function(){ more = throttling = false; }, wait); - return function() { - context = this; args = arguments; - var later = function() { - timeout = null; - if (more) { - result = func.apply(context, args); - } - whenDone(); - }; - if (!timeout) timeout = setTimeout(later, wait); - if (throttling) { - more = true; - } else { - throttling = true; - result = func.apply(context, args); - } - whenDone(); - return result; - }; - }; - - // Returns a function, that, as long as it continues to be invoked, will not - // be triggered. The function will be called after it stops being called for - // N milliseconds. If `immediate` is passed, trigger the function on the - // leading edge, instead of the trailing. - _.debounce = function(func, wait, immediate) { - var timeout, result; - return function() { - var context = this, args = arguments; - var later = function() { - timeout = null; - if (!immediate) result = func.apply(context, args); - }; - var callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if (callNow) result = func.apply(context, args); - return result; - }; - }; - - // Returns a function that will be executed at most one time, no matter how - // often you call it. Useful for lazy initialization. - _.once = function(func) { - var ran = false, memo; - return function() { - if (ran) return memo; - ran = true; - memo = func.apply(this, arguments); - func = null; - return memo; - }; - }; - - // Returns the first function passed as an argument to the second, - // allowing you to adjust arguments, run code before and after, and - // conditionally execute the original function. - _.wrap = function(func, wrapper) { - return function() { - var args = [func]; - push.apply(args, arguments); - return wrapper.apply(this, args); - }; - }; - - // Returns a function that is the composition of a list of functions, each - // consuming the return value of the function that follows. - _.compose = function() { - var funcs = arguments; - return function() { - var args = arguments; - for (var i = funcs.length - 1; i >= 0; i--) { - args = [funcs[i].apply(this, args)]; - } - return args[0]; - }; - }; - - // Returns a function that will only be executed after being called N times. - _.after = function(times, func) { - if (times <= 0) return func(); - return function() { - if (--times < 1) { - return func.apply(this, arguments); - } - }; - }; - - // Object Functions - // ---------------- - - // Retrieve the names of an object's properties. - // Delegates to **ECMAScript 5**'s native `Object.keys` - _.keys = nativeKeys || function(obj) { - if (obj !== Object(obj)) throw new TypeError('Invalid object'); - var keys = []; - for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key; - return keys; - }; - - // Retrieve the values of an object's properties. - _.values = function(obj) { - var values = []; - for (var key in obj) if (_.has(obj, key)) values.push(obj[key]); - return values; - }; - - // Convert an object into a list of `[key, value]` pairs. - _.pairs = function(obj) { - var pairs = []; - for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]); - return pairs; - }; - - // Invert the keys and values of an object. The values must be serializable. - _.invert = function(obj) { - var result = {}; - for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key; - return result; - }; - - // Return a sorted list of the function names available on the object. - // Aliased as `methods` - _.functions = _.methods = function(obj) { - var names = []; - for (var key in obj) { - if (_.isFunction(obj[key])) names.push(key); - } - return names.sort(); - }; - - // Extend a given object with all the properties in passed-in object(s). - _.extend = function(obj) { - each(slice.call(arguments, 1), function(source) { - for (var prop in source) { - obj[prop] = source[prop]; - } - }); - return obj; - }; - - // Return a copy of the object only containing the whitelisted properties. - _.pick = function(obj) { - var copy = {}; - var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); - each(keys, function(key) { - if (key in obj) copy[key] = obj[key]; - }); - return copy; - }; - - // Return a copy of the object without the blacklisted properties. - _.omit = function(obj) { - var copy = {}; - var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); - for (var key in obj) { - if (!_.contains(keys, key)) copy[key] = obj[key]; - } - return copy; - }; - - // Fill in a given object with default properties. - _.defaults = function(obj) { - each(slice.call(arguments, 1), function(source) { - for (var prop in source) { - if (obj[prop] == null) obj[prop] = source[prop]; - } - }); - return obj; - }; - - // Create a (shallow-cloned) duplicate of an object. - _.clone = function(obj) { - if (!_.isObject(obj)) return obj; - return _.isArray(obj) ? obj.slice() : _.extend({}, obj); - }; - - // Invokes interceptor with the obj, and then returns obj. - // The primary purpose of this method is to "tap into" a method chain, in - // order to perform operations on intermediate results within the chain. - _.tap = function(obj, interceptor) { - interceptor(obj); - return obj; - }; - - // Internal recursive comparison function for `isEqual`. - var eq = function(a, b, aStack, bStack) { - // Identical objects are equal. `0 === -0`, but they aren't identical. - // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. - if (a === b) return a !== 0 || 1 / a == 1 / b; - // A strict comparison is necessary because `null == undefined`. - if (a == null || b == null) return a === b; - // Unwrap any wrapped objects. - if (a instanceof _) a = a._wrapped; - if (b instanceof _) b = b._wrapped; - // Compare `[[Class]]` names. - var className = toString.call(a); - if (className != toString.call(b)) return false; - switch (className) { - // Strings, numbers, dates, and booleans are compared by value. - case '[object String]': - // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is - // equivalent to `new String("5")`. - return a == String(b); - case '[object Number]': - // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for - // other numeric values. - return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); - case '[object Date]': - case '[object Boolean]': - // Coerce dates and booleans to numeric primitive values. Dates are compared by their - // millisecond representations. Note that invalid dates with millisecond representations - // of `NaN` are not equivalent. - return +a == +b; - // RegExps are compared by their source patterns and flags. - case '[object RegExp]': - return a.source == b.source && - a.global == b.global && - a.multiline == b.multiline && - a.ignoreCase == b.ignoreCase; - } - if (typeof a != 'object' || typeof b != 'object') return false; - // Assume equality for cyclic structures. The algorithm for detecting cyclic - // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. - var length = aStack.length; - while (length--) { - // Linear search. Performance is inversely proportional to the number of - // unique nested structures. - if (aStack[length] == a) return bStack[length] == b; - } - // Add the first object to the stack of traversed objects. - aStack.push(a); - bStack.push(b); - var size = 0, result = true; - // Recursively compare objects and arrays. - if (className == '[object Array]') { - // Compare array lengths to determine if a deep comparison is necessary. - size = a.length; - result = size == b.length; - if (result) { - // Deep compare the contents, ignoring non-numeric properties. - while (size--) { - if (!(result = eq(a[size], b[size], aStack, bStack))) break; - } - } - } else { - // Objects with different constructors are not equivalent, but `Object`s - // from different frames are. - var aCtor = a.constructor, bCtor = b.constructor; - if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && - _.isFunction(bCtor) && (bCtor instanceof bCtor))) { - return false; - } - // Deep compare objects. - for (var key in a) { - if (_.has(a, key)) { - // Count the expected number of properties. - size++; - // Deep compare each member. - if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break; - } - } - // Ensure that both objects contain the same number of properties. - if (result) { - for (key in b) { - if (_.has(b, key) && !(size--)) break; - } - result = !size; - } - } - // Remove the first object from the stack of traversed objects. - aStack.pop(); - bStack.pop(); - return result; - }; - - // Perform a deep comparison to check if two objects are equal. - _.isEqual = function(a, b) { - return eq(a, b, [], []); - }; - - // Is a given array, string, or object empty? - // An "empty" object has no enumerable own-properties. - _.isEmpty = function(obj) { - if (obj == null) return true; - if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; - for (var key in obj) if (_.has(obj, key)) return false; - return true; - }; - - // Is a given value a DOM element? - _.isElement = function(obj) { - return !!(obj && obj.nodeType === 1); - }; - - // Is a given value an array? - // Delegates to ECMA5's native Array.isArray - _.isArray = nativeIsArray || function(obj) { - return toString.call(obj) == '[object Array]'; - }; - - // Is a given variable an object? - _.isObject = function(obj) { - return obj === Object(obj); - }; - - // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. - each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { - _['is' + name] = function(obj) { - return toString.call(obj) == '[object ' + name + ']'; - }; - }); - - // Define a fallback version of the method in browsers (ahem, IE), where - // there isn't any inspectable "Arguments" type. - if (!_.isArguments(arguments)) { - _.isArguments = function(obj) { - return !!(obj && _.has(obj, 'callee')); - }; - } - - // Optimize `isFunction` if appropriate. - if (typeof (/./) !== 'function') { - _.isFunction = function(obj) { - return typeof obj === 'function'; - }; - } - - // Is a given object a finite number? - _.isFinite = function(obj) { - return _.isNumber(obj) && isFinite(obj); - }; - - // Is the given value `NaN`? (NaN is the only number which does not equal itself). - _.isNaN = function(obj) { - return _.isNumber(obj) && obj != +obj; - }; - - // Is a given value a boolean? - _.isBoolean = function(obj) { - return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; - }; - - // Is a given value equal to null? - _.isNull = function(obj) { - return obj === null; - }; - - // Is a given variable undefined? - _.isUndefined = function(obj) { - return obj === void 0; - }; - - // Shortcut function for checking if an object has a given property directly - // on itself (in other words, not on a prototype). - _.has = function(obj, key) { - return hasOwnProperty.call(obj, key); - }; - - // Utility Functions - // ----------------- - - // Run Underscore.js in *noConflict* mode, returning the `_` variable to its - // previous owner. Returns a reference to the Underscore object. - _.noConflict = function() { - root._ = previousUnderscore; - return this; - }; - - // Keep the identity function around for default iterators. - _.identity = function(value) { - return value; - }; - - // Run a function **n** times. - _.times = function(n, iterator, context) { - for (var i = 0; i < n; i++) iterator.call(context, i); - }; - - // Return a random integer between min and max (inclusive). - _.random = function(min, max) { - if (max == null) { - max = min; - min = 0; - } - return min + (0 | Math.random() * (max - min + 1)); - }; - - // List of HTML entities for escaping. - var entityMap = { - escape: { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''', - '/': '/' - } - }; - entityMap.unescape = _.invert(entityMap.escape); - - // Regexes containing the keys and values listed immediately above. - var entityRegexes = { - escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'), - unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g') - }; - - // Functions for escaping and unescaping strings to/from HTML interpolation. - _.each(['escape', 'unescape'], function(method) { - _[method] = function(string) { - if (string == null) return ''; - return ('' + string).replace(entityRegexes[method], function(match) { - return entityMap[method][match]; - }); - }; - }); - - // If the value of the named property is a function then invoke it; - // otherwise, return it. - _.result = function(object, property) { - if (object == null) return null; - var value = object[property]; - return _.isFunction(value) ? value.call(object) : value; - }; - - // Add your own custom functions to the Underscore object. - _.mixin = function(obj) { - each(_.functions(obj), function(name){ - var func = _[name] = obj[name]; - _.prototype[name] = function() { - var args = [this._wrapped]; - push.apply(args, arguments); - return result.call(this, func.apply(_, args)); - }; - }); - }; - - // Generate a unique integer id (unique within the entire client session). - // Useful for temporary DOM ids. - var idCounter = 0; - _.uniqueId = function(prefix) { - var id = idCounter++; - return prefix ? prefix + id : id; - }; - - // By default, Underscore uses ERB-style template delimiters, change the - // following template settings to use alternative delimiters. - _.templateSettings = { - evaluate : /<%([\s\S]+?)%>/g, - interpolate : /<%=([\s\S]+?)%>/g, - escape : /<%-([\s\S]+?)%>/g - }; - - // When customizing `templateSettings`, if you don't want to define an - // interpolation, evaluation or escaping regex, we need one that is - // guaranteed not to match. - var noMatch = /(.)^/; - - // Certain characters need to be escaped so that they can be put into a - // string literal. - var escapes = { - "'": "'", - '\\': '\\', - '\r': 'r', - '\n': 'n', - '\t': 't', - '\u2028': 'u2028', - '\u2029': 'u2029' - }; - - var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; - - // JavaScript micro-templating, similar to John Resig's implementation. - // Underscore templating handles arbitrary delimiters, preserves whitespace, - // and correctly escapes quotes within interpolated code. - _.template = function(text, data, settings) { - settings = _.defaults({}, settings, _.templateSettings); - - // Combine delimiters into one regular expression via alternation. - var matcher = new RegExp([ - (settings.escape || noMatch).source, - (settings.interpolate || noMatch).source, - (settings.evaluate || noMatch).source - ].join('|') + '|$', 'g'); - - // Compile the template source, escaping string literals appropriately. - var index = 0; - var source = "__p+='"; - text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { - source += text.slice(index, offset) - .replace(escaper, function(match) { return '\\' + escapes[match]; }); - source += - escape ? "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'" : - interpolate ? "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'" : - evaluate ? "';\n" + evaluate + "\n__p+='" : ''; - index = offset + match.length; - }); - source += "';\n"; - - // If a variable is not specified, place data values in local scope. - if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; - - source = "var __t,__p='',__j=Array.prototype.join," + - "print=function(){__p+=__j.call(arguments,'');};\n" + - source + "return __p;\n"; - - try { - var render = new Function(settings.variable || 'obj', '_', source); - } catch (e) { - e.source = source; - throw e; - } - - if (data) return render(data, _); - var template = function(data) { - return render.call(this, data, _); - }; - - // Provide the compiled function source as a convenience for precompilation. - template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; - - return template; - }; - - // Add a "chain" function, which will delegate to the wrapper. - _.chain = function(obj) { - return _(obj).chain(); - }; - - // OOP - // --------------- - // If Underscore is called as a function, it returns a wrapped object that - // can be used OO-style. This wrapper holds altered versions of all the - // underscore functions. Wrapped objects may be chained. - - // Helper function to continue chaining intermediate results. - var result = function(obj) { - return this._chain ? _(obj).chain() : obj; - }; - - // Add all of the Underscore functions to the wrapper object. - _.mixin(_); - - // Add all mutator Array functions to the wrapper. - each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { - var method = ArrayProto[name]; - _.prototype[name] = function() { - var obj = this._wrapped; - method.apply(obj, arguments); - if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; - return result.call(this, obj); - }; - }); - - // Add all accessor Array functions to the wrapper. - each(['concat', 'join', 'slice'], function(name) { - var method = ArrayProto[name]; - _.prototype[name] = function() { - return result.call(this, method.apply(this._wrapped, arguments)); - }; - }); - - _.extend(_.prototype, { - - // Start chaining a wrapped Underscore object. - chain: function() { - this._chain = true; - return this; - }, - - // Extracts the result from a wrapped and chained object. - value: function() { - return this._wrapped; - } - - }); - -}).call(this); diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index b9fa69ccb4..b8ac55d60c 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -1,48 +1,49 @@ -{ - "author": "Umbraco HQ", - "name": "umbraco", - "homepage": "https://github.com/umbraco/umbraco-cms/", - "version": "7.1.2", - "repository": { - "type": "git", - "url": "git@github.com:umbraco/umbraco-cms.git" - }, - "bugs": { - "url": "https://issues.umbraco.org" - }, - "licenses": [ - { - "type": "MIT", - "url": "https://github.com/umbraco/Umbraco-CMS/blob/7.0.0/docs/License.txt" - } - ], - "engines": { - "node": ">= 0.8.4" - }, - "dependencies": {}, - "devDependencies": { - "grunt": "~0.4.0", - "phantomjs": "~1.9.1-0", - "grunt-recess": "~0.3", - "grunt-contrib-clean": "~0.4.0", - "grunt-contrib-copy": "~0.4.0", - "grunt-contrib-jshint": "~0.2.0", - "grunt-contrib-concat": "~0.1.3", - "grunt-contrib-uglify": "~0.1.1", - "grunt-html2js": "~0.1.0", - "grunt-contrib-watch": "~0.3.1", - "grunt-open": "~0.2.0", - "grunt-contrib-connect": "~0.3.0", - "grunt-karma": "~0.5", - "karma-chrome-launcher": "0.0.2", - "karma-script-launcher": "0.0.1", - "karma-firefox-launcher": "0.0.2", - "karma-jasmine": "0.0.1", - "karma-requirejs": "0.0.1", - "karma-coffee-preprocessor": "0.0.1", - "karma": "~0.9", - "karma-phantomjs-launcher": "0.0.2", - "grunt-ngdocs": "~0.1.2", - "bower": "~1.3.3" - } -} +{ + "author": "Umbraco HQ", + "name": "umbraco", + "homepage": "https://github.com/umbraco/umbraco-cms/", + "version": "7.1.2", + "repository": { + "type": "git", + "url": "git@github.com:umbraco/umbraco-cms.git" + }, + "bugs": { + "url": "https://issues.umbraco.org" + }, + "licenses": [ + { + "type": "MIT", + "url": "https://github.com/umbraco/Umbraco-CMS/blob/7.0.0/docs/License.txt" + } + ], + "engines": { + "node": ">= 0.8.4" + }, + "dependencies": {}, + "devDependencies": { + "bower": "~1.3.3", + "grunt": "~0.4.0", + "grunt-bower-task": "^0.4.0", + "grunt-contrib-clean": "~0.4.0", + "grunt-contrib-concat": "~0.1.3", + "grunt-contrib-connect": "~0.3.0", + "grunt-contrib-copy": "~0.7.0", + "grunt-contrib-jshint": "~0.2.0", + "grunt-contrib-uglify": "~0.1.1", + "grunt-contrib-watch": "~0.3.1", + "grunt-html2js": "~0.1.0", + "grunt-karma": "~0.5", + "grunt-ngdocs": "~0.1.2", + "grunt-open": "~0.2.0", + "grunt-recess": "~0.3", + "karma": "~0.9", + "karma-chrome-launcher": "0.0.2", + "karma-coffee-preprocessor": "0.0.1", + "karma-firefox-launcher": "0.0.2", + "karma-jasmine": "0.0.1", + "karma-phantomjs-launcher": "0.0.2", + "karma-requirejs": "0.0.1", + "karma-script-launcher": "0.0.1", + "phantomjs": "~1.9.1-0" + } +} diff --git a/src/Umbraco.Web.UI.Client/src/assets/css/nonodes.style.min.css b/src/Umbraco.Web.UI.Client/src/assets/css/nonodes.style.min.css new file mode 100644 index 0000000000..68cc761e96 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/css/nonodes.style.min.css @@ -0,0 +1 @@ +abbr,address,article,aside,audio,b,blockquote,body,canvas,caption,cite,code,dd,del,details,dfn,div,dl,dt,em,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,html,i,iframe,img,ins,kbd,label,legend,li,mark,menu,nav,object,ol,p,pre,q,samp,section,small,span,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,ul,var,video{margin:0;padding:0;outline:0;border:0;background:0 0;vertical-align:baseline;font-size:100%}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}nav ul{list-style:none}blockquote,q{quotes:none}blockquote:after,blockquote:before,q:after,q:before{content:'';content:none}a{margin:0;padding:0;background:0 0;vertical-align:baseline;font-size:100%}ins{background-color:#ff9;color:#000;text-decoration:none}mark{background-color:#ff9;color:#000;font-weight:700;font-style:italic}del{text-decoration:line-through}abbr[title],dfn[title]{border-bottom:1px dotted;cursor:help}table{border-spacing:0;border-collapse:collapse}hr{display:block;margin:1em 0;padding:0;height:1px;border:0;border-top:1px solid #ccc}input,select{vertical-align:middle}html{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}*,:after,:before{box-sizing:border-box}body,html{height:100%;width:100%;color:#fff;font-family:'Open Sans',sans-serif;font-weight:400;font-size:.9375em;line-height:1.5}h1{font-size:1.7em;margin:40px auto 10px;font-weight:700}h2{font-size:1.35em;margin:0 auto .4em;font-weight:700}h3{font-size:1em;font-weight:400;font-style:italic}p{font-size:1em;line-height:1.6}p+a{margin-top:1rem;display:inline-block}a,a:active,a:visited{text-decoration:none}.cta{margin:4.5em auto 1.5em;padding-bottom:4.5em}.button,.button:visited{padding:.9375em 1.875em;border-radius:.1em;font-weight:600;font-size:1.2em;background:#2e99f4;color:#fff;display:inline-block;border:none;transition:all 200ms ease-in-out}.button:hover,.button:visited:hover{border-bottom:none;background:#0c80e3}section{background:url(../img/nonodesbg.jpg) center center/cover;height:100%;width:100%;display:table;padding:3em 1.75em}section a,section a:focus,section a:visited{color:#46a5f5;font-size:1.1625em;border-bottom:1px solid transparent;transition:border-bottom 100ms ease-in-out}section a:focus:hover,section a:hover,section a:visited:hover{border-bottom:1px solid}section:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0;background:rgba(0,0,0,.17);background:linear-gradient(45deg,rgba(85,98,112,.1) 10%,rgba(255,107,107,.1) 95%);z-index:0}section article{display:table-cell;vertical-align:middle;margin:0 auto;text-align:center;position:relative;z-index:100}section article>div{max-width:60em;margin:0 auto}section .logo{background:url(../img/logo.png) no-repeat;width:91px;height:91px;margin:0 auto}section .row{overflow:hidden}section .row .col{text-align:left;width:100%}section .row .col:nth-child(2){margin-top:3em}@media screen and (min-width:48em){body,html{font-size:1em}h1{font-size:2.5em;margin:70px auto 0;letter-spacing:.5px}h2{font-size:1.4375em;margin:0 auto 1em}h3{font-size:1.2em}a{font-size:1.1rem;font-weight:600}p+a{margin-top:3rem}.cta{margin:7.5em auto 2.5em;border-bottom:1px solid rgba(255,255,255,.5);padding-bottom:7.5em}section{padding:0 15px}section .row .col{float:left;width:50%;padding-right:5%;display:inline-block}section .row .col:nth-child(2){padding-right:0;padding-left:5%;margin-top:0}.button{font-size:1.1625em}} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg b/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg index 40e6b0b6e4..d460bf4cd3 100644 Binary files a/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg and b/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/logo.png b/src/Umbraco.Web.UI.Client/src/assets/img/logo.png new file mode 100644 index 0000000000..ec59683c50 Binary files /dev/null and b/src/Umbraco.Web.UI.Client/src/assets/img/logo.png differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/mocks/big-image.jpg b/src/Umbraco.Web.UI.Client/src/assets/img/mocks/big-image.jpg deleted file mode 100644 index fd62385a83..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/img/mocks/big-image.jpg and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/mocks/big-thumb.jpg b/src/Umbraco.Web.UI.Client/src/assets/img/mocks/big-thumb.jpg deleted file mode 100644 index ab33fb4a1c..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/img/mocks/big-thumb.jpg and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/nonodesbg.jpg b/src/Umbraco.Web.UI.Client/src/assets/img/nonodesbg.jpg new file mode 100644 index 0000000000..61e1803924 Binary files /dev/null and b/src/Umbraco.Web.UI.Client/src/assets/img/nonodesbg.jpg differ diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js b/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js index fa8dbdcaff..64e8d46a30 100644 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js +++ b/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js @@ -2,7 +2,7 @@ LazyLoad.js([ '/Umbraco/lib/jquery/jquery-2.0.3.min.js', '/Umbraco/lib/jquery/jquery-ui-1.10.4.custom.min.js', '/Umbraco/lib/angular/1.1.5/angular.min.js', - '/Umbraco/lib/underscore/underscore.js', + '/Umbraco/lib/underscore/underscore-min.js', '/Umbraco/js/app.js', '/Umbraco/js/umbraco.resources.js', '/Umbraco/js/umbraco.services.js', diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/index.html b/src/Umbraco.Web.UI.Client/src/canvasdesigner/index.html index 0da6dcb021..e6c7bb9a77 100644 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/index.html +++ b/src/Umbraco.Web.UI.Client/src/canvasdesigner/index.html @@ -151,7 +151,7 @@

Styles saved and published

- + diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/editors/umbAutoFocus.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/editors/umbAutoFocus.directive.js new file mode 100644 index 0000000000..bf415ff25d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/editors/umbAutoFocus.directive.js @@ -0,0 +1,16 @@ +angular.module("umbraco.directives") + .directive('umbAutoFocus', function($timeout) { + + return function(scope, element, attr){ + var update = function() { + //if it uses its default naming + if(element.val() === ""){ + element.focus(); + } + }; + + $timeout(function() { + update(); + }); + }; +}); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/editors/umbAutoResize.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/editors/umbAutoResize.directive.js new file mode 100644 index 0000000000..7e7d5a7c5a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/editors/umbAutoResize.directive.js @@ -0,0 +1,32 @@ +angular.module("umbraco.directives") + .directive('umbAutoResize', function($timeout) { + + return function(scope, element, attr){ + var domEl = element[0]; + var update = function(force) { + + if(force === true){ + element.height(0); + } + + if(domEl.scrollHeight !== domEl.clientHeight){ + element.height(domEl.scrollHeight); + } + }; + + element.bind('keyup keydown keypress change', update); + element.bind('blur', function(){ update(true); }); + + $timeout(function() { + update(true); + }, 200); + + + //I hate bootstrap tabs + $('a[data-toggle="tab"]').on('shown', update); + + scope.$on('$destroy', function() { + $('a[data-toggle="tab"]').unbind("shown", update); + }); + }; +}); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbcontrolgroup.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbcontrolgroup.directive.js index 3eec10f332..f371dba9b5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbcontrolgroup.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbcontrolgroup.directive.js @@ -33,6 +33,14 @@ angular.module("umbraco.directives.html") else { scope.labelstring = scope.label; } + + if (scope.description && scope.description[0] === "@") { + scope.descriptionstring = localizationService.localize(scope.description.substring(1)); + } + else { + scope.descriptionstring = scope.description; + } + } }; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagegravity.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagegravity.directive.js index 2e05e662ad..3de777cee9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagegravity.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagegravity.directive.js @@ -49,6 +49,8 @@ angular.module("umbraco.directives") if(scope.center){ scope.dimensions.left = scope.center.left * scope.dimensions.width -10; scope.dimensions.top = scope.center.top * scope.dimensions.height -10; + }else{ + scope.center = { left: 0.5, top: 0.5 }; } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagethumbnail.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagethumbnail.directive.js index 650597e32f..803ab278d0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagethumbnail.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagethumbnail.directive.js @@ -44,7 +44,7 @@ angular.module("umbraco.directives") scope.height, scope.maxSize, scope.maxSize, - true); + false); //so if we have a max size, override the thumb sizes scope.width = ratioCalculation.width; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbproperty.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/umbproperty.directive.js index cd84a3b2ca..d84bb8e24d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbproperty.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/umbproperty.directive.js @@ -13,8 +13,18 @@ angular.module("umbraco.directives") restrict: 'E', replace: true, templateUrl: 'views/directives/umb-property.html', - link: function (scope, element, attrs, ctrl) { + //Define a controller for this directive to expose APIs to other directives + controller: function ($scope, $timeout) { + + var self = this; + + //set the API properties/methods + + self.property = $scope.property; + self.setPropertyError = function(errorMsg) { + $scope.property.propertyErrorMessage = errorMsg; + }; } }; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js index 75268f0d5e..aabf19268c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js @@ -29,10 +29,10 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat //var showheader = (attrs.showheader !== 'false'); var hideoptions = (attrs.hideoptions === 'true') ? "hide-options" : ""; var template = '
  • '; - template += '
    ' + + template += '
    ' + '
    ' + '' + - '{{tree.name}}
    ' + + '{{tree.name}}' + '' + '
    '; template += '
      ' + diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbtreesearchbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/umbtreesearchbox.directive.js index 77362b6313..c9dd185495 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbtreesearchbox.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/umbtreesearchbox.directive.js @@ -5,7 +5,7 @@ * @element ANY * @restrict E **/ -function treeSearchBox(localizationService, searchService) { +function treeSearchBox(localizationService, searchService, $q) { return { scope: { searchFromId: "@", @@ -34,19 +34,37 @@ function treeSearchBox(localizationService, searchService) { scope.showSearch = "false"; } + //used to cancel any request in progress if another one needs to take it's place + var canceler = null; + function performSearch() { if (scope.term) { scope.results = []; + + //a canceler exists, so perform the cancelation operation and reset + if (canceler) { + console.log("CANCELED!"); + canceler.resolve(); + canceler = $q.defer(); + } + else { + canceler = $q.defer(); + } var searchArgs = { - term: scope.term + term: scope.term, + canceler: canceler }; + //append a start node context if there is one if (scope.searchFromId) { searchArgs["searchFrom"] = scope.searchFromId; } + searcher(searchArgs).then(function (data) { scope.searchCallback(data); + //set back to null so it can be re-created + canceler = null; }); } } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/prevententersubmit.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/util/prevententersubmit.directive.js new file mode 100644 index 0000000000..42133aa0e1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/util/prevententersubmit.directive.js @@ -0,0 +1,25 @@ +/** +* @ngdoc directive +* @name umbraco.directives.directive:preventEnterSubmit +* @description prevents a form from submitting when the enter key is pressed on an input field +**/ +angular.module("umbraco.directives") + .directive('preventEnterSubmit', function() { + return function(scope, element, attrs) { + + var enabled = true; + //check if there's a value for the attribute, if there is and it's false then we conditionally don't + //prevent default. + if (attrs.preventEnterSubmit) { + attrs.$observe("preventEnterSubmit", function (newVal) { + enabled = (newVal === "false" || newVal === 0 || newVal === false) ? false : true; + }); + } + + $(element).keypress(function (event) { + if (event.which === 13) { + event.preventDefault(); + } + }); + }; + }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/nodirtycheck.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/nodirtycheck.directive.js index 74c007dfbc..f027d7a12f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/nodirtycheck.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/nodirtycheck.directive.js @@ -15,4 +15,4 @@ function noDirtyCheck() { } }; } -angular.module('umbraco.directives').directive("noDirtyCheck", noDirtyCheck); \ No newline at end of file +angular.module('umbraco.directives.validation').directive("noDirtyCheck", noDirtyCheck); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valHighlight.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valHighlight.directive.js index b276c8c931..fdcf768947 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valHighlight.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valHighlight.directive.js @@ -27,4 +27,4 @@ function valHighlight($timeout) { } }; } -angular.module('umbraco.directives').directive("valHighlight", valHighlight); \ No newline at end of file +angular.module('umbraco.directives.validation').directive("valHighlight", valHighlight); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valemail.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valemail.directive.js new file mode 100644 index 0000000000..88ffd6f0fa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valemail.directive.js @@ -0,0 +1,44 @@ +/** + * @ngdoc directive + * @name umbraco.directives.directive:valEmail + * @restrict A + * @description A custom directive to validate an email address string, this is required because angular's default validator is incorrect. + **/ +function valEmail(valEmailExpression) { + + return { + require: 'ngModel', + restrict: "A", + link: function (scope, elm, attrs, ctrl) { + + var patternValidator = function (viewValue) { + //NOTE: we don't validate on empty values, use required validator for that + if (!viewValue || valEmailExpression.EMAIL_REGEXP.test(viewValue)) { + // it is valid + ctrl.$setValidity('valEmail', true); + //assign a message to the validator + ctrl.errorMsg = ""; + return viewValue; + } + else { + // it is invalid, return undefined (no model update) + ctrl.$setValidity('valEmail', false); + //assign a message to the validator + ctrl.errorMsg = "Invalid email"; + return undefined; + } + }; + + ctrl.$formatters.push(patternValidator); + ctrl.$parsers.push(patternValidator); + } + }; +} + +angular.module('umbraco.directives.validation') + .directive("valEmail", valEmail) + .factory('valEmailExpression', function() { + return { + EMAIL_REGEXP: /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i + }; + }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js index d20feb0379..1bd4d409e7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js @@ -105,4 +105,4 @@ function valFormManager(serverValidationManager, $rootScope, $log, $timeout, not } }; } -angular.module('umbraco.directives').directive("valFormManager", valFormManager); \ No newline at end of file +angular.module('umbraco.directives.validation').directive("valFormManager", valFormManager); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js index ea4b1b82c8..a8d546bc36 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js @@ -9,22 +9,86 @@ * and when an error is detected for this property we'll show the error message. * In order for this directive to work, the valStatusChanged directive must be placed on the containing form. **/ -function valPropertyMsg(serverValidationManager) { +function valPropertyMsg(serverValidationManager) { + return { scope: { - property: "=property" + property: "=" }, require: "^form", //require that this directive is contained within an ngForm replace: true, //replace the element with the template restrict: "E", //restrict to element - template: "
      {{errorMsg}}
      ", - + template: "
      {{errorMsg}}
      ", + /** Our directive requries a reference to a form controller which gets passed in to this parameter */ link: function (scope, element, attrs, formCtrl) { + var watcher = null; + + // Gets the error message to display + function getErrorMsg() { + //this can be null if no property was assigned + if (scope.property) { + //first try to get the error msg from the server collection + var err = serverValidationManager.getPropertyError(scope.property.alias, ""); + //if there's an error message use it + if (err && err.errorMsg) { + return err.errorMsg; + } + else { + return scope.property.propertyErrorMessage ? scope.property.propertyErrorMessage : "Property has errors"; + } + + } + return "Property has errors"; + } + + // We need to subscribe to any changes to our model (based on user input) + // This is required because when we have a server error we actually invalidate + // the form which means it cannot be resubmitted. + // So once a field is changed that has a server error assigned to it + // we need to re-validate it for the server side validator so the user can resubmit + // the form. Of course normal client-side validators will continue to execute. + function startWatch() { + //if there's not already a watch + if (!watcher) { + watcher = scope.$watch("property.value", function (newValue, oldValue) { + + if (!newValue || angular.equals(newValue, oldValue)) { + return; + } + + var errCount = 0; + for (var e in formCtrl.$error) { + if (angular.isArray(formCtrl.$error[e])) { + errCount++; + } + } + + //we are explicitly checking for valServer errors here, since we shouldn't auto clear + // based on other errors. We'll also check if there's no other validation errors apart from valPropertyMsg, if valPropertyMsg + // is the only one, then we'll clear. + + if ((errCount === 1 && angular.isArray(formCtrl.$error.valPropertyMsg)) || (formCtrl.$invalid && angular.isArray(formCtrl.$error.valServer))) { + scope.errorMsg = ""; + formCtrl.$setValidity('valPropertyMsg', true); + stopWatch(); + } + }, true); + } + } + + //clear the watch when the property validator is valid again + function stopWatch() { + if (watcher) { + watcher(); + watcher = null; + } + } + //if there's any remaining errors in the server validation service then we should show them. var showValidation = serverValidationManager.items.length > 0; var hasError = false; @@ -33,7 +97,7 @@ function valPropertyMsg(serverValidationManager) { scope.errorMsg = ""; //listen for form error changes - scope.$on("valStatusChanged", function(evt, args) { + scope.$on("valStatusChanged", function (evt, args) { if (args.form.$invalid) { //first we need to check if the valPropertyMsg validity is invalid @@ -47,12 +111,7 @@ function valPropertyMsg(serverValidationManager) { hasError = true; //update the validation message if we don't already have one assigned. if (showValidation && scope.errorMsg === "") { - var err; - //this can be null if no property was assigned - if (scope.property) { - err = serverValidationManager.getPropertyError(scope.property.alias, ""); - } - scope.errorMsg = err ? err.errorMsg : "Property has errors"; + scope.errorMsg = getErrorMsg(); } } else { @@ -70,15 +129,11 @@ function valPropertyMsg(serverValidationManager) { scope.$on("formSubmitting", function (ev, args) { showValidation = true; if (hasError && scope.errorMsg === "") { - var err; - //this can be null if no property was assigned - if (scope.property) { - err = serverValidationManager.getPropertyError(scope.property.alias, ""); - } - scope.errorMsg = err ? err.errorMsg : "Property has errors"; + scope.errorMsg = getErrorMsg(); } else if (!hasError) { scope.errorMsg = ""; + stopWatch(); } }); @@ -86,37 +141,10 @@ function valPropertyMsg(serverValidationManager) { scope.$on("formSubmitted", function (ev, args) { showValidation = false; scope.errorMsg = ""; - formCtrl.$setValidity('valPropertyMsg', true); + formCtrl.$setValidity('valPropertyMsg', true); + stopWatch(); }); - //We need to subscribe to any changes to our model (based on user input) - // This is required because when we have a server error we actually invalidate - // the form which means it cannot be resubmitted. - // So once a field is changed that has a server error assigned to it - // we need to re-validate it for the server side validator so the user can resubmit - // the form. Of course normal client-side validators will continue to execute. - scope.$watch("property.value", function(newValue) { - //we are explicitly checking for valServer errors here, since we shouldn't auto clear - // based on other errors. We'll also check if there's no other validation errors apart from valPropertyMsg, if valPropertyMsg - // is the only one, then we'll clear. - - if (!newValue) { - return; - } - - var errCount = 0; - for (var e in formCtrl.$error) { - if (angular.isArray(formCtrl.$error[e])) { - errCount++; - } - } - - if ((errCount === 1 && angular.isArray(formCtrl.$error.valPropertyMsg)) || (formCtrl.$invalid && angular.isArray(formCtrl.$error.valServer))) { - scope.errorMsg = ""; - formCtrl.$setValidity('valPropertyMsg', true); - } - }, true); - //listen for server validation changes // NOTE: we pass in "" in order to listen for all validation changes to the content property, not for // validation changes to fields in the property this is because some server side validators may not @@ -124,31 +152,34 @@ function valPropertyMsg(serverValidationManager) { // It's important to note that we need to subscribe to server validation changes here because we always must // indicate that a content property is invalid at the property level since developers may not actually implement // the correct field validation in their property editors. - + if (scope.property) { //this can be null if no property was assigned - serverValidationManager.subscribe(scope.property.alias, "", function(isValid, propertyErrors, allErrors) { + serverValidationManager.subscribe(scope.property.alias, "", function (isValid, propertyErrors, allErrors) { hasError = !isValid; if (hasError) { //set the error message to the server message scope.errorMsg = propertyErrors[0].errorMsg; //flag that the current validator is invalid formCtrl.$setValidity('valPropertyMsg', false); + startWatch(); } else { scope.errorMsg = ""; //flag that the current validator is valid formCtrl.$setValidity('valPropertyMsg', true); + stopWatch(); } }); //when the element is disposed we need to unsubscribe! // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain // but they are a different callback instance than the above. - element.bind('$destroy', function() { + element.bind('$destroy', function () { + stopWatch(); serverValidationManager.unsubscribe(scope.property.alias, ""); }); } } }; } -angular.module('umbraco.directives').directive("valPropertyMsg", valPropertyMsg); \ No newline at end of file +angular.module('umbraco.directives.validation').directive("valPropertyMsg", valPropertyMsg); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertyvalidator.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertyvalidator.directive.js new file mode 100644 index 0000000000..77652d7f69 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertyvalidator.directive.js @@ -0,0 +1,71 @@ +/** +* @ngdoc directive +* @name umbraco.directives.directive:valPropertyValidator +* @restrict A +* @description Performs any custom property value validation checks on the client side. This allows property editors to be highly flexible when it comes to validation + on the client side. Typically if a property editor stores a primitive value (i.e. string) then the client side validation can easily be taken care of + with standard angular directives such as ng-required. However since some property editors store complex data such as JSON, a given property editor + might require custom validation. This directive can be used to validate an Umbraco property in any way that a developer would like by specifying a + callback method to perform the validation. The result of this method must return an object in the format of + {isValid: true, errorKey: 'required', errorMsg: 'Something went wrong' } + The error message returned will also be displayed for the property level validation message. + This directive should only be used when dealing with complex models, if custom validation needs to be performed with primitive values, use the simpler + angular validation directives instead since this will watch the entire model. +**/ + +function valPropertyValidator(serverValidationManager) { + return { + scope: { + valPropertyValidator: "=" + }, + + // The element must have ng-model attribute and be inside an umbProperty directive + require: ['ngModel', '?^umbProperty'], + + restrict: "A", + + link: function (scope, element, attrs, ctrls) { + + var modelCtrl = ctrls[0]; + var propCtrl = ctrls.length > 1 ? ctrls[1] : null; + + // Check whether the scope has a valPropertyValidator method + if (!scope.valPropertyValidator || !angular.isFunction(scope.valPropertyValidator)) { + throw new Error('val-property-validator directive must specify a function to call'); + } + + var initResult = scope.valPropertyValidator(); + + // Validation method + var validate = function (viewValue) { + // Calls the validition method + var result = scope.valPropertyValidator(); + if (!result.errorKey || result.isValid === undefined || !result.errorMsg) { + throw "The result object from valPropertyValidator does not contain required properties: isValid, errorKey, errorMsg"; + } + if (result.isValid === true) { + // Tell the controller that the value is valid + modelCtrl.$setValidity(result.errorKey, true); + if (propCtrl) { + propCtrl.setPropertyError(null); + } + } + else { + // Tell the controller that the value is invalid + modelCtrl.$setValidity(result.errorKey, false); + if (propCtrl) { + propCtrl.setPropertyError(result.errorMsg); + } + } + }; + + // Formatters are invoked when the model is modified in the code. + modelCtrl.$formatters.push(validate); + + // Parsers are called as soon as the value in the form input is modified + modelCtrl.$parsers.push(validate); + + } + }; +} +angular.module('umbraco.directives.validation').directive("valPropertyValidator", valPropertyValidator); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valregex.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valregex.directive.js index e98fb06c98..651c0a54c7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valregex.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valregex.directive.js @@ -61,4 +61,4 @@ function valRegex() { } }; } -angular.module('umbraco.directives').directive("valRegex", valRegex); \ No newline at end of file +angular.module('umbraco.directives.validation').directive("valRegex", valRegex); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js index fbc91dcb11..6225485073 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js @@ -7,14 +7,53 @@ **/ function valServer(serverValidationManager) { return { - require: 'ngModel', + require: ['ngModel', '?^umbProperty'], restrict: "A", - link: function (scope, element, attr, ctrl) { - - if (!scope.model || !scope.model.alias){ - throw "valServer can only be used in the scope of a content property object"; + link: function (scope, element, attr, ctrls) { + + var modelCtrl = ctrls[0]; + var umbPropCtrl = ctrls.length > 1 ? ctrls[1] : null; + if (!umbPropCtrl) { + //we cannot proceed, this validator will be disabled + return; } - var currentProperty = scope.model; + + var watcher = null; + + //Need to watch the value model for it to change, previously we had subscribed to + //modelCtrl.$viewChangeListeners but this is not good enough if you have an editor that + // doesn't specifically have a 2 way ng binding. This is required because when we + // have a server error we actually invalidate the form which means it cannot be + // resubmitted. So once a field is changed that has a server error assigned to it + // we need to re-validate it for the server side validator so the user can resubmit + // the form. Of course normal client-side validators will continue to execute. + function startWatch() { + //if there's not already a watch + if (!watcher) { + watcher = scope.$watch(function () { + return modelCtrl.$modelValue; + }, function (newValue, oldValue) { + + if (!newValue || angular.equals(newValue, oldValue)) { + return; + } + + if (modelCtrl.$invalid) { + modelCtrl.$setValidity('valServer', true); + stopWatch(); + } + }, true); + } + } + + function stopWatch() { + if (watcher) { + watcher(); + watcher = null; + } + } + + var currentProperty = umbPropCtrl.property; //default to 'value' if nothing is set var fieldName = "value"; @@ -25,33 +64,20 @@ function valServer(serverValidationManager) { fieldName = attr.valServer; } } - - //Need to watch the value model for it to change, previously we had subscribed to - //ctrl.$viewChangeListeners but this is not good enough if you have an editor that - // doesn't specifically have a 2 way ng binding. This is required because when we - // have a server error we actually invalidate the form which means it cannot be - // resubmitted. So once a field is changed that has a server error assigned to it - // we need to re-validate it for the server side validator so the user can resubmit - // the form. Of course normal client-side validators will continue to execute. - scope.$watch(function() { - return ctrl.$modelValue; - }, function (newValue) { - if (ctrl.$invalid) { - ctrl.$setValidity('valServer', true); - } - }); //subscribe to the server validation changes serverValidationManager.subscribe(currentProperty.alias, fieldName, function (isValid, propertyErrors, allErrors) { if (!isValid) { - ctrl.$setValidity('valServer', false); + modelCtrl.$setValidity('valServer', false); //assign an error msg property to the current validator - ctrl.errorMsg = propertyErrors[0].errorMsg; + modelCtrl.errorMsg = propertyErrors[0].errorMsg; + startWatch(); } else { - ctrl.$setValidity('valServer', true); + modelCtrl.$setValidity('valServer', true); //reset the error message - ctrl.errorMsg = ""; + modelCtrl.errorMsg = ""; + stopWatch(); } }); @@ -59,9 +85,10 @@ function valServer(serverValidationManager) { // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain // but they are a different callback instance than the above. element.bind('$destroy', function () { + stopWatch(); serverValidationManager.unsubscribe(currentProperty.alias, fieldName); }); } }; } -angular.module('umbraco.directives').directive("valServer", valServer); \ No newline at end of file +angular.module('umbraco.directives.validation').directive("valServer", valServer); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserverfield.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserverfield.directive.js index da79253c2f..9a077615df 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserverfield.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserverfield.directive.js @@ -51,4 +51,4 @@ function valServerField(serverValidationManager) { } }; } -angular.module('umbraco.directives').directive("valServerField", valServerField); \ No newline at end of file +angular.module('umbraco.directives.validation').directive("valServerField", valServerField); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtab.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtab.directive.js index 0216fe14d7..cd6dc51eca 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtab.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtab.directive.js @@ -35,4 +35,4 @@ function valTab() { } }; } -angular.module('umbraco.directives').directive("valTab", valTab); \ No newline at end of file +angular.module('umbraco.directives.validation').directive("valTab", valTab); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtogglemsg.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtogglemsg.directive.js index 5a57754524..cdcfbcfe2a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtogglemsg.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtogglemsg.directive.js @@ -84,4 +84,4 @@ function valToggleMsg(serverValidationManager) { * @requires formController * @description This directive will show/hide an error based on: is the value + the given validator invalid? AND, has the form been submitted ? **/ -angular.module('umbraco.directives').directive("valToggleMsg", valToggleMsg); \ No newline at end of file +angular.module('umbraco.directives.validation').directive("valToggleMsg", valToggleMsg); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js index de010d9fa2..cff7628788 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js @@ -753,7 +753,7 @@ angular.module('umbraco.mocks'). return { register: function() { $httpBackend - .whenGET(mocksUtils.urlRegex('js/language.aspx')) + .whenGET(mocksUtils.urlRegex('LocalizedText')) .respond(getLanguageResource); } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 8e5ed0a9b6..c136f88bc7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -29,6 +29,7 @@ * - User * - Language * - Domain + * - DataType **/ function entityResource($q, $http, umbRequestHelper) { @@ -263,20 +264,26 @@ function entityResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the entity array. * */ - search: function (query, type, searchFrom) { + search: function (query, type, searchFrom, canceler) { var args = [{ query: query }, { type: type }]; if (searchFrom) { args.push({ searchFrom: searchFrom }); } + var httpConfig = {}; + if (canceler) { + httpConfig["timeout"] = canceler; + } + return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "Search", - args)), - 'Failed to retrieve entity data for query ' + query); + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "Search", + args), + httpConfig), + 'Failed to retrieve entity data for query ' + query); }, @@ -301,15 +308,21 @@ function entityResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the entity array. * */ - searchAll: function (query) { + searchAll: function (query, canceler) { + + var httpConfig = {}; + if (canceler) { + httpConfig["timeout"] = canceler; + } return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "SearchAll", - [{ query: query }])), - 'Failed to retrieve entity data for query ' + query); + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "SearchAll", + [{ query: query }]), + httpConfig), + 'Failed to retrieve entity data for query ' + query); } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/template.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/template.resource.js deleted file mode 100644 index d6fec8640f..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/resources/template.resource.js +++ /dev/null @@ -1,82 +0,0 @@ -/** - * @ngdoc service - * @name umbraco.resources.stylesheetResource - * @description service to retrieve available stylesheets - * - * - **/ -function templateResource($q, $http, umbRequestHelper) { - - //the factory object returned - return { - - /** - * @ngdoc method - * @name umbraco.resources.stylesheetResource#getAll - * @methodOf umbraco.resources.stylesheetResource - * - * @description - * Gets all registered stylesheets - * - * ##usage - *
      -         * stylesheetResource.getAll()
      -         *    .then(function(stylesheets) {
      -         *        alert('its here!');
      -         *    });
      -         * 
      - * - * @returns {Promise} resourcePromise object containing the stylesheets. - * - */ - getAll: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "templateApiBaseUrl", - "GetAll")), - 'Failed to retrieve stylesheets '); - }, - - /** - * @ngdoc method - * @name umbraco.resources.stylesheetResource#getRulesByName - * @methodOf umbraco.resources.stylesheetResource - * - * @description - * Returns all defined child rules for a stylesheet with a given name - * - * ##usage - *
      -         * stylesheetResource.getRulesByName("ie7stylesheet")
      -         *    .then(function(rules) {
      -         *        alert('its here!');
      -         *    });
      -         * 
      - * - * @returns {Promise} resourcePromise object containing the rules. - * - */ - getById: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "templateApiBaseUrl", - "GetById", - [{ id: id }])), - 'Failed to retreive template '); - }, - - saveAndRender: function(html, templateId, pageId){ - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "templateApiBaseUrl", - "PostSaveAndRender"), - {templateId: templateId, pageId: pageId, html: html }), - 'Failed to retreive template '); - } - }; -} - -angular.module('umbraco.resources').factory('templateResource', templateResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js b/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js index 94a84f0749..057e9c3b27 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js @@ -230,11 +230,11 @@ angular.module('umbraco.services') asset.state = "loaded"; asset.deferred.resolve(true); }else{ - asset.state = "loaded"; + asset.state = "loaded"; angularHelper.safeApply(scope, function () { asset.deferred.resolve(true); - }); - } + }); + } }); }); }else{ diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index cd329995b7..0c39bdfa38 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -31,7 +31,10 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica var deferred = $q.defer(); - if (formHelper.submitForm({ scope: args.scope, statusMessage: args.statusMessage })) { + if (!args.scope.busy && formHelper.submitForm({ scope: args.scope, statusMessage: args.statusMessage })) { + + args.scope.busy = true; + args.saveMethod(args.content, $routeParams.create, fileManager.getFiles()) .then(function (data) { @@ -42,6 +45,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica savedContent: data, rebindCallback: self.reBindChangedProperties(args.content, data) }); + + args.scope.busy = false; deferred.resolve(data); }, function (err) { @@ -50,6 +55,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica err: err, rebindCallback: self.reBindChangedProperties(args.content, err.data) }); + args.scope.busy = false; deferred.reject(err); }); } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js b/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js index 0cdb600f1e..027a055bba 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js @@ -1,118 +1,122 @@ angular.module('umbraco.services') .factory('localizationService', function ($http, $q, eventsService, $window, $filter, userService) { - var service = { - // array to hold the localized resource string entries - dictionary:[], - // location of the resource file - url: "js/language.aspx", - // flag to indicate if the service hs loaded the resource file - resourceFileLoaded:false, - // success handler for all server communication - successCallback:function (data) { - // store the returned array in the dictionary - service.dictionary = data; - // set the flag that the resource are loaded - service.resourceFileLoaded = true; - // broadcast that the file has been loaded - eventsService.emit("localizationService.updated", data); - }, + var url = "LocalizedText"; + var resourceFileLoadStatus = "none"; + var resourceLoadingPromise = []; - // allows setting of language on the fly - setLanguage: function(value) { - service.initLocalizedResources(); - }, + function _lookup(value, tokens, dictionary) { - // allows setting of resource url on the fly - setUrl: function(value) { - service.url = value; - service.initLocalizedResources(); - }, + //strip the key identifier if its there + if (value && value[0] === "@") { + value = value.substring(1); + } - // loads the language resource file from the server - initLocalizedResources:function () { - var deferred = $q.defer(); - // build the url to retrieve the localized resource file - $http({ method:"GET", url:service.url, cache:false }) - .then(function(response){ - service.resourceFileLoaded = true; - service.dictionary = response.data; + //if no area specified, add general_ + if (value && value.indexOf("_") < 0) { + value = "general_" + value; + } - eventsService.emit("localizationService.updated", service.dictionary); - - return deferred.resolve(service.dictionary); - }, function(err){ - return deferred.reject("Something broke"); - }); - return deferred.promise; - }, - - //helper to tokenize and compile a localization string - tokenize: function(value,scope) { - if (value) { - var localizer = value.split(':'); - var retval = { tokens: undefined, key: localizer[0].substring(0) }; - if (localizer.length > 1) { - retval.tokens = localizer[1].split(','); - for (var x = 0; x < retval.tokens.length; x++) { - retval.tokens[x] = scope.$eval(retval.tokens[x]); - } - } - - return retval; + var entry = dictionary[value]; + if (entry) { + if (tokens) { + for (var i = 0; i < tokens.length; i++) { + entry = entry.replace("%" + i + "%", tokens[i]); } - return value; - }, - - // checks the dictionary for a localized resource string - localize: function(value,tokens) { - var deferred = $q.defer(); - - if(service.resourceFileLoaded){ - var val = service._lookup(value,tokens); - deferred.resolve(val); - }else{ - service.initLocalizedResources().then(function(dic){ - var val = service._lookup(value,tokens); - deferred.resolve(val); - }); - } - - return deferred.promise; - }, - _lookup: function(value,tokens){ - - //strip the key identifier if its there - if(value && value[0] === "@"){ - value = value.substring(1); - } - - //if no area specified, add general_ - if(value && value.indexOf("_") < 0){ - value = "general_" + value; - } - - var entry = service.dictionary[value]; - if(entry){ - if(tokens){ - for (var i = 0; i < tokens.length; i++) { - entry = entry.replace("%"+i+"%", tokens[i]); - } - } - return entry; - } - return "[" + value + "]"; } - }; + return entry; + } + return "[" + value + "]"; + } - // force the load of the resource file - service.initLocalizedResources(); + var service = { + // array to hold the localized resource string entries + dictionary: [], - //This happens after login / auth and assets loading - eventsService.on("app.authenticated", function(){ - service.resourceFileLoaded = false; - }); + // loads the language resource file from the server + initLocalizedResources: function () { + var deferred = $q.defer(); - // return the local instance when called - return service; - }); \ No newline at end of file + //if the resource is already loading, we don't want to force it to load another one in tandem, we'd rather + // wait for that initial http promise to finish and then return this one with the dictionary loaded + if (resourceFileLoadStatus === "loading") { + //add to the list of promises waiting + resourceLoadingPromise.push(deferred); + + //exit now it's already loading + return deferred.promise; + } + + resourceFileLoadStatus = "loading"; + + // build the url to retrieve the localized resource file + $http({ method: "GET", url: url, cache: false }) + .then(function (response) { + resourceFileLoadStatus = "loaded"; + service.dictionary = response.data; + + eventsService.emit("localizationService.updated", response.data); + + deferred.resolve(response.data); + //ensure all other queued promises are resolved + for (var p in resourceLoadingPromise) { + resourceLoadingPromise[p].resolve(response.data); + } + }, function (err) { + deferred.reject("Something broke"); + //ensure all other queued promises are resolved + for (var p in resourceLoadingPromise) { + resourceLoadingPromise[p].reject("Something broke"); + } + }); + return deferred.promise; + }, + + //helper to tokenize and compile a localization string + tokenize: function (value, scope) { + if (value) { + var localizer = value.split(':'); + var retval = { tokens: undefined, key: localizer[0].substring(0) }; + if (localizer.length > 1) { + retval.tokens = localizer[1].split(','); + for (var x = 0; x < retval.tokens.length; x++) { + retval.tokens[x] = scope.$eval(retval.tokens[x]); + } + } + + return retval; + } + return value; + }, + + // checks the dictionary for a localized resource string + localize: function (value, tokens) { + var deferred = $q.defer(); + + if (resourceFileLoadStatus === "loaded") { + var val = _lookup(value, tokens, service.dictionary); + deferred.resolve(val); + } else { + service.initLocalizedResources().then(function (dic) { + var val = _lookup(value, tokens, dic); + deferred.resolve(val); + }); + } + + return deferred.promise; + }, + + }; + + // force the load of the resource file + service.initLocalizedResources(); + + //This happens after login / auth and assets loading + eventsService.on("app.authenticated", function () { + resourceFileLoadStatus = "none"; + resourceLoadingPromise = []; + }); + + // return the local instance when called + return service; +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js index ab4bac2235..f945070a2a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js @@ -88,7 +88,7 @@ angular.module('umbraco.services') throw "args.term is required"; } - return entityResource.search(args.term, "Document", args.searchFrom).then(function (data) { + return entityResource.search(args.term, "Document", args.searchFrom, args.canceler).then(function (data) { _.each(data, function (item) { configureContentResult(item); }); @@ -138,7 +138,7 @@ angular.module('umbraco.services') throw "args.term is required"; } - return entityResource.searchAll(args.term).then(function (data) { + return entityResource.searchAll(args.term, args.canceler).then(function (data) { _.each(data, function(resultByType) { switch(resultByType.type) { diff --git a/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js index db3cc3e0bc..386e9a6712 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js @@ -7,7 +7,7 @@ * Controls the search functionality in the site * */ -function SearchController($scope, searchService, $log, $location, navigationService) { +function SearchController($scope, searchService, $log, $location, navigationService, $q) { $scope.searchTerm = null; $scope.searchResults = []; @@ -87,25 +87,41 @@ function SearchController($scope, searchService, $log, $location, navigationServ $scope.selectedItem = group.results[itemIndex]; } - //watch the value change but don't do the search on every change - that's far too many queries - // we need to debounce - var debounced = _.debounce(function () { - if ($scope.searchTerm) { - $scope.isSearching = true; - navigationService.showSearch(); - $scope.selectedItem = undefined; - searchService.searchAll({ term: $scope.searchTerm }).then(function (result) { - $scope.groups = _.filter(result, function(group){return group.results.length > 0;}); - }); - }else{ - $scope.isSearching = false; - navigationService.hideSearch(); - $scope.selectedItem = undefined; - } - }, 300); + //used to cancel any request in progress if another one needs to take it's place + var canceler = null; - - $scope.$watch("searchTerm", debounced); + $scope.$watch("searchTerm", _.debounce(function (newVal, oldVal) { + $scope.$apply(function() { + if ($scope.searchTerm) { + if (newVal !== null && newVal !== undefined && newVal !== oldVal) { + $scope.isSearching = true; + navigationService.showSearch(); + $scope.selectedItem = undefined; + + //a canceler exists, so perform the cancelation operation and reset + if (canceler) { + console.log("CANCELED!"); + canceler.resolve(); + canceler = $q.defer(); + } + else { + canceler = $q.defer(); + } + + searchService.searchAll({ term: $scope.searchTerm, canceler: canceler }).then(function(result) { + $scope.groups = _.filter(result, function (group) { return group.results.length > 0; }); + //set back to null so it can be re-created + canceler = null; + }); + } + } + else { + $scope.isSearching = false; + navigationService.hideSearch(); + $scope.selectedItem = undefined; + } + }); + }, 200)); } //register it diff --git a/src/Umbraco.Web.UI.Client/src/index.html b/src/Umbraco.Web.UI.Client/src/index.html index 6a1b32b03f..a16987ce2c 100644 --- a/src/Umbraco.Web.UI.Client/src/index.html +++ b/src/Umbraco.Web.UI.Client/src/index.html @@ -1,4 +1,4 @@ - + @@ -12,7 +12,7 @@
      - +
      @@ -22,7 +22,7 @@ - - + + diff --git a/src/Umbraco.Web.UI.Client/src/install.loader.js b/src/Umbraco.Web.UI.Client/src/install.loader.js index a9699283fa..5415a106e1 100644 --- a/src/Umbraco.Web.UI.Client/src/install.loader.js +++ b/src/Umbraco.Web.UI.Client/src/install.loader.js @@ -6,11 +6,12 @@ LazyLoad.js( [ 'lib/angular/1.1.5/angular-mobile.min.js', 'lib/angular/1.1.5/angular-mocks.js', 'lib/angular/1.1.5/angular-sanitize.min.js', - 'lib/underscore/underscore.js', - 'js/umbraco.installer.js' + 'lib/underscore/underscore-min.js', + 'js/umbraco.installer.js', + 'js/umbraco.directives.js' ], function () { jQuery(document).ready(function () { - angular.bootstrap(document, ['ngSanitize', 'umbraco.install']); + angular.bootstrap(document, ['ngSanitize', 'umbraco.install', 'umbraco.directives.validation']); }); } ); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer.html b/src/Umbraco.Web.UI.Client/src/installer/installer.html index c736515c96..6a644bf3d7 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer.html +++ b/src/Umbraco.Web.UI.Client/src/installer/installer.html @@ -1,4 +1,4 @@ - + @@ -17,8 +17,8 @@
      -
      - +
      + diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js index cd0f080005..1ab84900f4 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js +++ b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js @@ -18,6 +18,7 @@ angular.module("umbraco.install").factory('installerService', function($rootScop //add to umbraco installer facts here var facts = ['Umbraco helped millions of people watch a man jump from the edge of space', 'Over 250.000 websites are currently powered by Umbraco', + "At least 2 people have named their cat 'Umbraco'", 'On an average day, more than 1.000 people download Umbraco', 'umbraco.tv is the premier source of Umbraco video tutorials to get you started', 'You can find the world\'s friendliest CMS community at our.umbraco.org', diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html b/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html index 1868798bcb..78e90445db 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html @@ -1,14 +1,17 @@ -
      +

      Install a starter website

      Installing a starter website helps you learn how Umbraco works, and gives you a solid and simple foundation to build on top of.

      + + Loading... -
        +
        • + Loading... {{pck.name}}
        • diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/user.html b/src/Umbraco.Web.UI.Client/src/installer/steps/user.html index 9e6b5c22fb..2cd50fe208 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/user.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/user.html @@ -18,7 +18,7 @@
          - + Your email will be used as your login
          @@ -32,6 +32,7 @@ ng-pattern="passwordPattern" autocorrect="off" autocapitalize="off" + autocomplete="off" required ng-model="installer.current.model.password" id="password" /> At least {{installer.current.model.minCharLength}} characters long diff --git a/src/Umbraco.Web.UI.Client/src/less/gridview.less b/src/Umbraco.Web.UI.Client/src/less/gridview.less index f9724fa974..b832f4dd18 100644 --- a/src/Umbraco.Web.UI.Client/src/less/gridview.less +++ b/src/Umbraco.Web.UI.Client/src/less/gridview.less @@ -5,7 +5,7 @@ overflow-y:hidden!important; } - IFRAME {overflow:hidden;} +IFRAME {overflow:hidden;} // Sortabel @@ -330,7 +330,9 @@ min-height:60px; } - + .usky-grid .usky-cell-embed iframe { + width: 100%; +} // ICONS // ------------------------- @@ -498,7 +500,7 @@ .usky-grid .usky-templates-columns{ margin-top: 30px; } - + .usky-grid .usky-row-inner{ margin-right: 45px; border: 1px dashed transparent; @@ -596,7 +598,7 @@ -moz-box-sizing: border-box; box-sizing: border-box; width: 125px; - margin: 35px 40px 15px 0; + margin: 35px 40px 15px 0; border: 2px solid @grayLight; /* @grayLight */ transition: border 200ms linear; @@ -616,7 +618,7 @@ border-color: @blue; cursor: pointer; } - + .preview-row { display: inline-block; width: 100%; @@ -625,7 +627,6 @@ } .preview-rows.layout { - height: 180px; padding: 2px; .preview-row { @@ -633,7 +634,7 @@ } .preview-col { - height: 100%; + height: 180px; border: dashed 1px @grayLight; } @@ -804,7 +805,7 @@ } .usky-grid-configuration .uSky-templates-column.last{ - margin-right: -1px; + margin-right: -1px; } .usky-grid-configuration .uSky-templates-column.add{ diff --git a/src/Umbraco.Web.UI.Client/src/less/installer.less b/src/Umbraco.Web.UI.Client/src/less/installer.less index 298f4f4728..574fde27a9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/installer.less +++ b/src/Umbraco.Web.UI.Client/src/less/installer.less @@ -288,4 +288,21 @@ select { width:100%; overflow:auto; border:none; +} +#starterKits .thumbnails{ + min-height:128px; +} +#starterKits a.thumbnail { + position:relative; + height:98px; +} +#starterKits a.thumbnail small { + position:absolute; + z-index: 50; + top:10px; + left:10px; +} +#starterKits a.thumbnail img { + position:relative; + z-index:100; } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/loader.js b/src/Umbraco.Web.UI.Client/src/loader.js index 735159459f..9ba166a2c2 100644 --- a/src/Umbraco.Web.UI.Client/src/loader.js +++ b/src/Umbraco.Web.UI.Client/src/loader.js @@ -22,7 +22,7 @@ LazyLoad.js( 'lib/jquery/jquery.upload/js/jquery.fileupload-angular.js', 'lib/bootstrap/js/bootstrap.2.3.2.min.js', - 'lib/underscore/underscore.js', + 'lib/underscore/underscore-min.js', 'lib/umbraco/Extensions.js', 'lib/umbraco/NamespaceManager.js', diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/content/edit.controller.js index 02655b3d11..04e926742f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/content/edit.controller.js @@ -12,16 +12,13 @@ function ContentEditDialogController($scope, editorState, $routeParams, $q, $tim scope: $scope, content: $scope.content }).then(function (content) { - //success - $scope.busy = false; - + //success if (dialogOptions.closeOnSave) { $scope.submit(content); } }, function(err) { //error - $scope.busy = false; }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js index 85f256c699..2b7c5519f4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js @@ -9,8 +9,8 @@ angular.module("umbraco") $scope.showDetails = dialogOptions.showDetails; $scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false; $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1; - - + $scope.cropSize = dialogOptions.cropSize; + $scope.options = { url: umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostAddFile"), autoUpload: true, diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.html index 2528d4d3bc..7500c0ae42 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.html @@ -5,9 +5,24 @@ data-file-upload="options" data-file-upload-progress="" data-ng-class="{'fileupl
        + +
      diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html index c94ef6b279..c5de0a69db 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html @@ -14,10 +14,10 @@ {{node.name}} - +
      - Edit -
      + Edit +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/email/email.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/email/email.html index f31b16a6da..0081d6ac26 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/email/email.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/email/email.html @@ -1,13 +1,14 @@ 
    - + Required - Invalid email + Invalid email
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js index 9106e2b334..37dea64c21 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js @@ -21,6 +21,11 @@ function fileUploadController($scope, $element, $compile, imageHelper, fileManag fileManager.setFiles($scope.model.alias, []); //clear the current files $scope.files = []; + if ($scope.propertyForm.fileCount) { + //this is required to re-validate + $scope.propertyForm.fileCount.$setViewValue($scope.files.length); + } + } /** this method is used to initialize the data and to re-initialize it if the server value is changed */ @@ -71,6 +76,15 @@ function fileUploadController($scope, $element, $compile, imageHelper, fileManag initialize(); + // Method required by the valPropertyValidator directive (returns true if the property editor has at least one file selected) + $scope.validateMandatory = function () { + return { + isValid: !$scope.model.validation.mandatory || ((($scope.persistedFiles != null && $scope.persistedFiles.length > 0) || ($scope.files != null && $scope.files.length > 0)) && !$scope.clearFiles), + errorMsg: "Value cannot be empty", + errorKey: "required" + }; + } + //listen for clear files changes to set our model to be sent up to the server $scope.$watch("clearFiles", function (isCleared) { if (isCleared == true) { @@ -80,6 +94,8 @@ function fileUploadController($scope, $element, $compile, imageHelper, fileManag else { //reset to original value $scope.model.value = $scope.originalValue; + //this is required to re-validate + $scope.propertyForm.fileCount.$setViewValue($scope.files.length); } }); @@ -96,6 +112,10 @@ function fileUploadController($scope, $element, $compile, imageHelper, fileManag $scope.files.push({ alias: $scope.model.alias, file: args.files[i] }); newVal += args.files[i].name + ","; } + + //this is required to re-validate + $scope.propertyForm.fileCount.$setViewValue($scope.files.length); + //set clear files to false, this will reset the model too $scope.clearFiles = false; //set the model value to be the concatenation of files selected. Please see the notes diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html index afcba5a493..e200b2726c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html @@ -25,4 +25,7 @@
  • {{file.file.name}}
- + + + + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/additem.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/additem.html index dec1d8a3bc..77e2c27bd5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/additem.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/additem.html @@ -1,8 +1,7 @@
- -
Insert control
+
  • diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/config.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/config.html index d54ba2a087..429bd68abd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/config.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/config.html @@ -31,6 +31,10 @@
@@ -91,7 +91,7 @@
{{row.name}}
- {{row.areas.length}} configurable cells
+ {{row.areas.length}} cells
@@ -113,4 +113,4 @@
- \ No newline at end of file + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html index 38ead7b06d..07aa2d2fbd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html @@ -3,10 +3,10 @@ ng-controller="Umbraco.PropertyEditors.GridPrevalueEditor.RowConfigController"> \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index 16e9357864..a0c0c8b517 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -1,4 +1,4 @@ -function listViewController($rootScope, $scope, $routeParams, $injector, notificationsService, iconHelper, dialogService, editorState, localizationService, $location) { +function listViewController($rootScope, $scope, $routeParams, $injector, notificationsService, iconHelper, dialogService, editorState, localizationService, $location, appState) { //this is a quick check to see if we're in create mode, if so just exit - we cannot show children for content // that isn't created yet, if we continue this will use the parent id in the route params which isn't what @@ -12,7 +12,8 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific //Now we need to check if this is for media, members or content because that will depend on the resources we use var contentResource, getContentTypesCallback, getListResultsCallback, deleteItemCallback, getIdCallback, createEditUrlCallback; - if ($scope.model.config.entityType && $scope.model.config.entityType === "member") { + //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals) + if (($scope.model.config.entityType && $scope.model.config.entityType === "member") || (appState.getSectionState("currentSection") === "member")) { $scope.entityType = "member"; contentResource = $injector.get('memberResource'); getContentTypesCallback = $injector.get('memberTypeResource').getTypes; @@ -26,7 +27,8 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific }; } else { - if ($scope.model.config.entityType && $scope.model.config.entityType === "media") { + //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals) + if (($scope.model.config.entityType && $scope.model.config.entityType === "media") || (appState.getSectionState("currentSection") === "media")) { $scope.entityType = "media"; contentResource = $injector.get('mediaResource'); getContentTypesCallback = $injector.get('mediaTypeResource').getAllowedTypes; @@ -149,8 +151,14 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific }); } + //NOTE: This might occur if we are requesting a higher page number than what is actually available, for example + // if you have more than one page and you delete all items on the last page. In this case, we need to reset to the last + // available page and then re-load again if ($scope.options.pageNumber > $scope.listViewResultSet.totalPages) { $scope.options.pageNumber = $scope.listViewResultSet.totalPages; + + //reload! + $scope.reloadView(id); } $scope.pagination = []; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html index 920f762966..88e72c3484 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html @@ -64,7 +64,7 @@
- +
@@ -72,7 +72,7 @@ - +

There are no items show in the list.

@@ -135,4 +135,4 @@ - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/macrocontainer/macrocontainer.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/macrocontainer/macrocontainer.controller.js index c3d3f91914..1c1e94601b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/macrocontainer/macrocontainer.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/macrocontainer/macrocontainer.controller.js @@ -41,7 +41,7 @@ angular.module('umbraco') if(index !== null && $scope.renderModel[index]) { var macro = $scope.renderModel[index]; - dialogData[macroData] = macro; + dialogData["macroData"] = macro; } dialogService.macroPicker({ @@ -100,4 +100,4 @@ angular.module('umbraco') return str.replace(rgxtrim, ''); } -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html index ffa8b3f00d..9eeab2c3b4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html @@ -1,7 +1,7 @@ 
- + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js index f0c9cbc1e1..417fc82ab2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js @@ -128,7 +128,7 @@ function getElementIndexByUrl(url) { for (var i = 0; i < $scope.model.value.length; i++) { - if ($scope.model.value[i].link === url) { + if ($scope.model.value[i].link == url) { return i; } } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html index 43ee95b5cd..8095d4adfc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html @@ -3,9 +3,9 @@ - Caption - Link - New window + Caption + Link + New window @@ -26,12 +26,12 @@ @@ -42,12 +42,12 @@
- - + +
- - + +
@@ -56,23 +56,23 @@ - +
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html index df5e259637..f00dfa06c1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html @@ -31,6 +31,6 @@ - + Pixels
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.controller.js index 137cd3b516..9c0d016189 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.controller.js @@ -7,84 +7,91 @@ angular.module("umbraco") $scope.isLoading = true; $scope.tagToAdd = ""; - assetsService.loadJs("lib/typeahead/typeahead.bundle.min.js").then(function () { + assetsService.loadJs("lib/typeahead-js/typeahead.bundle.min.js").then(function () { $scope.isLoading = false; //load current value - $scope.currentTags = []; + if ($scope.model.value) { - if ($scope.model.config.storageType && $scope.model.config.storageType === "Json") { - //it's a json array already - $scope.currentTags = $scope.model.value; - } - else { + if (!$scope.model.config.storageType || $scope.model.config.storageType !== "Json") { //it is csv if (!$scope.model.value) { - $scope.currentTags = []; + $scope.model.value = []; } else { - $scope.currentTags = $scope.model.value.split(","); + $scope.model.value = $scope.model.value.split(","); } } } + else { + $scope.model.value = []; + } + + // Method required by the valPropertyValidator directive (returns true if the property editor has at least one tag selected) + $scope.validateMandatory = function () { + return { + isValid: !$scope.model.validation.mandatory || ($scope.model.value != null && $scope.model.value.length > 0), + errorMsg: "Value cannot be empty", + errorKey: "required" + }; + } //Helper method to add a tag on enter or on typeahead select function addTag(tagToAdd) { - if (tagToAdd.length > 0) { - if ($scope.currentTags.indexOf(tagToAdd) < 0) { - $scope.currentTags.push(tagToAdd); + if (tagToAdd != null && tagToAdd.length > 0) { + if ($scope.model.value.indexOf(tagToAdd) < 0) { + $scope.model.value.push(tagToAdd); + //this is required to re-validate + $scope.propertyForm.tagCount.$setViewValue($scope.model.value.length); } } } - $scope.addTag = function (e) { + $scope.addTagOnEnter = function (e) { var code = e.keyCode || e.which; if (code == 13) { //Enter keycode - - //ensure that we're not pressing the enter key whilst selecting a typeahead value from the drop down if ($element.find('.tags-' + $scope.model.alias).parent().find(".tt-dropdown-menu .tt-cursor").length === 0) { //this is required, otherwise the html form will attempt to submit. e.preventDefault(); - //we need to use jquery because typeahead duplicates the text box - addTag($scope.tagToAdd); - $scope.tagToAdd = ""; - //this clears the value stored in typeahead so it doesn't try to add the text again - // http://issues.umbraco.org/issue/U4-4947 - $typeahead.typeahead('val', ''); + $scope.addTag(); } - } }; + $scope.addTag = function () { + //ensure that we're not pressing the enter key whilst selecting a typeahead value from the drop down + //we need to use jquery because typeahead duplicates the text box + addTag($scope.tagToAdd); + $scope.tagToAdd = ""; + //this clears the value stored in typeahead so it doesn't try to add the text again + // http://issues.umbraco.org/issue/U4-4947 + $typeahead.typeahead('val', ''); + }; + + + $scope.removeTag = function (tag) { - var i = $scope.currentTags.indexOf(tag); + var i = $scope.model.value.indexOf(tag); if (i >= 0) { - $scope.currentTags.splice(i, 1); + $scope.model.value.splice(i, 1); + //this is required to re-validate + $scope.propertyForm.tagCount.$setViewValue($scope.model.value.length); } }; - //sync model on submit, always push up a json array - $scope.$on("formSubmitting", function (ev, args) { - $scope.model.value = $scope.currentTags; - }); - //vice versa $scope.model.onValueChanged = function (newVal, oldVal) { //update the display val again if it has changed from the server $scope.model.value = newVal; - if ($scope.model.config.storageType && $scope.model.config.storageType === "Json") { - //it's a json array already - $scope.currentTags = $scope.model.value; - } - else { + if (!$scope.model.config.storageType || $scope.model.config.storageType !== "Json") { //it is csv if (!$scope.model.value) { - $scope.currentTags = []; + $scope.model.value = []; } else { - $scope.currentTags = $scope.model.value.split(","); + $scope.model.value = $scope.model.value.split(","); } } }; @@ -99,14 +106,14 @@ angular.module("umbraco") }); // remove current tags from the list return $.grep(tagList, function (tag) { - return ($.inArray(tag.value, $scope.currentTags) === -1); + return ($.inArray(tag.value, $scope.model.value) === -1); }); } // helper method to remove current tags function removeCurrentTagsFromSuggestions(suggestions) { return $.grep(suggestions, function (suggestion) { - return ($.inArray(suggestion.value, $scope.currentTags) === -1); + return ($.inArray(suggestion.value, $scope.model.value) === -1); }); } @@ -147,7 +154,6 @@ angular.module("umbraco") // name = the data set name, we'll make this the tag group name name: $scope.model.config.group, displayKey: "value", - //source: tagsHound.ttAdapter(), source: function (query, cb) { tagsHound.get(query, function (suggestions) { cb(removeCurrentTagsFromSuggestions(suggestions)); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.html index e0f46727ac..b1049a2309 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.html @@ -1,11 +1,14 @@
- -
- Loading... + +
+ Loading...
- + + + + @@ -13,7 +16,11 @@ + ng-keydown="$parent.addTagOnEnter($event)" + on-blur="$parent.addTag()" + localize="placeholder" + placeholder="@placeholders_enterTags" /> +
+
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/test/config/karma.conf.js b/src/Umbraco.Web.UI.Client/test/config/karma.conf.js index d7d5786246..8df9c90183 100644 --- a/src/Umbraco.Web.UI.Client/test/config/karma.conf.js +++ b/src/Umbraco.Web.UI.Client/test/config/karma.conf.js @@ -23,9 +23,9 @@ module.exports = function(karma) { 'lib/angular/1.2/angular-mocks.js',*/ - 'lib/underscore/underscore.js', + 'lib/../build/belle/lib/underscore/underscore-min.js', 'lib/umbraco/Extensions.js', - 'lib/lazyload/lazyload.min.js', + 'lib/../build/belle/lib/rgrove-lazyload/lazyload.js', 'test/config/app.unit.js', diff --git a/src/Umbraco.Web.UI.Client/test/unit/common/directives/val-email.spec.js b/src/Umbraco.Web.UI.Client/test/unit/common/directives/val-email.spec.js new file mode 100644 index 0000000000..265689988d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/test/unit/common/directives/val-email.spec.js @@ -0,0 +1,34 @@ +describe('valEmail directive tests', function() { + + var valEmailExpression; + + beforeEach(module('umbraco')); + + beforeEach(inject(function ($injector) { + //TODO: I have no idea why this doesn't work!!?? it freakin should + //valEmailExpression = $injector.get('valEmailExpression'); + + //in the meantime, i've had to hard code the regex statement here + valEmailExpression = { + EMAIL_REGEXP: /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i + }; + + })); + + describe('EMAIL_REGEXP', function () { + /* global EMAIL_REGEXP: false */ + it('should validate email', function () { + expect(valEmailExpression.EMAIL_REGEXP.test('a@b.com')).toBe(true); + expect(valEmailExpression.EMAIL_REGEXP.test('a@b.museum')).toBe(true); + expect(valEmailExpression.EMAIL_REGEXP.test('a@B.c')).toBe(true); + expect(valEmailExpression.EMAIL_REGEXP.test('a@.b.c')).toBe(false); + expect(valEmailExpression.EMAIL_REGEXP.test('a@-b.c')).toBe(false); + expect(valEmailExpression.EMAIL_REGEXP.test('a@b-.c')).toBe(false); + expect(valEmailExpression.EMAIL_REGEXP.test('a@3b.c')).toBe(true); + expect(valEmailExpression.EMAIL_REGEXP.test('a@b')).toBe(true); + expect(valEmailExpression.EMAIL_REGEXP.test('abc@xyz.financial')).toBe(true); + + }); + }); + +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Global.asax b/src/Umbraco.Web.UI/Global.asax index 1627b363bc..9036e4acd6 100644 --- a/src/Umbraco.Web.UI/Global.asax +++ b/src/Umbraco.Web.UI/Global.asax @@ -1 +1,2 @@ -<%@ Application Codebehind="Global.asax.cs" Inherits="Umbraco.Web.UmbracoApplication" Language="C#" %> +<%@ Application Inherits="Umbraco.Web.UmbracoApplication" Language="C#" %> + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 4c2f5e8591..0fbbe9923a 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -115,18 +115,21 @@ False ..\packages\AutoMapper.3.0.0\lib\net40\AutoMapper.Net4.dll - + False - ..\packages\ClientDependency.1.7.1.2\lib\ClientDependency.Core.dll + ..\packages\ClientDependency.1.8.1.0\lib\net45\ClientDependency.Core.dll - + False - ..\packages\ClientDependency-Mvc.1.7.0.4\lib\ClientDependency.Core.Mvc.dll + ..\packages\ClientDependency-Mvc.1.8.0.0\lib\net45\ClientDependency.Core.Mvc.dll - + False - ..\packages\Examine.0.1.57.2941\lib\Examine.dll - True + ..\packages\Examine.0.1.59.2941\lib\Examine.dll + + + False + ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll False @@ -140,6 +143,10 @@ False ..\packages\log4net-mediumtrust.2.0.0\lib\log4net.dll + + False + ..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll + ..\packages\Microsoft.Bcl.Async.1.0.165\lib\net45\Microsoft.Threading.Tasks.dll @@ -229,9 +236,9 @@ ..\packages\Microsoft.AspNet.WebApi.WebHost.4.0.30506.0\lib\net40\System.Web.Http.WebHost.dll True - + True - ..\packages\Microsoft.AspNet.Mvc.4.0.40804.0\lib\net40\System.Web.Mvc.dll + ..\packages\Microsoft.AspNet.Mvc.4.0.30506.0\lib\net40\System.Web.Mvc.dll True @@ -1327,13 +1334,14 @@ Designer - - - - - - - + + + + + + + + Web.Template.config @@ -1776,7 +1784,7 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.0\x86\*.* "$(TargetDir)x86\" True True - 7200 + 7210 / http://localhost:8000 False diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Breadcrumb.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Breadcrumb.cshtml index 721abfb63b..ff63081996 100644 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Breadcrumb.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Breadcrumb.cshtml @@ -1,19 +1,21 @@ @inherits Umbraco.Web.Macros.PartialViewMacroPage @* - This snippet makes a breadcrumb of parents using an unordred html list. + This snippet makes a breadcrumb of parents using an unordered html list. How it works: - - It uses the Ancestors() method to get all parents and then generates links so the visitor get go back + - It uses the Ancestors() method to get all parents and then generates links so the visitor can go back - Finally it outputs the name of the current page (without a link) *@ -@if (CurrentPage.Ancestors().Any()) +@{ var selection = CurrentPage.Ancestors(); } + +@if (selection.Any()) {
- - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - - + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/config/tinyMceConfig.config b/src/Umbraco.Web.UI/config/tinyMceConfig.config index 43257ac797..a4cdb5f3f6 100644 --- a/src/Umbraco.Web.UI/config/tinyMceConfig.config +++ b/src/Umbraco.Web.UI/config/tinyMceConfig.config @@ -2,213 +2,220 @@ - - - code - images/editor/code.gif - code - 1 - - - removeformat - images/editor/removeformat.gif - removeformat - 2 - + + + code + images/editor/code.gif + code + 1 + + + codemirror + images/editor/code.gif + codemirror + 1 + + + removeformat + images/editor/removeformat.gif + removeformat + 2 + - - Undo - images/editor/undo.gif - undo - 11 - - - Redo - images/editor/redo.gif - redo - 12 - - - Cut - images/editor/cut.gif - cut - 13 - - - Copy - images/editor/copy.gif - copy - 14 - - - - styleselect - images/editor/showStyles.png - styleselect - 20 - - - bold - images/editor/bold.gif - bold - 21 - - - italic - images/editor/italic.gif - italic - 22 - - - Underline - images/editor/underline.gif - underline - 23 - - - Strikethrough - images/editor/strikethrough.gif - strikethrough - 24 - + + Undo + images/editor/undo.gif + undo + 11 + + + Redo + images/editor/redo.gif + redo + 12 + + + Cut + images/editor/cut.gif + cut + 13 + + + Copy + images/editor/copy.gif + copy + 14 + - - JustifyLeft - images/editor/justifyleft.gif - justifyleft - 31 - - - JustifyCenter - images/editor/justifycenter.gif - justifycenter - 32 - - - JustifyRight - images/editor/justifyright.gif - justifyright - 33 - - - JustifyFull - images/editor/justifyfull.gif - alignjustify - 34 - + + styleselect + images/editor/showStyles.png + styleselect + 20 + + + bold + images/editor/bold.gif + bold + 21 + + + italic + images/editor/italic.gif + italic + 22 + + + Underline + images/editor/underline.gif + underline + 23 + + + Strikethrough + images/editor/strikethrough.gif + strikethrough + 24 + - - bullist - images/editor/bullist.gif - bullist - 41 - - - numlist - images/editor/numlist.gif - numlist - 42 - - - Outdent - images/editor/outdent.gif - outdent - 43 - - - Indent - images/editor/indent.gif - indent - 44 - + + JustifyLeft + images/editor/justifyleft.gif + justifyleft + 31 + + + JustifyCenter + images/editor/justifycenter.gif + justifycenter + 32 + + + JustifyRight + images/editor/justifyright.gif + justifyright + 33 + + + JustifyFull + images/editor/justifyfull.gif + alignjustify + 34 + - - mceLink - images/editor/link.gif - link - 51 - - - unlink - images/editor/unLink.gif - unlink - 52 - - - mceInsertAnchor - images/editor/anchor.gif - anchor - 53 - + + bullist + images/editor/bullist.gif + bullist + 41 + + + numlist + images/editor/numlist.gif + numlist + 42 + + + Outdent + images/editor/outdent.gif + outdent + 43 + + + Indent + images/editor/indent.gif + indent + 44 + - - mceImage - images/editor/image.gif - image - 61 - + + mceLink + images/editor/link.gif + link + 51 + + + unlink + images/editor/unLink.gif + unlink + 52 + + + mceInsertAnchor + images/editor/anchor.gif + anchor + 53 + - - umbracomacro - images/editor/insMacro.gif - umbracomacro - 62 - - - - - - mceInsertTable - images/editor/table.gif - table - 63 - - - - umbracoembed - images/editor/media.gif - umbracoembed - 66 - - - inserthorizontalrule - images/editor/hr.gif - hr - 71 - - - subscript - images/editor/sub.gif - subscript - 72 - + + mceImage + images/editor/image.gif + image + 61 + - - superscript - images/editor/sup.gif - superscript - 73 - + + umbracomacro + images/editor/insMacro.gif + umbracomacro + 62 + - - mceCharMap - images/editor/charmap.gif - charmap - 74 - - - - - codemirror - paste - umbracolink - anchor - charmap - table - lists - - - + mceInsertTable + images/editor/table.gif + table + 63 + + + + umbracoembed + images/editor/media.gif + umbracoembed + 66 + + + inserthorizontalrule + images/editor/hr.gif + hr + 71 + + + subscript + images/editor/sub.gif + subscript + 72 + + + + superscript + images/editor/sup.gif + superscript + 73 + + + + mceCharMap + images/editor/charmap.gif + charmap + 74 + + + + + code + codemirror + paste + umbracolink + anchor + charmap + table + lists + + + - - font + + font - - - - raw - - { - "indentOnInit": false, - "path": "../../../../../umbraco_client/CodeMirror/Js", - "config": { - }, - "jsFiles": [ - ], - "cssFiles": [ - ] - } - - + + + + raw + + { + "indentOnInit": false, + "path": "../../../../../umbraco_client/CodeMirror/Js", + "config": { + }, + "jsFiles": [ + ], + "cssFiles": [ + ] + } + + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config index 73500c7fe1..dcaaa382e8 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config @@ -45,7 +45,7 @@ inline - ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd + ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd,swf,xml,html,htm,svg,php,htaccess Textstring diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.config b/src/Umbraco.Web.UI/config/umbracoSettings.config index 1c17ce1014..a7e4ee8ad7 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.config @@ -98,7 +98,7 @@ throw - ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd + ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd,swf,xml,html,htm,svg,php,htaccess Textstring diff --git a/src/Umbraco.Web.UI/packages.config b/src/Umbraco.Web.UI/packages.config index b0a303bf2b..4aaca57a25 100644 --- a/src/Umbraco.Web.UI/packages.config +++ b/src/Umbraco.Web.UI/packages.config @@ -1,14 +1,14 @@  - - - + + + - - + + @@ -23,7 +23,7 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco/Install/Views/Index.cshtml b/src/Umbraco.Web.UI/umbraco/Install/Views/Index.cshtml index 5673ceff68..8171aad9e2 100644 --- a/src/Umbraco.Web.UI/umbraco/Install/Views/Index.cshtml +++ b/src/Umbraco.Web.UI/umbraco/Install/Views/Index.cshtml @@ -54,7 +54,7 @@ "umbracoBaseUrl": "@ViewBag.UmbracoBaseFolder" }; - + diff --git a/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml b/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml index 37c5534fae..bef809a593 100644 --- a/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml +++ b/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml @@ -20,6 +20,7 @@ + Umbraco @@ -62,7 +63,7 @@ @*And finally we can load in our angular app*@ - + @{ diff --git a/src/Umbraco.Web.UI/umbraco/config/create/UI.Release.xml b/src/Umbraco.Web.UI/umbraco/config/create/UI.Release.xml index d05908e2cf..0e13eba345 100644 --- a/src/Umbraco.Web.UI/umbraco/config/create/UI.Release.xml +++ b/src/Umbraco.Web.UI/umbraco/config/create/UI.Release.xml @@ -130,7 +130,7 @@ - +
member
/create/member.ascx @@ -324,7 +324,7 @@
Macro
- /Create/PartialView.ascx + /Create/PartialView.ascx @@ -338,4 +338,20 @@
+ +
Macro
+ /Create/PartialView.ascx + + + + +
+ +
Macro
+ /Create/PartialViewMacro.ascx + + + + +
diff --git a/src/Umbraco.Web.UI/umbraco/config/create/UI.xml b/src/Umbraco.Web.UI/umbraco/config/create/UI.xml index c2a4c90d0a..1ab0f4180c 100644 --- a/src/Umbraco.Web.UI/umbraco/config/create/UI.xml +++ b/src/Umbraco.Web.UI/umbraco/config/create/UI.xml @@ -330,14 +330,6 @@ - -
Macro
- /Create/PartialView.ascx - - - - -
Macro
/Create/PartialViewMacro.ascx @@ -346,6 +338,14 @@
+ +
Macro
+ /Create/PartialView.ascx + + + + +
Macro
/Create/PartialViewMacro.ascx diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index a6fe801c51..ad7737e5be 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -2,7 +2,7 @@ The Umbraco community - http://Umbraco.org/documentation/language-files + http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files Tilføj domæne @@ -228,6 +228,7 @@ Indtast navn... Søg... Filtrer... + Indtast nøgleord (tryk på Enter efter hvert nøgleord)... Tillad på rodniveau @@ -409,7 +410,7 @@ installér knappen for at installere Umbraco %0% databasen]]> Næste for at fortsætte.]]> Databasen er ikke fundet. Kontrollér venligst at informationen i database forbindelsesstrengen i "web.config" filen er korrekt.

-

For at fortsætte bedes du venligst rette "web.config" filen (ved at bruge Visual Studio eller dit favoritprogram), scroll til bunden, tilføj forbindelsesstrengen til din database i feltet som hedder "UmbracoDbDSN" og gem filen.

Klik på Forsøg igen knappen når du er færdig.
Mere information om at redigere web.config her.

]]>
+

For at fortsætte bedes du venligst rette "web.config" filen (ved at bruge Visual Studio eller dit favoritprogram), scroll til bunden, tilføj forbindelsesstrengen til din database i feltet som hedder "umbracoDbDSN" og gem filen.

Klik på Forsøg igen knappen når du er færdig.
Mere information om at redigere web.config her.

]]> Kontakt venligst din ISP hvis det er nødvendigt. Hvis du installerer på en lokal maskine eller server kan du muligvis få informationerne fra din systemadministrator.]]> Tryk på Opgradér knappen for at opgradere din database til Umbraco %0%

Bare rolig - intet indhold vil blive slettet og alt vil stadig fungere bagefter!

]]>
Tryk på Næste for at fortsætte.]]> @@ -487,7 +488,7 @@ Din session er udløbet - © 2001 - %0%
Umbraco.org

]]>
+ © 2001 - %0%
umbraco.com

]]>
Skrivebord @@ -524,7 +525,11 @@ Ha' en dejlig dag! Mange hilsner fra Umbraco robotten ]]> - Hej %0%

Dette er en automatisk mail for at informere dig om at opgaven '%1%' er blevet udførtpå siden '%2%' af brugeren '%3%'

Opdateringssammendrag:

%6%

Hav en fortsat god dag!

De bedste hilsner fra Umbraco robotten

]]>
+ Hej %0%

+

Dette er en automatisk mail for at informere dig om at opgaven '%1%' + er blevet udførtpå siden '%2%' af brugeren '%3%'

+

Opdateringssammendrag:

%6%

Hav en fortsat god dag!

De bedste hilsner fra umbraco robotten

]]>
[%0%] Notificering om %1% udført på %2% Notificeringer @@ -585,23 +590,58 @@ Mange hilsner fra Umbraco robotten %0% ud af %1% sider er blevet udgivet... %0% er nu publiceret %0% og alle undersider er nu publiceret - Publisér alle undersider + Publicér alle undersider ok for at udgive %0% og derved gøre indholdet offentligt tilgængeligt..

Du kan udgive denne side og dens undersider ved at klikke Udgiv alle undersider forneden]]>
Du har ikke konfigureret nogen godkendte farver - Tilføj eksternt link - Tilføj internt link - Tilføj - Billedtekst - Intern side - URL - Flyt ned - Flyt op - Åben i nyt vindue - Fjern link + indtast eksternt link + vælg en intern side + Tekst + Link + Nyt vindue + Indtast en tekst + Indtast et link + + + Nulstil + + + Indsæt element + Tilføj rækker til dit layout + nedenfor og tilføje det første element]]> + + Klik for at indlejre + Klik for at indsætte et billede + Billedtekst... + Skriv her... + + Gridlayout + Et layout er det overordnede arbejdsområde til dit gitter - du vil typisk kun behøve ét eller to + Tilføj gitterlayout + Juster dit layout ved at justere kolonnebredder og tilføj yderligere sektioner + + Rækkekonfigurationer + Rækker er foruddefinerede celler, der arrangeres vandret + Tilføj rækkekonfiguration + Juster rækken ved at indstille cellebredder og tilføje yderligere celler + + Kolonner + Det totale antaller kolonner i gitteret + + Indstillinger + Konfigurer, hvilket indstillinger, brugeren kan ændre + + + Typografi + Vælg, hvilke typografiværdier, en redaktør kan ændre + + Indstillinger gemmes kun, hvis den indtaste json-konfiguration er gyldig + + Tillad alle editorer + Tillad alle rækkekonfigurationer Nuværende version @@ -642,6 +682,8 @@ Mange hilsner fra Umbraco robotten Faneblad Titel på faneblad Faneblade + Master Dokument Type + Opret matchende skabelon Sortering udført @@ -753,7 +795,7 @@ Mange hilsner fra Umbraco robotten Download XML DTD Felter Inkluder undersider - Hej %0%. Dette er en automatisk mail sendt for at informere dig om at dokumentet '%1' har en forespørgsel omkring oversættelse til '%5%' af %2%. Gå til http://%3%/Umbraco/translation/details.aspx?id=%4% for at redigere. Eller log ind i Umbraco for at få en oversigt over dine oversættelsesopgaver: http://%3%/Umbraco Hav en god dag! Mange hilsner Umbraco-robotten + Hej %0%. Dette er en automatisk mail sendt for at informere dig om at dokumentet '%1' har en forespørgsel omkring oversættelse til '%5%' af %2%. Gå til http://%3%/translation/details.aspx?id=%4% for at redigere. Eller log ind i Umbraco for at få en oversigt over dine oversættelsesopgaver: http://%3%/Umbraco Hav en god dag! Mange hilsner Umbraco-robotten [%0%] Oversættelsesopgave for %1% Ingen oversættelsesbrugere er fundet. Opret venligst en oversættelsesbruger før du begynder at sende indhold til oversættelse Opgaver oprettet af dig diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml index 1cc39347d8..6c7f52b3aa 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml @@ -1,8 +1,8 @@ - The German Umbraco Community - http://Umbraco.org + The Umbraco community + http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files Hostnamen verwalten @@ -465,7 +465,7 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die Erneuern Sie, um Ihre Arbeit zu speichern ... - <p style="text-align:right;">&copy; 2001 - %0% <br /><a href="http://Umbraco.org" style="text-decoration: none" target="_blank">Umbraco.org</a></p> + <p style="text-align:right;">&copy; 2001 - %0% <br /><a href="http://umbraco.com" style="text-decoration: none" target="_blank">umbraco.org</a></p> Einen wunderbaren Sonntag Frohen freundlichen Freitag Donnerwetter Donnerstag @@ -779,7 +779,7 @@ Hallo %0%, das Dokument '%1%' wurde von '%2%' zur Übersetzung in '%5%' freigegeben. -Zum Bearbeiten verwenden Sie bitte diesen Link: http://%3%/Umbraco/translation/details.aspx?id=%4%. +Zum Bearbeiten verwenden Sie bitte diesen Link: http://%3%/translation/details.aspx?id=%4%. Sie können sich auch alle anstehenden Übersetzungen gesammelt im Umbraco-Verwaltungsbereich anzeigen lassen: http://%3%/Umbraco/Umbraco.aspx diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 9dfdb392d1..e43af2752f 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -1,8 +1,8 @@ - + - Umbraco - http://Umbraco.org + The Umbraco community + http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files Culture and Hostnames @@ -44,16 +44,16 @@ Invalid node. Invalid domain format. Domain has already been assigned. - Domain Language + Domain New domain '%0%' has been created Domain '%0%' is deleted Domain '%0%' has already been assigned + Domain '%0%' has been updated + Edit Current Domains
One-level paths in domains are supported, eg. "example.com/en". However, they should be avoided. Better use the culture setting above.]]>
- Domain '%0%' has been updated - Edit Current Domains Inherit Culture or inherit culture from parent nodes. Will also apply
@@ -132,7 +132,7 @@ This item has been changed after publication This item is not published Last published - There are no items show in the list. + There are no items to show in the list. Media Type Link to media item(s) Member Group @@ -244,7 +244,8 @@ Name the %0%... Enter a name... Type to search... - Type to filter... + Type to filter... + Type to add tags (press enter after each tag)... Allow at root @@ -409,9 +410,9 @@ Search results - Background color + Background colour Bold - Text color + Text colour Font Text @@ -427,11 +428,11 @@ Press the install button to install the Umbraco %0% database ]]>
Next to proceed.]]> - Database not found! Please check that the information in the "connection string" of the “web.config” file is correct.

+ Database not found! Please check that the information in the "connection string" of the "web.config" file is correct.

To proceed, please edit the "web.config" file (using Visual Studio or your favourite text editor), scroll to the bottom, add the connection string for your database in the key named "UmbracoDbDSN" and save the file.

Click the retry button when - done.
+ done.
More information on editing web.config here.

]]>
Please contact your ISP if necessary. @@ -446,7 +447,7 @@ Press Next to proceed. ]]> next to continue the configuration wizard]]> - The Default users’ password needs to be changed!]]> + The Default users' password needs to be changed!]]> The Default user has been disabled or has no access to Umbraco!

No further actions needs to be taken. Click Next to proceed.]]> The Default user's password has been successfully changed since the installation!

No further actions needs to be taken. Click Next to proceed.]]> The password is changed! @@ -487,11 +488,11 @@ ]]> I want to start from scratch learn how) You can still choose to install Runway later on. Please go to the Developer section and choose Packages. ]]> - You’ve just set up a clean Umbraco platform. What do you want to do next? + You've just set up a clean Umbraco platform. What do you want to do next? Runway is installed @@ -502,7 +503,7 @@ "Runway" is a simple website providing some basic document types and templates. The installer can set up Runway for you automatically, - but you can easily edit, extend or remove it. It’s not necessary and you can perfectly use Umbraco without it. However, + but you can easily edit, extend or remove it. It's not necessary and you can perfectly use Umbraco without it. However, Runway offers an easy foundation based on best practices to get you started faster than ever. If you choose to install Runway, you can optionally select basic building blocks called Runway Modules to enhance your Runway pages.

@@ -701,19 +702,19 @@ To manage your website, simply open the Umbraco back office and start adding con ]]>
- You have not configured any approved colors + You have not configured any approved colours - Add external link - Add internal link - Add + enter external link + choose internal page Caption - Internal page - URL - Move Down - Move Up - Open in new window - Remove link + Link + New window + Enter a new caption + Enter the link + + + Reset Current version @@ -761,6 +762,8 @@ To manage your website, simply open the Umbraco back office and start adding con This Content Type uses as a Master Content Type. Tabs from Master Content Types are not shown and can only be edited on the Master Content Type itself No properties defined on this tab. Click on the "add a new property" link at the top to create a new property. + Master Document Type + Create matching template Sorting complete. @@ -841,6 +844,43 @@ To manage your website, simply open the Umbraco back office and start adding con Quick Guide to Umbraco template tags Template + + + Insert control + Add rows to your layout + below and add your first element]]> + + Click to embed + Click to insert image + Image caption... + Write here... + + Grid layouts + Layouts are the overall work area for the grid editor, usually you only need one or two different layouts + Add grid layout + Adjust the layout by setting column widths and adding additional sections + + Row configurations + Rows are predefined cells arranged horizontally + Add row configuration + Adjust the row by setting cell widths and adding additional cells + + Columns + Total combined number of columns in the grid layout + + Settings + Configure what settings editors can change + + + Styles + Configure what styling editors can change + + Settings will only save if the entered json configuration is valid + + Allow all editors + Allow all row configurations + + Alternative field Alternative Text @@ -998,4 +1038,4 @@ To manage your website, simply open the Umbraco back office and start adding con Your recent history Session expires in -
\ No newline at end of file +
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 2bde2829dd..0d700fa50f 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -1,8 +1,8 @@ - Umbraco - http://Umbraco.org + The Umbraco community + http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files Culture and Hostnames @@ -134,6 +134,7 @@ This item has been changed after publication This item is not published Last published + There are no items show in the list. Media Type Link to media item(s) Member Group @@ -153,6 +154,7 @@ To sort the nodes, simply drag the nodes or click one of the column headers. You can select multiple nodes by holding the "shift" or "control" key while selecting Statistics Title (optional) + Alternative text (optional) Type Unpublish Last edited @@ -209,7 +211,7 @@ Last Edited Link Internal link: - When using local links, insert "#" infront of link + When using local links, insert "#" in front of link Open in new window? Macro Settings This macro does not contain any properties you can edit @@ -223,7 +225,7 @@ Remove Macro Required Field Site is reindexed - The website cache has been refreshed. All publish content is now uptodate. While all unpublished content is still unpublished + The website cache has been refreshed. All publish content is now up to date. While all unpublished content is still unpublished The website cache will be refreshed. All published content will be updated, while unpublished content will stay unpublished. Number of columns Number of rows @@ -283,7 +285,7 @@ Your data has been saved, but before you can publish this page there are some errors you need to fix first: - The current MemberShip Provider does not support changing password (EnablePasswordRetrieval need to be true) + The current membership provider does not support changing password (EnablePasswordRetrieval need to be true) %0% already exists There were errors: There were errors: @@ -297,7 +299,7 @@ The specified file type has been disallowed by the administrator NOTE! Even though CodeMirror is enabled by configuration, it is disabled in Internet Explorer because it's not stable enough. - Please fill both alias and name on the new propertytype! + Please fill both alias and name on the new property type! There is a problem with read/write access to a specific file or folder Please enter a title Please choose a type @@ -432,11 +434,11 @@ Press the install button to install the Umbraco %0% database ]]> Next to proceed.]]> - Database not found! Please check that the information in the "connection string" of the “web.config” file is correct.

+ Database not found! Please check that the information in the "connection string" of the "web.config" file is correct.

To proceed, please edit the "web.config" file (using Visual Studio or your favourite text editor), scroll to the bottom, add the connection string for your database in the key named "UmbracoDbDSN" and save the file.

Click the retry button when - done.
+ done.
More information on editing web.config here.

]]>
Please contact your ISP if necessary. @@ -451,7 +453,7 @@ Press Next to proceed. ]]> next to continue the configuration wizard]]> - The Default users’ password needs to be changed!]]> + The Default users' password needs to be changed!]]> The Default user has been disabled or has no access to Umbraco!

No further actions needs to be taken. Click Next to proceed.]]> The Default user's password has been successfully changed since the installation!

No further actions needs to be taken. Click Next to proceed.]]> The password is changed! @@ -465,7 +467,7 @@

]]>
Get a great start, watch our introduction videos - By clicking the next button (or modifying the UmbracoConfigurationStatus in web.config), you accept the license for this software as specified in the box below. Notice that this Umbraco distribution consists of two different licenses, the open source MIT license for the framework and the Umbraco freeware license that covers the UI. + By clicking the next button (or modifying the umbracoConfigurationStatus in web.config), you accept the license for this software as specified in the box below. Notice that this Umbraco distribution consists of two different licenses, the open source MIT license for the framework and the Umbraco freeware license that covers the UI. Not installed yet. Affected files and folders More information on setting up permissions for Umbraco here @@ -492,11 +494,11 @@ ]]>
I want to start from scratch learn how) You can still choose to install Runway later on. Please go to the Developer section and choose Packages. ]]> - You’ve just set up a clean Umbraco platform. What do you want to do next? + You've just set up a clean Umbraco platform. What do you want to do next? Runway is installed @@ -507,7 +509,7 @@ "Runway" is a simple website providing some basic document types and templates. The installer can set up Runway for you automatically, - but you can easily edit, extend or remove it. It’s not necessary and you can perfectly use Umbraco without it. However, + but you can easily edit, extend or remove it. It's not necessary and you can perfectly use Umbraco without it. However, Runway offers an easy foundation based on best practices to get you started faster than ever. If you choose to install Runway, you can optionally select basic building blocks called Runway Modules to enhance your Runway pages.

@@ -561,8 +563,7 @@ To manage your website, simply open the Umbraco back office and start adding con log in below Session timed out - - © 2001 - %0%
Umbraco.org

]]>
+ © 2001 - %0%
Umbraco.com

]]>
@@ -677,7 +678,7 @@ To manage your website, simply open the Umbraco back office and start adding con %0% is now protected Protection removed from %0% Login Page - Choose the page that has the login formular + Choose the page that contains the login form Remove Protection Select the pages that contain login form and error messages Pick the roles who have access to this page @@ -695,7 +696,7 @@ To manage your website, simply open the Umbraco back office and start adding con %0% could not be published because these properties: %1% did not pass validation rules. ]]>
%0% and subpages have been published Publish %0% and all its subpages ok to publish %0% and thereby making its content publicly available.

- You can publish this page and all its sub-pages by checking publish all children below. + You can publish this page and all it's sub-pages by checking publish all children below. ]]>
@@ -729,7 +730,7 @@ To manage your website, simply open the Umbraco back office and start adding con Current version Red text will not be shown in the selected version. , green means added]]> Document has been rolled back - This displays the selected version as html, if you wish to see the difference between 2 versions at the same time, use the diff view + This displays the selected version as HTML, if you wish to see the difference between 2 versions at the same time, use the diff view Rollback to Select version View @@ -752,13 +753,14 @@ To manage your website, simply open the Umbraco back office and start adding con Users Help + Analytics Default template Dictionary Key To import a document type, find the ".udt" file on your computer by clicking the "Browse" button and click "Import" (you'll be asked for confirmation on the next screen) New Tab Title - Nodetype + Node type Type Stylesheet Script @@ -770,6 +772,8 @@ To manage your website, simply open the Umbraco back office and start adding con This Content Type uses as a Master Content Type. Tabs from Master Content Types are not shown and can only be edited on the Master Content Type itself No properties defined on this tab. Click on the "add a new property" link at the top to create a new property. + Master Document Type + Create matching template Sorting complete. @@ -850,14 +854,44 @@ To manage your website, simply open the Umbraco back office and start adding con Quick Guide to Umbraco template tags Template + + Insert control + Add rows to your layout + below and add your first element]]> + + Grid layouts + Layouts are the overall work area for the grid editor, usually you only need one or two different layouts + Add grid layout + Adjust the layout by setting column widths and adding additional sections + + Row configurations + Rows are predefined cells arranged horizontally + Add row configuration + Adjust the row by setting cell widths and adding additional cells + + Columns + Total combined number of columns in the grid layout + + Settings + Configure what settings editors can change + + + Styles + Configure what styling editors can change + + Settings will only save if the entered json configuration is valid + + Allow all editors + Allow all row configurations + Alternative field Alternative Text Casing Encoding Choose field - Convert Linebreaks - Replaces linebreaks with html-tag &lt;br&gt; + Convert line breaks + Replaces line breaks with html-tag &lt;br&gt; Custom Fields Yes, Date only Format as date @@ -888,9 +922,9 @@ To manage your website, simply open the Umbraco back office and start adding con ]]>
close task Translation details - Download all translation tasks as xml - Download xml - Download xml DTD + Download all translation tasks as XML + Download XML + Download XML DTD Fields Include subpages Translate to Translation completed. You can preview the pages, you've just translated, by clicking below. If the original page is found, you will get a comparison of the 2 pages. - Translation failed, the xml file might be corrupt + Translation failed, the XML file might be corrupt Translation options Translator - Upload translation xml + Upload translation XML Cache Browser @@ -957,6 +991,7 @@ To manage your website, simply open the Umbraco back office and start adding con Stylesheets Templates XSLT Files + Analytics New update ready diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/es.xml b/src/Umbraco.Web.UI/umbraco/config/lang/es.xml index cbdc2be1a5..fab7d30f03 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/es.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/es.xml @@ -1,13 +1,14 @@ - + The Umbraco community - http://Umbraco.org/documentation/language-files + http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files Administrar hostnames Auditoría Nodo de Exploración + Cambiar tipo de documento Copiar Crear Crear Paquete @@ -15,8 +16,6 @@ Deshabilitar Vaciar Papelera Exportar Documento (tipo) - TRANSLATE ME: 'Export to .NET' - TRANSLATE ME: 'Export to .NET' Importar Documento (tipo) Importar Paquete Editar en lienzo @@ -25,6 +24,7 @@ Notificaciones Acceso Público Publicar + Unpublish Recargar Nodos Republicar sitio completo Permisos @@ -35,21 +35,41 @@ Enviar a publicación Traducir Actualizar + Valor por defecto + Permiso denegado. Añadir nuevo dominio + quitar + Nodo no válido. + Formato de dominio no válido. + Este dominio ya ha sido asignado. + Language Dominio El nuevo dominio %0% ha sido creado El dominio %0% ha sido borrado El dominio'%0%' ya ha sido asignado - p.ej.: tudominio.com, www.tudominio.com El dominio %0% ha sido actualizado Editar dominios actuales + +
Los dominios de un nivel están soportados, por ej. "example.com/en". De todas formas deberían de evitarse. Mejor usar la configuración cultural especificada arriba.]]> +
+ Heredar + Cultura + + o hereda la cultura de los nodos padres. También se aplicará
+ para el nodo actual, a menos que un dominio por debajo lo aplique también.]]> +
+ Domains Visualización de + Seleccionar + Selecciona la carpeta actual + Hacer otra cosa Negrita Cancelar Sangría del Párrafo Insertar campo de formulario @@ -71,18 +91,44 @@ Guardar y publicar Guardar y enviar para aprobación Previsualizar + La previsualización está deshabilitada porque no hay ninguna plantilla asignada Elegir estilo Mostrar estilos Insertar tabla + Volver al listado + + + Para cambiar el tipo de documento al contenido seleccionado, primero selecciona uno de la lista de tipos válidos. + Entonces confirma el mapeo de propiedades del tipo actual al nuevo y haz click en Guardar. + El contenido se ha vuelto a publicar. + Propiedad actual + Tipo actual + El tipo de contenido no se puede cambiar, porque no hay alternativas válidas para este contenido. + Tipo de documento cambiado + Mapeo de propiedades + Mapea a la propiedad + Nueva plantilla + Nuevo tipo + ninguno + Contenido + Selecciona un nuevo Tipo de Documento + El tipo de documento del contenido seleccionado ha sido cambiado correctamente a [new type] y las siguientes propiedades mapeadas: + a + No se ha podido completar el mapeo de propiedades porque uno o más propiedades tienen más de un mapeo definido. + Solo se muestran otros tipos válidos para el contenido actual. + Está publicado Acerca de Link alternativo (como describe la imagen sobre el teléfono) Vinculos Alternativos Click para editar esta entrada Creado por + Autor original + Actualizado por Creado + Fecha/hora de creación del documento Tipo de Documento Editando Remover el @@ -97,9 +143,11 @@ Título de la página Propiedades Este documento ha sido publicado pero no es visible porque el padre '%0%' no esta publicado + Upss: este documento está publicado pero no está en la caché (error interno) Publicar Estado de la Publicación Publicar el + Despublicar el Fecha de Eliminación El Orden esta actualizado Para organizar los nodos, simplemente arrastre los nodos o realice un clic en uno de los encabezados de columna. Puede seleccionar multiple nodos manteniendo presionados "Shift" o "Control" mientras selecciona @@ -108,17 +156,29 @@ Tipo No Publicar Última actualización + Fecha/hora este documento fue modificado Eliminar archivo Vínculo al documento + Miembro de grupo(s) + No es miembreo de grupo(s) + Nodos hijo + Target + No hay datos que mostrar + + + Haz click para subir archivos + Arrastra los archivos aquí... ¿Dónde quieres crear el nuevo %0% Crear debajo de Elije un tipo y un título + "Tipos de documentos".]]> + "Tipos de medios".]]> Navega en tu sitio Web - TRANSLATE ME: '- Hide' + No volver a mostrar Si Umbraco no se ha abierto tendrás que permitir ventanas emergentes para este sitio Web se ha abierto en una nueva ventana Reinicio @@ -134,11 +194,11 @@ Por favor seleccione esta casilla para confirmar la eliminación de %0% entrada(s) Esta usted seguro? Esta usted Seguro? - TRANSLATE ME: 'Cut' + Cortar Editar entrada del Diccionario Editar idioma Agregar enlace interno - TRANSLATE ME: 'Insert character' + Insertar caracter Insertar titular gráfico Insertar imagen Insertar enlace @@ -149,7 +209,7 @@ Enlace interno Al usar enlaces locales, insertar "#" delante del enlace ¿Abrir en nueva ventana? - TRANSLATE ME: 'Macro Settings' + Ajustes para la Macro Esta macro no contiene ninguna propiedad que pueda editar Pegar Editar permisos para @@ -158,7 +218,7 @@ No podrá recuperar los items una vez sean borrados de la papelera regexlib.com está experimentando algunos problemas en estos momentos, de los cuales no somos responsables. Pedimos disculpas por las molestias.]]> Buscar una expresión regular para agregar validación a un campo de formulario. Ejemplo: 'correo electrónico', código postal "," url " - TRANSLATE ME: 'Remove Macro' + Eliminar macro Campo obligatorio El sitio ha sido reindexado Se ha actualizado la caché y se ha publicado el contenido del sitio web. @@ -176,14 +236,31 @@ + + Escribe tu nombre de usuario + Escribe tu contraseña + Nombre del %0%... + Escribe un nombre... + Escribe tu búsqueda... + Escribe para filtrar resultados... + + Permitir en nodo raíz + Sólo tipos de contenido permitidos podrán crearse bajo el nodo raíz de los árboles de Contenido y Media Tipos de nodos hijos permitidos + Composiciones de Tipo de Documento Crear Borrar pestaña - TRANSLATE ME: 'Description' + Descripción Nueva pestaña Pestaña - TRANSLATE ME: 'Thumbnail' + Miniatura + Permitir vista de listado + Configura el contenido para mostrar un listado de nodos hijos, en lugar de mostrarlos en forma de árbol + Vista de listado actual + El tipo de vista de listado activa + Crear un tipo de listado personalizado + Quitar el tipo de listado personalizado añadir prevalor @@ -215,7 +292,7 @@ Debe poner un formato correcto en %0% - TRANSLATE ME: 'NOTE! Even though CodeMirror is enabled by configuration, it is disabled in Internet Explorer because it's not stable enough.' + NOTA: Aunque CodeMirror esté activado en los ajustes de configuracion, no se muestra en Internet Explorer debido a que no es lo suficientemente estable.' Debe llenar el alias y el nombre en el propertytype Hay un problema de lectura y escritura al acceder a un archivo o carpeta Acerca de Acción + Acciones Añadir Alias ¿Está seguro? @@ -268,7 +346,7 @@ Editar Editado Elementos - Email + Mail Error Buscar Altura @@ -282,7 +360,7 @@ Idioma Diseño Cargando - TRANSLATE ME: 'Locked' + Bloqueado Iniciar sesión Cerrar sesión Cerrar sesión @@ -302,11 +380,11 @@ Un momento por favor... Anterior Propiedades - Email para recibir los datos del formulario + Mail para recibir los datos del formulario Papelera Restantes Renombrar - TRANSLATE ME: 'Renew' + Renovar Reintentar Permisos Buscar @@ -347,7 +425,7 @@ Configuración de la base de datos instalar para instalar %0% la base de datos de Umbraco]]> Próximo para continuar]]> - ¡No se ha encontrado ninguna base de datos! Mira si la información en la "connection string" del “web.config” es correcta.

Para continuar, edite el "web.config" (bien sea usando Visual Studio o su editor de texto preferido), vaya al final del archivo y añada la cadena de conexión para la base de datos con el nombre (key) "UmbracoDbDSN" y guarde el archivo.

Pinche en reintentar cuando haya terminado.
Pinche aquí para mayor información de como editar el web.config (en inglés)

]]>
+ ¡No se ha encontrado ninguna base de datos! Mira si la información en la "connection string" del “web.config” es correcta.

Para continuar, edite el "web.config" (bien sea usando Visual Studio o su editor de texto preferido), vaya al final del archivo y añada la cadena de conexión para la base de datos con el nombre (key) "umbracoDbDSN" y guarde el archivo.

Pinche en reintentar cuando haya terminado.
Pinche aquí para mayor información de como editar el web.config (en inglés)

]]>
Por favor, contacta con tu ISP si es necesario. Si estás realizando la instalación en una máquina o servidor local, quizás necesites información de tu administrador de sistemas.]]> Pinche en actualizar para actualizar la base de datos a Umbraco %0%

Ningún contenido será borrado de la base de datos y seguirá funcionando después de la actualización

]]>
Pinche en Próximo para continuar. ]]> @@ -381,8 +459,7 @@ Esta es nuestra lista de módulos recomendados, selecciona los que desees instalar, o mira la lista completa de módulos ]]> Sólo recomendado para usuarios expertos Quiero empezar con un sitio web sencillo - "Runway" es un sitio web sencillo que contiene unos tipos de documentos y plantillas básicos. El instalador puede configurar Runway por ti de forma automática, pero fácilmente puedes editarlo, extenderlo o eliminarlo. No es necesario y puedes usar Umbrao perfectamente sin él. Sin embargo, Runway ofrece unos cimientos sencillos basados en buenas prácticas para iniciarte más rápido que nunca. Si eliges instalar Runway, puedes seleccionar bloques de construcción básicos llamados Módulos de Runway de forma opcional para realzar tus páginas de Runway. Incluido con Runway: Página de inicio, página de Cómo empezar, página de Instalación de módulos.
Módulos opcionales: Navegación superior, Mapa del sitio, Contacto, Galería.
-However, Runway offers an easy foundation based on best practices to get you started faster than ever. If you choose to install Runway, you can optionally select basic building blocks called Runway Modules to enhance your Runway pages.

Included with Runway: Home page, Getting Started page, Installing Modules page.
Optional Modules: Top Navigation, Sitemap, Contact, Gallery.
]]>
+ "Runway" es un sitio web sencillo que contiene unos tipos de documentos y plantillas básicos. El instalador puede configurar Runway por ti de forma automática, pero fácilmente puedes editarlo, extenderlo o eliminarlo. No es necesario y puedes usar Umbrao perfectamente sin él. Sin embargo, Runway ofrece unos cimientos sencillos basados en buenas prácticas para iniciarte más rápido que nunca. Si eliges instalar Runway, puedes seleccionar bloques de construcción básicos llamados Módulos de Runway de forma opcional para realzar tus páginas de Runway. Incluido con Runway: Página de inicio, página de Cómo empezar, página de Instalación de módulos.
Módulos opcionales: Navegación superior, Mapa del sitio, Contacto, Galería.
]]>
¿Qué es Runway? Paso 1 de 5. Aceptar los términos de la licencia Paso 2 de 5. Configuración de la base de datos @@ -407,13 +484,22 @@ However, Runway offers an easy foundation based on best practices to get you sta Nombre de cultura - TRANSLATE ME: 'You've been idle and logout will automatically occur in' - TRANSLATE ME: 'Renew now to save your work' + No ha habido ninguna actividad y su sessión se cerrará en + Renovar su sessión para guardar sus cambios - © 2001 - %0%
Umbraco.org

]]>
- Bienvenido a Umbraco, escribe tu nombre de usuario y clave en los campos de debajo: + Feliz super domingo + Feliz lunes + Tremendo martes + Maravilloso miércoles + Fantástico jueves + ¡Ya es viernes! + Resplandeciente sábado + Iniciar sesión + La sesión ha caducado + © 2001 - %0%
umbraco.com

]]>
+ Panel de Administración Secciones @@ -429,7 +515,7 @@ However, Runway offers an easy foundation based on best practices to get you sta No ha seleccionado ningún nodo. Seleccione un nodo en la lista mostrada arriba antes the pinchar en 'continuar' (continue) No se puede colgar el nodo actual bajo el nodo elegido debido a su tipo El nodo actual no puede moverse a ninguna de sus subpáginas - TRANSLATE ME: 'The action isn't allowed since you have insufficient permissions on 1 or more child documents.' + Acción no permitida. No tiene permisos suficientes para uno o más subnodos.' Edite su notificación para %0% @@ -499,16 +585,16 @@ However, Runway offers an easy foundation based on best practices to get you sta aceptar para publicar %0% y por lo tanto, hacer que su contenido esté disponible al público.

Puedes publicar esta página y todas sus subpáginas marcando publicar todos los hijos debajo. ]]>
- TRANSLATE ME: 'Add external link' - TRANSLATE ME: 'Add internal link' - TRANSLATE ME: 'Add' - TRANSLATE ME: 'Caption' - TRANSLATE ME: 'Internal page' - TRANSLATE ME: 'URL' - TRANSLATE ME: 'Move Down' - TRANSLATE ME: 'Move Up' - TRANSLATE ME: 'Open in new window' - TRANSLATE ME: 'Remove link' + Añadir un enlace externo + Añadir un enlace interno + Añadir + Título + Página interna + Enlace + Bajar + Subir + Abrir en una nueva ventana + Quitar el enlace Versión actual @@ -535,6 +621,8 @@ However, Runway offers an easy foundation based on best practices to get you sta Estadísticas Traducción Usuarios + Ayuda + Analytics Plantilla por defecto @@ -548,6 +636,12 @@ However, Runway offers an easy foundation based on best practices to get you sta Pestaña Nombre de la pestaña Pestañas + Tipo de Contenido Maestro activado + Este Tipo de Contenido usa + como Tipo de Contenido Maestro. Las pestañas para los Tipos de Contenido Maestros no se muestran y solo se pueden modificar desde el Tipo de Contenido Maestro + No existen propiedades para esta pestaña. Haga clic en el enlace "añadir nueva propiedad" para crear una nueva propiedad. + Tipo de documento Maestro + Crear template correspondiente Ordenación completa. @@ -621,6 +715,36 @@ However, Runway offers an easy foundation based on best practices to get you sta Guía rápida sobre las etiquetas de plantilla de Umbraco Plantilla + + Insertar control + Añade más filas + y añade tu primer contenido]]> + + Plantillas de Grid + Las plantillas son el área de trabajo para el editor de grids, normalmente sólo necesitas una o dos plantillas diferentes + Añadir plantilla de grid + Ajusta la plantilla configurando la anchura de las columnas y añadiendo más secciones + + Configuraciones de filas + Las filas son celdas predefinidas que se disponen horizontalmente + Añade una configuración de fila + Ajusta la fila configurando los anchos de cada celda y añadiendo más celdas + + Columnas + Número total de columnas en la plantilla del grid + + Configuración + Configura qué ajustes pueden cambiar los editores + + + Estilos + Configura qué estilos pueden cambiar los editores + + La configuración sólo se guardará si el json introducido es válido + + Permitir todos los controles de edición + Permitir todas las configuraciones de fila + Campo opcional Texto opcional @@ -661,7 +785,7 @@ However, Runway offers an easy foundation based on best practices to get you sta Tipos de datos Diccionario Paquetes instalados - TRANSLATE ME: 'Install skin' - TRANSLATE ME: 'Install starter kit' + Instalar skin + Instalar starter kit Idiomas Instalar paquete local Macros @@ -710,7 +834,7 @@ However, Runway offers an easy foundation based on best practices to get you sta Instalar desde repositorio Instalar pasarela Módulos pasarela - TRANSLATE ME: 'Scripting Files' + Ficheros de script Scripts Hojas de estilo Plantillas @@ -725,8 +849,10 @@ However, Runway offers an easy foundation based on best practices to get you sta Administrador Campo de categoria - TRANSLATE ME: 'Change Your Password' - TRANSLATE ME: 'You can change your password for accessing the Umbraco Back Office by filling out the form below and click the 'Change Password' button' + Cambiar contraseña + Change Your Password + Confirm new password + Puede cambiar su contraseña para acceder al 'back office' de Umbraco rellenando el siguiente formulario y haciendo clic en el botón 'Cambiar contraseña' Canal de contenido Campo descriptivo Deshabilitar usuario @@ -738,13 +864,16 @@ However, Runway offers an easy foundation based on best practices to get you sta Nodo de comienzo en la libreria de medios Secciones Deshabilitar acceso a Umbraco - Password - TRANSLATE ME: 'Your password has been changed!' - TRANSLATE ME: 'Please confirm the new password' - TRANSLATE ME: 'Enter your new password' - TRANSLATE ME: 'Your new password cannot be blank!' - TRANSLATE ME: 'There was a difference between the new password and the confirmed password. Please try again!' - TRANSLATE ME: 'The confirmed password doesn't match the new password!' + Contraseña + Reset password + Su contraseña ha sido cambiada + Por favor confirme su nueva contraseña + Introduzca su nueva contraseña + La nueva contraseña no puede estar vacía + Current password + Invalid current password + La nueva contraseña no coincide con la contraseña de confirmación. Por favor, vuela a intentarlo!' + La contraseña de confirmación no coincide con la nueva contraseña!' Reemplazar los permisos de los nodos hijo Estas modificando los permisos para las páginas: Selecciona las páginas para modificar sus permisos @@ -755,5 +884,8 @@ However, Runway offers an easy foundation based on best practices to get you sta Tipo de usuario Tipos de usuarios Redactor + Tu perfil + Tu historial reciente + La sesión caduca en -
\ No newline at end of file +
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml index 33c37d610b..af1c39345b 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml @@ -2,7 +2,7 @@ The Umbraco community - http://Umbraco.org/documentation/language-files + http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files Gérer les noms d'hôtes @@ -420,7 +420,7 @@

Pour poursuivre, éditez le fichier "web.config" (avec Visual Studio ou votre éditeur de texte favori), scrollez jusqu'en bas, ajoutez une "connection string" dans la ligne "umbracoDbDSN" et sauvegardez votre fichier.

Cliquez sur le bouton Réessayer lorsque cela est fait. -
+
Plus d'informations sur l'édition du fichier web.config ici.

]]> Contactez votre administrateur système si nécessaire. @@ -545,6 +545,7 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Joyeux samedi Connectez-vous ci-dessous La session a expiré + © 2001 - %0%
umbraco.com

]]>
Tableau de bord @@ -750,6 +751,8 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Ce type de contenu utilise en tant que type de contenu master, les onglets du type de contenu master ne sont pas affichés et peuvent seulement être modifiés dans le type de contenu master lui-même. Aucune propriétés définies dans cet onglet. Cliquez sur le lien "Ajouter une nouvelle propriété" en haut pour créer une nouvelle propriété. + Type de contenu parent + Créer le template correspondant Tri achevé. diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/he.xml b/src/Umbraco.Web.UI/umbraco/config/lang/he.xml index 937847ca62..81cf4b2ff5 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/he.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/he.xml @@ -1,8 +1,8 @@  - Umbraco Hebrew 2011 - http://Umbraco.org + The Umbraco community + http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files נהל שמות מתחם @@ -354,7 +354,7 @@

To proceed, please edit the "web.config" file (using Visual Studio or your favourite text editor), scroll to the bottom, add the connection string for your database in the key named "UmbracoDbDSN" and save the file.

Click the retry button when - done.
+ done.
More information on editing web.config here.

]]>
Please contact your ISP if necessary. @@ -469,7 +469,7 @@ To manage your website, simply open the Umbraco back office and start adding con יש לבצע חידוש פעילות על מנת לשמור על התוכן - © 2001 - %0%
Umbraco.org

]]>
+ © 2001 - %0%
umbraco.com

]]>
ברוכים הבאים לאומברקו, יש להזין שם משתמש וסיסמה בשדות למטה: diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/it.xml b/src/Umbraco.Web.UI/umbraco/config/lang/it.xml index f90a4bc415..975ce0bb6a 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/it.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/it.xml @@ -2,7 +2,7 @@ The Umbraco community - http://Umbraco.org/documentation/language-files + http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files Gestisci hostnames @@ -266,7 +266,7 @@ Trova Cartella Altezza - Aiuto + Guida Icona Importa @@ -343,7 +343,7 @@ Avanti per proseguire.]]> Database non trovato! Perfavore, controlla che le informazioni della stringa di connessione nel file "web.config" siano corrette.

-

Per procedere, edita il file "web.config" (utilizzando Visual Studio o l'editor di testo che preferisci), scorri in basso, aggiungi la stringa di connessione per il database chiamato "UmbracoDbDSN" e salva il file.

Clicca il tasto riprova quando hai finito.
Maggiori dettagli per la modifica del file web.config qui.

]]> +

Per procedere, edita il file "web.config" (utilizzando Visual Studio o l'editor di testo che preferisci), scorri in basso, aggiungi la stringa di connessione per il database chiamato "umbracoDbDSN" e salva il file.

Clicca il tasto riprova quando hai finito.
Maggiori dettagli per la modifica del file web.config qui.

]]>
Premi il tasto aggiorna per aggiornare il database ad Umbraco %0%

Non preoccuparti, il contenuto non verrà perso e tutto continuerà a funzionare dopo l'aggiornamento!

]]>
@@ -428,7 +428,7 @@ Per gestire il tuo sito web, è sufficiente aprire il back office di Umbraco e i Riconnetti adesso per salvare il tuo lavoro - © 2001 - %0%
Umbraco.org

]]>
+ © 2001 - %0%
umbraco.com

]]>
Benvenuti in Umbraco, digita il tuo username e la tua password nelle caselle seguenti: @@ -630,7 +630,7 @@ Per gestire il tuo sito web, è sufficiente aprire il back office di Umbraco e i Tab creata Tab eliminata Tab con id: %0% eliminata - Contenuto non pubblicato + Contenuto non pubblicare diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml index f7176eace1..cd0bfe03d7 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml @@ -1,8 +1,8 @@ - Umbraco - http://Umbraco.org + The Umbraco community + http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files ドメインの割り当て @@ -351,7 +351,7 @@

続行するには"web.config"ファイルを編集(Visual Studioないし使い慣れたテキストエディタで)し、下の方にスクロールし、"UmbracoDbDSN"という名前のキーでデータベースの接続文字列を追加して保存します。

再施行ボタンをクリックして - 続けます。
+ 続けます。
より詳細にはこちらの web.config を編集します。

]]>
必要ならISPに連絡するなどしてみてください。 @@ -466,7 +466,7 @@ Runwayをインストールして作られた新しいウェブサイトがど 作業を保存して今すぐ更新 - © 2001 - %0%
Umbraco.org

]]>
+ © 2001 - %0%
umbraco.com

]]>
Umbraco にようこそ。ユーザー名とパスワードを入力してください: diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml index e940f0da1a..b3f6a3a384 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml @@ -2,7 +2,7 @@ The Umbraco community - http://Umbraco.org/documentation/language-files + http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files 호스트명 관리 @@ -344,7 +344,7 @@ 데이터베이스를 찾을 수 없습니다. “web.config”파일의 "connection string"이 바르게 설정되었는지 확인하세요.

"web.config" 파일에 맨아래에 ,키네임을 "UmbracoDbDSN"로 하여 사용하시는 데이터베이스의 connection string 정보를 입력하시고 파일을 저장하세요.

- 완료 후재시도버튼을 누르세요.
+ 완료 후재시도버튼을 누르세요.
web.config의 더많은 정보는 여기에 있습니다.

]]>
필요하시다면 사용하시는 ISP쪽에 문의하시기 바랍니다.. @@ -447,7 +447,7 @@ TRANSLATE ME: 'Renew now to save your work' - © 2001 - %0%
Umbraco.org

]]>
+ © 2001 - %0%
umbraco.com

]]>
Umbraco 에 오신것을 환영합니다. 사용자명과 비밀번호를 입력하십시오: diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml index 79468980f8..05e9de462e 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml @@ -2,7 +2,7 @@ The Umbraco community - http://Umbraco.org/documentation/language-files + http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files Beheer domeinnamen @@ -421,7 +421,7 @@ Database configuratie installeren om de Umbraco %0% database te installeren]]> Volgende om door te gaan.]]> - De database kon niet gevonden worden! Gelieve na te kijken of de informatie in de "connection string" van het "web.config" bestand correct is.

Om door te gaan, gelieve het "web.config" bestand aan te passen (met behulp van Visual Studio of je favoriete tekstverwerker), scroll in het bestand naar beneden, voeg de connection string voor je database toe in de key met naam "UmbracoDbDSN" en sla het bestand op.

Klik op de knop opnieuw proberen als je hiermee klaar bent.
Meer informatie over het aanpassen van de web.config vind je hier.

]]>
+ De database kon niet gevonden worden! Gelieve na te kijken of de informatie in de "connection string" van het "web.config" bestand correct is.

Om door te gaan, gelieve het "web.config" bestand aan te passen (met behulp van Visual Studio of je favoriete tekstverwerker), scroll in het bestand naar beneden, voeg de connection string voor je database toe in de key met naam "umbracoDbDSN" en sla het bestand op.

Klik op de knop opnieuw proberen als je hiermee klaar bent.
Meer informatie over het aanpassen van de web.config vind je hier.

]]>
Gelieve contact op te nemen met je ISP indien nodig. Wanneer je installeert op een lokale computer of server, dan heb je waarschijnlijk informatie nodig van je systeembeheerder.]]> Klik de upgrade knop om je database te upgraden naar Umbraco %0%

Maak je geen zorgen - er zal geen inhoud worden gewist en alles blijft gewoon werken!

]]>
Klik Volgende om verder te gaan.]]> @@ -500,7 +500,7 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je log hieronder in Sessie is verlopen - © 2001 - %0%
Umbraco.org

]]>
+ © 2001 - %0%
umbraco.com

]]>
Welkom bij Umbraco, geef je gebruikersnaam en wachtwoord op in de onderstaande velden: @@ -788,6 +788,36 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Quick Guide voor Umbraco template tags Sjabloon + + Item toevoegen + Een rij aan de lay-out toevoegen + teken onderaan en voeg je eerste item toe]]> + + Grid lay-outs + Lay-outs zijn het globale werkgebied voor de grid editor. Je hebt meestal maar één of twee verschillende lay-outs nodig + Een grid layout toevoegen + De lay-out aanpassen door de kolombreedte aan te passen en extra kolommen toe te voegen + + Rijconfiguratie + Rijen zijn voorgedefinieerde cellen die horizontaal zijn gerangschikt + Een rijconfiguratie toevoegen + De rijconfiguratie aanpassen door de breedte van de cel in te stellen en extra cellen toe te voegen + + Kolommen + Het totaal aantal gecombineerde kolommen in de grid layout + + Instellingen + Configureren welke instellingen de editors kunnen aanpassen + + + Styles + Configureren welke stijlen de editors kunnen aanpassen + + De instellingen worden enkel bewaard indien de ingevoerde Json geldig is + + Alle editors toelaten + Alle rijconfiguraties toelaten + Alternatief veld Alternatieve tekst diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/no.xml b/src/Umbraco.Web.UI/umbraco/config/lang/no.xml index 1f5df0d3dd..f5d5fe9773 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/no.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/no.xml @@ -2,7 +2,7 @@ The Umbraco community - http://Umbraco.org/documentation/language-files + http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files Angi domene @@ -352,7 +352,7 @@ Databasekonfigurasjon Klikk <strong>installer</strong>-knappen for å installere Umbraco %0% databasen Umbraco %0% har nå blitt kopiert til din database. Trykk <strong>Neste</strong> for å fortsette. - <p>Databasen ble ikke funnet! Vennligst sjekk at informasjonen i "connection string" i "web.config"-filen er korrekt.</p><p>For å fortsette, vennligst rediger "web.config"-filen (bruk Visual Studio eller din favoritteditor), rull ned til bunnen, og legg til tilkoblingsstrengen for din database i nøkkelen "UmbracoDbDSN" og lagre filen.</p><p>Klikk <strong>prøv på nytt</strong> når du er ferdig.<br /> <a href="http://Umbraco.org/redir/installWebConfig" target="_blank">Mer informasjon om redigering av web.config her.</a></p> + <p>Databasen ble ikke funnet! Vennligst sjekk at informasjonen i "connection string" i "web.config"-filen er korrekt.</p><p>For å fortsette, vennligst rediger "web.config"-filen (bruk Visual Studio eller din favoritteditor), rull ned til bunnen, og legg til tilkoblingsstrengen for din database i nøkkelen "umbracoDbDSN" og lagre filen.</p><p>Klikk <strong>prøv på nytt</strong> når du er ferdig.<br /> <a href="http://our.umbraco.org/documentation/Using-Umbraco/Config-files/webconfig7" target="_blank">Mer informasjon om redigering av web.config her.</a></p> For å fullføre dette steget, må du vite en del informasjon om din database server ("tilkoblingsstreng").<br/> Vennligst kontakt din ISP om nødvendig. Hvis du installerer på en lokal maskin eller server, må du kanskje skaffe informasjonen fra din systemadministrator. <p> Trykk på knappen <strong>oppgrader</strong> for å oppgradere databasen din til Umbraco %0%</p> <p> Ikke vær urolig - intet innhold vil bli slettet og alt vil fortsette å virke etterpå! </p> Databasen din har blitt oppgradert til den siste utgaven, %0%.<br/>Trykk <strong>Neste</strong> for å fortsette. @@ -415,7 +415,7 @@ Forny innlogging for å lagre - <p style="text-align:right;">&copy; 2001 - %0% <br /><a href="http://Umbraco.org" style="text-decoration: none" target="_blank">Umbraco.org</a></p> + <p style="text-align:right;">&copy; 2001 - %0% <br /><a href="http://umbraco.com" style="text-decoration: none" target="_blank">umbraco.org</a></p> Velkommen til Umbraco, skriv inn ditt brukernavn og passord i feltene under: diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml b/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml index d156bf6bf8..1b59676ae7 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml @@ -2,7 +2,7 @@ The Umbraco community - http://Umbraco.org/documentation/language-files + http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files Zarządzanie hostami @@ -339,7 +339,7 @@ Możesz dodać dodatkowe języki w menu "Języki" po lewej stronie.]]>
Konfiguracja bazy danych instaluj aby zainstalować bazę danych Umbraco %0%]]> Dalej aby kontynuować.]]> - Nie odnaleziono bazy danych! Sprawdź czy informacje w sekcji "connection string" w pliku "web.config" są prawidłowe.

Aby kontynuować, dokonaj edycji pliku "web.config" (używając Visual Studio lub dowolnego edytora tekstu), przemieść kursor na koniec pliku, dodaj parametry połączenia do Twojej bazy danych w kluczu o nazwie "UmbracoDbDSN" i zapisz plik.

Kliknij ponów próbę kiedy skończysz.
Tu znajdziesz więcej informacji na temat edycji pliku "web.config".

]]>
+ Nie odnaleziono bazy danych! Sprawdź czy informacje w sekcji "connection string" w pliku "web.config" są prawidłowe.

Aby kontynuować, dokonaj edycji pliku "web.config" (używając Visual Studio lub dowolnego edytora tekstu), przemieść kursor na koniec pliku, dodaj parametry połączenia do Twojej bazy danych w kluczu o nazwie "umbracoDbDSN" i zapisz plik.

Kliknij ponów próbę kiedy skończysz.
Tu znajdziesz więcej informacji na temat edycji pliku "web.config".

]]>
Skontaktuj się z Twoim dostawą usług internetowych jeśli zajdzie taka potrzeba. W przypadku instalacji na lokalnej maszynie lub serwerze możesz potrzebować pomocy administratora.]]> Naciśnij przycisk aktualizuj by zaktualizować swoją bazę danych do Umbraco %0%

Bez obaw - żadne dane nie zostaną usunięte i wszystko będzie działać jak należy!

]]>
Naciśnij przycisk Dalej aby kontynuować.]]> @@ -402,7 +402,7 @@ Możesz dodać dodatkowe języki w menu "Języki" po lewej stronie.]]>
TRANSLATE ME: 'Renew now to save your work' - © 2001 - %0%
Umbraco.org

]]>
+ © 2001 - %0%
umbraco.com

]]>
Witamy w Umbraco. Wprowadź swój login oraz hasło w polach poniżej: diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml b/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml index 0c90014a15..a713ba8d5b 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml @@ -1,8 +1,8 @@  - Carlos Casalicchio - http://www.zueuz.net + The Umbraco community + http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files Gerenciar hostnames @@ -340,7 +340,7 @@ Próximo para prosseguir.]]> Banco de dados não encontrado! Favor checar se a informação no "connection string" do "web.config" esteja correta.

Para prosseguir, favor editar o arquivo "web.config" (usando Visual Studio ou seu editor de texto favorito), role até embaixo, adicione a connection string para seu banco de dados com a chave de nome "UmbracoDbDSN" e salve o arquivo

-

Clique o botão tentar novamente quando terminar.
+

Clique o botão tentar novamente quando terminar.
Mais informações em como editar o web.config aqui.

]]>
Favor contatar seu provedor de internet ou hospedagem web se necessário. Se você estiver instalando em uma máquina ou servidor local é possível que você precise dessas informações por um administrador de sistema.]]> @@ -440,7 +440,7 @@ Pressione "próximo" para iniciar o assistente.]]> Renovar agora para salvar seu trabalho - © 2001 - %0%
Umbraco.org

]]>
+ © 2001 - %0%
umbraco.com

]]>
Bem vindo(a) à Umbraco, digite seu nome de usuário e senha nas caixas abaixo: diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml index 5de4144f79..41dd15fbf2 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml @@ -1,8 +1,8 @@ - Alexander Bryukhov, Roman Voropaev (Unico Design Ltd., Novosibirsk, RU) - http://unicodsgn.com + The Umbraco community + http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files Языки и домены @@ -410,7 +410,7 @@

Для настройки откройте файл "web.config" с помошью любого текстового редактора и добавьте нужную информацию в строку подключения (параметр "UmbracoDbDSN"), затем сохраните файл.

Нажмите кнопку "Повторить" когда все будет готово
- Более подробно о внесении изменений в файл "web.config" расскзано здесь.

]]> @@ -525,7 +525,7 @@ Обновите сейчас, чтобы сохранить сделанные изменения - © 2001 - %0%
umbraco.org

]]>
+ © 2001 - %0%
umbraco.com

]]>
Сегодня ж выходной Понедельник — день тяжелый Уже вторник diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml index 44c2b6a757..820be8ac1e 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml @@ -1,8 +1,8 @@  - Kalle Ekstrand, Toxic Interactive Solutions AB - http://www.toxic.se + The Umbraco community + http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files Hantera domännamn @@ -410,7 +410,7 @@ Databaskonfiguration installera]]> Nästa för att fortsätta.]]> - Databasen kunde inte hittas! Kontrollera att informationen i databasanslutnings-inställningarna i filen "web.config" är rätt.

För att fortsätta måste du redigera filen "web.config" (du kan använda Visual Studio eller din favorit text-redigerare), bläddra till slutet, lägg till databasanslutnings-inställningarna för din databas i fältet som heter "UmbracoDbDSN" och spara filen.

Klicka på Försök igen knappen när du är klar.
> Mer information om att redigera web.config hittar du här.

]]>
+ Databasen kunde inte hittas! Kontrollera att informationen i databasanslutnings-inställningarna i filen "web.config" är rätt.

För att fortsätta måste du redigera filen "web.config" (du kan använda Visual Studio eller din favorit text-redigerare), bläddra till slutet, lägg till databasanslutnings-inställningarna för din databas i fältet som heter "umbracoDbDSN" och spara filen.

Klicka på Försök igen knappen när du är klar.
> Mer information om att redigera web.config hittar du här.

]]>
Eventuellt kan du behöva kontakta ditt webb-hotell. Om du installerar på en lokal maskin eller server kan du få informationen från din systemadministratör.]]> Tryck Uppgradera knappen för att uppgradera din databas till Umbraco {0}

Du behöver inte vara orolig. Inget innehåll kommer att raderas och efteråt kommer allt att fungera som vanligt!

]]>
Tryck Nästa för att fortsätta.]]> @@ -474,7 +474,7 @@ Förnya nu för att spara ditt arbete - © 2001 - {0}
Umbraco.org

]]>
+ © 2001 - {0}
umbraco.com

]]>
Happy super Sunday Happy manic Monday Happy tremendous Tuesday diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml b/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml index f2f50916b4..a45a626366 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml @@ -2,7 +2,7 @@ 孙柱梁 - + http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files 管理主机名 @@ -384,7 +384,7 @@ 数据库未找到!请检查数据库连接串设置。

您可以自行编辑“web.config”文件,键名为 “UmbracoDbDSN”

- 当自行编辑后,单击重试按钮
。 + 当自行编辑后,单击重试按钮
。 如何编辑web.config

]]>
diff --git a/src/Umbraco.Web.UI/umbraco/create/nodeType.ascx b/src/Umbraco.Web.UI/umbraco/create/nodeType.ascx index bb498c0f0f..4b11f4d990 100644 --- a/src/Umbraco.Web.UI/umbraco/create/nodeType.ascx +++ b/src/Umbraco.Web.UI/umbraco/create/nodeType.ascx @@ -2,15 +2,17 @@ <%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> - - - - + * + + + + + diff --git a/src/Umbraco.Web.UI/umbraco/developer/Macros/editMacro.aspx b/src/Umbraco.Web.UI/umbraco/developer/Macros/editMacro.aspx index a746a4f9e3..53bf2969aa 100644 --- a/src/Umbraco.Web.UI/umbraco/developer/Macros/editMacro.aspx +++ b/src/Umbraco.Web.UI/umbraco/developer/Macros/editMacro.aspx @@ -93,11 +93,11 @@ - + - + diff --git a/src/Umbraco.Web.UI/umbraco/developer/Packages/proxy.htm b/src/Umbraco.Web.UI/umbraco/developer/Packages/proxy.htm index b65ea2d674..8a3de035bb 100644 --- a/src/Umbraco.Web.UI/umbraco/developer/Packages/proxy.htm +++ b/src/Umbraco.Web.UI/umbraco/developer/Packages/proxy.htm @@ -7,15 +7,16 @@