Merge remote-tracking branch 'upstream/7.0.2' into 7.0.2

This commit is contained in:
Robert Foster
2014-03-14 13:45:40 +11:00
185 changed files with 4253 additions and 2928 deletions
+6 -2
View File
@@ -1,7 +1,11 @@
Umbraco CMS
===========
##Note: Building version 7 from source##
If you're interested in using the source code and building version 7, make sure to read the [Belle ReadMe file](src/Umbraco.Web.UI.Client/README.md). Note that you can always [download a nightly build](http://nightly.umbraco.org/umbraco%207.0.0/) so you don't have to build the code yourself.
Umbraco is a free open source Content Management System built on the ASP.NET platform.
## Building Umbraco from source ##
The easiest way to get started is to run `build/build.bat` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `grunt dev` in `src\Umbraco.Web.UI.Client`.
If you're interested in making changes to Belle make sure to read the [Belle ReadMe file](src/Umbraco.Web.UI.Client/README.md). Note that you can always [download a nightly build](http://nightly.umbraco.org/umbraco%207.0.0/) so you don't have to build the code yourself.
## Watch a five minute introduction video ##
+9
View File
@@ -4,11 +4,18 @@ SET comment=
SET version=%release%
IF [%comment%] EQU [] (SET version=%release%) ELSE (SET version=%release%-%comment%)
ECHO Building Umbraco %version%
ReplaceIISExpressPortNumber.exe ..\src\Umbraco.Web.UI\Umbraco.Web.UI.csproj %release%
ECHO Installing the Microsoft.Bcl.Build package before anything else, otherwise you'd have to run build.cmd twice
SET nuGetFolder=%CD%\..\src\packages\
..\src\.nuget\NuGet.exe install ..\src\Umbraco.Web.UI\packages.config -OutputDirectory %nuGetFolder%
ECHO Performing MSBuild and producing Umbraco binaries zip files
%windir%\Microsoft.NET\Framework\v4.0.30319\msbuild.exe "Build.proj" /p:BUILD_RELEASE=%release% /p:BUILD_COMMENT=%comment%
ECHO Adding dummy files to include in the NuGet package so that empty folders actually get created
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_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
@@ -20,9 +27,11 @@ echo This file is only here so that the containing folder will be included in th
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
ECHO Adding Web.config transform files to the NuGet package
ren .\_BuildOutput\WebApp\MacroScripts\Web.config Web.config.transform
ren .\_BuildOutput\WebApp\Views\Web.config Web.config.transform
ECHO Packing the NuGet release files
..\src\.nuget\NuGet.exe pack NuSpecs\UmbracoCms.Core.nuspec -Version %version%
..\src\.nuget\NuGet.exe pack NuSpecs\UmbracoCms.nuspec -Version %version%
+1 -1
View File
@@ -267,7 +267,7 @@
<Message Text="Compiling web project to build\$(BuildFolder)" Importance="high" />
<!-- For UseWPP_CopyWebApplication=True see http://stackoverflow.com/questions/1983575/copywebapplication-with-web-config-transformations -->
<!-- Build the Umbraco.Web.UI project -->
<MSBuild Projects="..\src\Umbraco.Web.UI\Umbraco.Web.UI.csproj" Properties="WarningLevel=0;Configuration=$(BuildConfiguration);UseWPP_CopyWebApplication=True;PipelineDependsOnBuild=False;OutDir=$(SolutionBinFolderAbsolutePath);WebProjectOutputDir=$(WebAppFolderAbsolutePath)" Targets="Clean;Build;" BuildInParallel="False" ToolsVersion="4.0" UnloadProjectsOnCompletion="False">
<MSBuild Projects="..\src\Umbraco.Web.UI\Umbraco.Web.UI.csproj" Properties="WarningLevel=0;Configuration=$(BuildConfiguration);UseWPP_CopyWebApplication=True;PipelineDependsOnBuild=False;OutDir=$(SolutionBinFolderAbsolutePath);WebProjectOutputDir=$(WebAppFolderAbsolutePath)" Targets="Clean;Rebuild;" BuildInParallel="False" ToolsVersion="4.0" UnloadProjectsOnCompletion="False">
</MSBuild>
<!-- DONE -->
+32
View File
@@ -0,0 +1,32 @@
@ECHO OFF
ECHO Installing Npm NuGet Package
SET nuGetFolder=%CD%\..\src\packages\
ECHO Configured packages folder: %nuGetFolder%
ECHO Current folder: %CD%
%CD%\..\src\.nuget\NuGet.exe install Npm.js -OutputDirectory %nuGetFolder%
for /f "delims=" %%A in ('dir %nuGetFolder%node.js.* /b') do set "nodePath=%nuGetFolder%%%A\"
for /f "delims=" %%A in ('dir %nuGetFolder%npm.js.* /b') do set "npmPath=%nuGetFolder%%%A\tools\"
ECHO Temporarily adding Npm and Node to path
SET oldPath=%PATH%
path=%npmPath%;%nodePath%;%PATH%
SET buildFolder=%CD%
ECHO Change directory to %CD%\..\src\Umbraco.Web.UI.Client\
CD %CD%\..\src\Umbraco.Web.UI.Client\
ECHO Do npm install and the grunt build of Belle
call npm install
call npm install -g grunt-cli
call grunt build
ECHO Reset path to what it was before
path=%oldPath%
ECHO Move back to the build folder
CD %buildFolder%
+2 -1
View File
@@ -26,7 +26,7 @@
<dependency id="SharpZipLib" version="[0.86.0, 1.0.0)" />
<dependency id="MySql.Data" version="[6.6.5]" />
<dependency id="xmlrpcnet" version="[2.5.0, 3.0.0)" />
<dependency id="ClientDependency" version="[1.7.0, 2.0.0)" />
<!--<dependency id="ClientDependency" version="[1.7.0, 2.0.0)" />-->
<dependency id="ClientDependency-Mvc" version="[1.7.0, 2.0.0)" />
<dependency id="AutoMapper" version="[3.0.0, 4.0.0)" />
<dependency id="Newtonsoft.Json" version="[4.5.11, 5.0.0)" />
@@ -35,6 +35,7 @@
<files>
<file src="..\_BuildOutput\WebApp\bin\businesslogic.dll" target="lib\businesslogic.dll" />
<file src="..\_BuildOutput\WebApp\bin\businesslogic.xml" target="lib\businesslogic.xml" />
<file src="..\_BuildOutput\WebApp\bin\ClientDependency.Core.dll" target="lib\ClientDependency.Core.dll" />
<file src="..\_BuildOutput\WebApp\bin\cms.dll" target="lib\cms.dll" />
<file src="..\_BuildOutput\WebApp\bin\cms.xml" target="lib\cms.xml" />
<file src="..\_BuildOutput\WebApp\bin\controls.dll" target="lib\controls.dll" />
Binary file not shown.
+12 -27
View File
@@ -18,13 +18,13 @@
<ItemGroup Condition=" '$(PackageSources)' == '' ">
<!-- Package sources used to restore packages. By default, registered sources under %APPDATA%\NuGet\NuGet.Config will be used -->
<!-- The official NuGet package source (https://nuget.org/api/v2/) will be excluded if package sources are specified and it does not appear in the list -->
<!-- The official NuGet package source (https://www.nuget.org/api/v2/) will be excluded if package sources are specified and it does not appear in the list -->
<!--
<PackageSource Include="https://nuget.org/api/v2/" />
<PackageSource Include="https://www.nuget.org/api/v2/" />
<PackageSource Include="https://my-nuget-source/nuget/" />
-->
<PackageSource Include="https://nuget.org/api/v2/" />
<PackageSource Include="http://www.myget.org/F/umbracocore/" />
<PackageSource Include="http://www.myget.org/F/umbracocore/" />
</ItemGroup>
<PropertyGroup Condition=" '$(OS)' == 'Windows_NT'">
@@ -41,7 +41,7 @@
<PropertyGroup>
<!-- NuGet command -->
<NuGetExePath Condition=" '$(NuGetExePath)' == '' ">$(NuGetToolsPath)\nuget.exe</NuGetExePath>
<NuGetExePath Condition=" '$(NuGetExePath)' == '' ">$(NuGetToolsPath)\NuGet.exe</NuGetExePath>
<PackageSources Condition=" $(PackageSources) == '' ">@(PackageSource)</PackageSources>
<NuGetCommand Condition=" '$(OS)' == 'Windows_NT'">"$(NuGetExePath)"</NuGetCommand>
@@ -50,9 +50,14 @@
<PackageOutputDir Condition="$(PackageOutputDir) == ''">$(TargetDir.Trim('\\'))</PackageOutputDir>
<RequireConsentSwitch Condition=" $(RequireRestoreConsent) == 'true' ">-RequireConsent</RequireConsentSwitch>
<NonInteractiveSwitch Condition=" '$(VisualStudioVersion)' != '' AND '$(OS)' == 'Windows_NT' ">-NonInteractive</NonInteractiveSwitch>
<PaddedSolutionDir Condition=" '$(OS)' == 'Windows_NT'">"$(SolutionDir) "</PaddedSolutionDir>
<PaddedSolutionDir Condition=" '$(OS)' != 'Windows_NT' ">"$(SolutionDir)"</PaddedSolutionDir>
<!-- Commands -->
<RestoreCommand>$(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(RequireConsentSwitch) -solutionDir "$(SolutionDir) "</RestoreCommand>
<BuildCommand>$(NuGetCommand) pack "$(ProjectPath)" -p Configuration=$(Configuration) -o "$(PackageOutputDir)" -symbols</BuildCommand>
<RestoreCommand>$(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir)</RestoreCommand>
<BuildCommand>$(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols</BuildCommand>
<!-- We need to ensure packages are restored prior to assembly resolve -->
<BuildDependsOn Condition="$(RestorePackages) == 'true'">
@@ -70,7 +75,6 @@
<Target Name="CheckPrerequisites">
<!-- Raise an error if we're unable to locate nuget.exe -->
<Error Condition="'$(DownloadNuGetExe)' != 'true' AND !Exists('$(NuGetExePath)')" Text="Unable to locate '$(NuGetExePath)'" />
<SetEnvironmentVariable EnvKey="VisualStudioVersion" EnvValue="$(VisualStudioVersion)" Condition=" '$(VisualStudioVersion)' != '' AND '$(OS)' == 'Windows_NT' " />
<!--
Take advantage of MsBuild's build dependency tracking to make sure that we only ever download nuget.exe once.
This effectively acts as a lock that makes sure that the download operation will only happen once and all
@@ -119,7 +123,7 @@
Log.LogMessage("Downloading latest version of NuGet.exe...");
WebClient webClient = new WebClient();
webClient.DownloadFile("https://nuget.org/nuget.exe", OutputFilename);
webClient.DownloadFile("https://www.nuget.org/nuget.exe", OutputFilename);
return true;
}
@@ -131,23 +135,4 @@
</Code>
</Task>
</UsingTask>
<UsingTask TaskName="SetEnvironmentVariable" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<EnvKey ParameterType="System.String" Required="true" />
<EnvValue ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Using Namespace="System" />
<Code Type="Fragment" Language="cs">
<![CDATA[
try {
Environment.SetEnvironmentVariable(EnvKey, EnvValue, System.EnvironmentVariableTarget.Process);
}
catch {
}
]]>
</Code>
</Task>
</UsingTask>
</Project>
@@ -10,6 +10,8 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
bool RemoveDoubleDashes { get; }
bool ConvertUrlsToAscii { get; }
IEnumerable<IChar> CharCollection { get; }
}
}
@@ -121,6 +121,11 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
get { return UrlReplacing.RemoveDoubleDashes; }
}
bool IRequestHandlerSection.ConvertUrlsToAscii
{
get { return UrlReplacing.ConvertUrlsToAscii; }
}
IEnumerable<IChar> IRequestHandlerSection.CharCollection
{
get { return UrlReplacing.CharCollection; }
@@ -11,6 +11,12 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
get { return (bool) base["removeDoubleDashes"]; }
}
[ConfigurationProperty("toAscii", DefaultValue = false)]
internal bool ConvertUrlsToAscii
{
get { return (bool)base["toAscii"]; }
}
[ConfigurationCollection(typeof(CharCollection), AddItemName = "char")]
[ConfigurationProperty("", IsDefaultCollection = true)]
internal CharCollection CharCollection
@@ -260,6 +260,8 @@ namespace Umbraco.Core
/// MemberType alias for default member type.
/// </summary>
public const string Member = "Member";
public const string SystemDefaultProtectType = "_umbracoSystemDefaultProtectType";
}
/// <summary>
-17
View File
@@ -23,22 +23,5 @@
public const int RecycleBinMedia = -21;
}
/// <summary>
/// Defines the identifiers for Umbraco system nodes.
/// </summary>
public static class Web
{
/// <summary>
/// The preview cookie name
/// </summary>
public const string PreviewCookieName = "UMB_PREVIEW";
/// <summary>
/// The auth cookie name
/// </summary>
public const string AuthCookieName = "UMB_UCONTEXT";
}
}
}
+22
View File
@@ -0,0 +1,22 @@
namespace Umbraco.Core
{
public static partial class Constants
{
/// <summary>
/// Defines the identifiers for Umbraco system nodes.
/// </summary>
public static class Web
{
/// <summary>
/// The preview cookie name
/// </summary>
public const string PreviewCookieName = "UMB_PREVIEW";
/// <summary>
/// The auth cookie name
/// </summary>
public const string AuthCookieName = "UMB_UCONTEXT";
}
}
}
+6 -6
View File
@@ -231,7 +231,7 @@ namespace Umbraco.Core
/// <returns>The contains key ignore case.</returns>
public static bool ContainsKeyIgnoreCase<TValue>(this IDictionary<string, TValue> dictionary, string key)
{
return dictionary.Keys.Any(i => i.Equals(key, StringComparison.CurrentCultureIgnoreCase));
return dictionary.Keys.InvariantContains(key);
}
/// <summary>
@@ -257,9 +257,9 @@ namespace Umbraco.Core
/// <param name="key">The key.</param>
/// <typeparam name="TValue">The type</typeparam>
/// <returns>The entry</returns>
public static TValue GetEntryIgnoreCase<TValue>(this IDictionary<string, TValue> dictionary, string key)
public static TValue GetValueIgnoreCase<TValue>(this IDictionary<string, TValue> dictionary, string key)
{
return dictionary.GetEntryIgnoreCase(key, default(TValue));
return dictionary.GetValueIgnoreCase(key, default(TValue));
}
/// <summary>The get entry ignore case.</summary>
@@ -268,11 +268,11 @@ namespace Umbraco.Core
/// <param name="defaultValue">The default value.</param>
/// <typeparam name="TValue">The type</typeparam>
/// <returns>The entry</returns>
public static TValue GetEntryIgnoreCase<TValue>(this IDictionary<string, TValue> dictionary, string key, TValue defaultValue)
public static TValue GetValueIgnoreCase<TValue>(this IDictionary<string, TValue> dictionary, string key, TValue defaultValue)
{
key = dictionary.Keys.Where(i => i.Equals(key, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
key = dictionary.Keys.FirstOrDefault(i => i.InvariantEquals(key));
return !key.IsNullOrWhiteSpace()
return key.IsNullOrWhiteSpace() == false
? dictionary[key]
: defaultValue;
}
@@ -0,0 +1,52 @@
using System.Collections.Generic;
using System.Xml.Linq;
namespace Umbraco.Core.Events
{
public class ExportEventArgs<TEntity> : CancellableObjectEventArgs<IEnumerable<TEntity>>
{
/// <summary>
/// Constructor accepting a single entity instance
/// </summary>
/// <param name="eventObject"></param>
/// <param name="xml"></param>
/// <param name="canCancel"></param>
public ExportEventArgs(TEntity eventObject, XElement xml, bool canCancel)
: base(new List<TEntity> { eventObject }, canCancel)
{
Xml = xml;
}
/// <summary>
/// Constructor accepting a single entity instance
/// and cancellable by default
/// </summary>
/// <param name="eventObject"></param>
/// <param name="elementName"></param>
public ExportEventArgs(TEntity eventObject, string elementName) : base(new List<TEntity> {eventObject}, true)
{
Xml = new XElement(elementName);
}
protected ExportEventArgs(IEnumerable<TEntity> eventObject, bool canCancel) : base(eventObject, canCancel)
{
}
protected ExportEventArgs(IEnumerable<TEntity> eventObject) : base(eventObject)
{
}
/// <summary>
/// Returns all entities that were exported during the operation
/// </summary>
public IEnumerable<TEntity> ExportedEntities
{
get { return EventObject; }
}
/// <summary>
/// Returns the xml relating to the export event
/// </summary>
public XElement Xml { get; private set; }
}
}
@@ -0,0 +1,50 @@
using System.Collections.Generic;
using System.Xml.Linq;
namespace Umbraco.Core.Events
{
public class ImportEventArgs<TEntity> : CancellableObjectEventArgs<IEnumerable<TEntity>>
{
/// <summary>
/// Constructor accepting an XElement with the xml being imported
/// </summary>
/// <param name="xml"></param>
public ImportEventArgs(XElement xml) : base(new List<TEntity>(), true)
{
Xml = xml;
}
/// <summary>
/// Constructor accepting a list of entities and an XElement with the imported xml
/// </summary>
/// <param name="eventObject"></param>
/// <param name="xml"></param>
/// <param name="canCancel"></param>
public ImportEventArgs(IEnumerable<TEntity> eventObject, XElement xml, bool canCancel)
: base(eventObject, canCancel)
{
Xml = xml;
}
protected ImportEventArgs(IEnumerable<TEntity> eventObject, bool canCancel) : base(eventObject, canCancel)
{
}
protected ImportEventArgs(IEnumerable<TEntity> eventObject) : base(eventObject)
{
}
/// <summary>
/// Returns all entities that were imported during the operation
/// </summary>
public IEnumerable<TEntity> ImportedEntities
{
get { return EventObject; }
}
/// <summary>
/// Returns the xml relating to the import event
/// </summary>
public XElement Xml { get; private set; }
}
}
-3
View File
@@ -49,8 +49,5 @@ namespace Umbraco.Core.Events
{
get { return EventObject; }
}
}
}
+3 -3
View File
@@ -12,7 +12,7 @@ namespace Umbraco.Core.Macros
internal class MacroTagParser
{
private static readonly Regex MacroRteContent = new Regex(@"(<!--\s*?)(<\?UMBRACO_MACRO.*?/>)(\s*?-->)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
private static readonly Regex MacroPersistedFormat = new Regex(@"(<\?UMBRACO_MACRO macroAlias=[""'](\w+?)[""'].+?)(?:/>|>.*?</\?UMBRACO_MACRO>)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
private static readonly Regex MacroPersistedFormat = new Regex(@"(<\?UMBRACO_MACRO macroAlias=[""']([\w\.]+?)[""'].+?)(?:/>|>.*?</\?UMBRACO_MACRO>)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
/// <summary>
/// This formats the persisted string to something useful for the rte so that the macro renders properly since we
@@ -39,8 +39,8 @@ namespace Umbraco.Core.Macros
//<div class="umb-macro-holder myMacro mceNonEditable">
var alias = match.Groups[2].Value;
var sb = new StringBuilder("<div class=\"umb-macro-holder ");
sb.Append(alias);
sb.Append(" mceNonEditable\"");
//sb.Append(alias.ToSafeAlias());
sb.Append("mceNonEditable\"");
foreach (var htmlAttribute in htmlAttributes)
{
sb.Append(" ");
+4 -4
View File
@@ -618,7 +618,7 @@ namespace Umbraco.Core.Models
/// <returns>Xml representation of the passed in <see cref="IContent"/></returns>
internal static XElement ToDeepXml(this IContent content)
{
return ApplicationContext.Current.Services.PackagingService.Export(content, true);
return ApplicationContext.Current.Services.PackagingService.Export(content, true, raiseEvents: false);
}
/// <summary>
@@ -628,7 +628,7 @@ namespace Umbraco.Core.Models
/// <returns>Xml representation of the passed in <see cref="IContent"/></returns>
public static XElement ToXml(this IContent content)
{
return ApplicationContext.Current.Services.PackagingService.Export(content);
return ApplicationContext.Current.Services.PackagingService.Export(content, raiseEvents: false);
}
/// <summary>
@@ -638,7 +638,7 @@ namespace Umbraco.Core.Models
/// <returns>Xml representation of the passed in <see cref="IContent"/></returns>
public static XElement ToXml(this IMedia media)
{
return ApplicationContext.Current.Services.PackagingService.Export(media);
return ApplicationContext.Current.Services.PackagingService.Export(media, raiseEvents: false);
}
/// <summary>
@@ -648,7 +648,7 @@ namespace Umbraco.Core.Models
/// <returns>Xml representation of the passed in <see cref="IMedia"/></returns>
internal static XElement ToDeepXml(this IMedia media)
{
return ApplicationContext.Current.Services.PackagingService.Export(media, true);
return ApplicationContext.Current.Services.PackagingService.Export(media, true, raiseEvents: false);
}
/// <summary>
+5 -5
View File
@@ -107,7 +107,7 @@ namespace Umbraco.Core.Models
private bool _dontRender;
private string _scriptFile;
private string _scriptAssembly;
private string _python;
private string _scriptPath;
private string _xslt;
private readonly MacroPropertyCollection _properties;
private readonly List<string> _addedProperties;
@@ -360,14 +360,14 @@ namespace Umbraco.Core.Models
[DataMember]
public string ScriptPath
{
get { return _python; }
get { return _scriptPath; }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_python = value;
return _python;
}, _python, ScriptPathSelector);
_scriptPath = value;
return _scriptPath;
}, _scriptPath, ScriptPathSelector);
}
}
-1
View File
@@ -155,7 +155,6 @@ namespace Umbraco.Core.Models
/// </remarks>
[DataMember]
public string EditorAlias
{
get { return _editorAlias; }
set
+24
View File
@@ -15,6 +15,7 @@ namespace Umbraco.Core.Models
{
//Dictionary is divided into string: PropertyTypeAlias, Tuple: MemberCanEdit, VisibleOnProfile, PropertyTypeId
private IDictionary<string, Tuple<bool, bool, int>> _memberTypePropertyTypes;
private string _alias;
public MemberType(int parentId) : base(parentId)
{
@@ -27,6 +28,29 @@ namespace Umbraco.Core.Models
}
private static readonly PropertyInfo MemberTypePropertyTypesSelector = ExpressionHelper.GetPropertyInfo<MemberType, IDictionary<string, Tuple<bool, bool, int>>>(x => x.MemberTypePropertyTypes);
private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo<MemberType, string>(x => x.Alias);
/// <summary>
/// The Alias of the ContentType
/// </summary>
[DataMember]
public override string Alias
{
get { return _alias; }
set
{
//NOTE: WE are overriding this because we don't want to do a ToSafeAlias when the alias is the special case of
// "_umbracoSystemDefaultProtectType" which is used internally, currently there is an issue with the safe alias as it strips
// leading underscores which we don't want in this case.
// see : http://issues.umbraco.org/issue/U4-3968
SetPropertyValueAndDetectChanges(o =>
{
_alias = value == "_umbracoSystemDefaultProtectType" ? value : value.ToSafeAlias();
return _alias;
}, _alias, AliasSelector);
}
}
/// <summary>
/// Gets or Sets a Dictionary of Tuples (MemberCanEdit, VisibleOnProfile, PropertyTypeId) by the PropertyTypes' alias.
@@ -80,11 +80,8 @@ namespace Umbraco.Core.Models.PublishedContent
private void InitializeConverters()
{
var converters = PropertyValueConvertersResolver.Current.Converters.ToArray();
var defaultConverters = converters
.Where(x => x.GetType().GetCustomAttribute<DefaultPropertyValueConverterAttribute>(false) != null)
.ToArray();
var converters = PropertyValueConvertersResolver.Current.Converters.ToArray();
var defaultConvertersWithAttributes = PropertyValueConvertersResolver.Current.DefaultConverters;
_converter = null;
@@ -98,8 +95,14 @@ namespace Umbraco.Core.Models.PublishedContent
else if (foundConverters.Length > 1)
{
//more than one was found, we need to first figure out if one of these is an Umbraco default value type converter
var nonDefault = foundConverters.Except(defaultConverters).ToArray();
if (nonDefault.Length > 1)
//get the non-default and see if we have one
var nonDefault = foundConverters.Except(defaultConvertersWithAttributes.Select(x => x.Item1)).ToArray();
if (nonDefault.Length == 1)
{
//there's only 1 custom converter registered that so use it
_converter = nonDefault[0];
}
else if (nonDefault.Length > 1)
{
//this is not allowed, there cannot be more than 1 custom converter
throw new InvalidOperationException(
@@ -109,9 +112,31 @@ namespace Umbraco.Core.Models.PublishedContent
ContentType.Alias, PropertyTypeAlias,
nonDefault[1].GetType().FullName, nonDefault[0].GetType().FullName));
}
else
{
//we need to remove any converters that have been shadowed by another converter
var foundDefaultConvertersWithAttributes = defaultConvertersWithAttributes.Where(x => foundConverters.Contains(x.Item1));
var shadowedTypes = foundDefaultConvertersWithAttributes.SelectMany(x => x.Item2.DefaultConvertersToShadow);
var shadowedDefaultConverters = foundConverters.Where(x => shadowedTypes.Contains(x.GetType()));
var nonShadowedDefaultConverters = foundConverters.Except(shadowedDefaultConverters).ToArray();
//there's only 1 custom converter registered that so use it
_converter = nonDefault[0];
if (nonShadowedDefaultConverters.Length == 1)
{
//assign to the single default converter
_converter = nonShadowedDefaultConverters[0];
}
else if (nonShadowedDefaultConverters.Length > 1)
{
//this is not allowed, there cannot be more than 1 custom converter
throw new InvalidOperationException(
string.Format("Type '{2}' cannot be an IPropertyValueConverter"
+ " for property '{1}' of content type '{0}' because type '{3}' has already been detected as a converter"
+ " for that property, and only one converter can exist for a property.",
ContentType.Alias, PropertyTypeAlias,
nonShadowedDefaultConverters[1].GetType().FullName, nonShadowedDefaultConverters[0].GetType().FullName));
}
}
}
// get the cache levels, quietely fixing the inconsistencies (no need to throw, really)
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.UI.WebControls;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
internal static class UmbracoEntityExtensions
{
public static object GetAdditionalDataValueIgnoreCase(this IUmbracoEntity entity, string key, object defaultVal)
{
if (entity.AdditionalData.ContainsKeyIgnoreCase(key) == false) return defaultVal;
return entity.AdditionalData.GetValueIgnoreCase(key, defaultVal);
}
public static bool IsContainer(this IUmbracoEntity entity)
{
if (entity.AdditionalData.ContainsKeyIgnoreCase("IsContainer") == false) return false;
var val = entity.AdditionalData.GetValueIgnoreCase("IsContainer", null);
if (val is bool && (bool) val)
{
return true;
}
return false;
}
}
}
@@ -151,6 +151,21 @@ namespace Umbraco.Core.Persistence.Caching
_keyTracker.Remove(key);
}
public void Delete(Type type, int entityId)
{
var key = GetCompositeId(type, entityId);
if (_memoryCache != null)
{
_memoryCache.Remove(key);
}
else
{
HttpRuntime.Cache.Remove(key);
}
_keyTracker.Remove(key);
}
/// <summary>
/// Clear cache by type
/// </summary>
@@ -521,6 +521,10 @@ namespace Umbraco.Core.Persistence.Repositories
totalRecords = Convert.ToInt32(pagedResult.TotalItems);
//now that we have the member dto's we need to construct true members from the list.
if (totalRecords == 0)
{
return Enumerable.Empty<IMember>();
}
var result = GetAll(pagedResult.Items.Select(x => x.NodeId).ToArray());
//now we need to ensure this result is also ordered by the same order by clause
@@ -298,6 +298,11 @@ namespace Umbraco.Core.Persistence.Repositories
totalRecords = Convert.ToInt32(pagedResult.TotalItems);
//now that we have the user dto's we need to construct true members from the list.
if (totalRecords == 0)
{
return Enumerable.Empty<IUser>();
}
var result = GetAll(pagedResult.Items.Select(x => x.Id).ToArray());
//now we need to ensure this result is also ordered by the same order by clause
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Umbraco.Core.PropertyEditors
{
@@ -8,5 +10,27 @@ namespace Umbraco.Core.PropertyEditors
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
internal class DefaultPropertyValueConverterAttribute : Attribute
{
public DefaultPropertyValueConverterAttribute()
{
DefaultConvertersToShadow = Enumerable.Empty<Type>();
}
public DefaultPropertyValueConverterAttribute(params Type[] convertersToShadow)
{
DefaultConvertersToShadow = convertersToShadow;
}
/// <summary>
/// A DefaultPropertyValueConverter can 'shadow' other default property value converters so that
/// a DefaultPropertyValueConverter can be more specific than another one.
/// </summary>
/// <remarks>
/// An example where this is useful is that both the RelatedLiksEditorValueConverter and the JsonValueConverter
/// will be returned as value converters for the Related Links Property editor, however the JsonValueConverter
/// is a very generic converter and the RelatedLiksEditorValueConverter is more specific than it, so the RelatedLiksEditorValueConverter
/// can specify that it 'shadows' the JsonValueConverter.
/// </remarks>
public IEnumerable<Type> DefaultConvertersToShadow { get; private set; }
}
}
@@ -97,10 +97,7 @@ namespace Umbraco.Core.PropertyEditors
CreateMap("mediaCurrent", Constants.PropertyEditors.MediaPickerAlias);
CreateMap("number", Constants.PropertyEditors.IntegerAlias);
CreateMap("number", Constants.PropertyEditors.IntegerAlias);
}
}
}
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Umbraco.Core.ObjectResolution;
namespace Umbraco.Core.PropertyEditors
@@ -36,5 +38,41 @@ namespace Umbraco.Core.PropertyEditors
{
get { return Values; }
}
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
private Tuple<IPropertyValueConverter, DefaultPropertyValueConverterAttribute>[] _defaults = null;
/// <summary>
/// Caches and gets the default converters with their metadata
/// </summary>
internal Tuple<IPropertyValueConverter, DefaultPropertyValueConverterAttribute>[] DefaultConverters
{
get
{
using (var locker = new UpgradeableReadLock(_lock))
{
if (_defaults == null)
{
locker.UpgradeToWriteLock();
var defaultConvertersWithAttributes = Converters
.Select(x => new
{
attribute = x.GetType().GetCustomAttribute<DefaultPropertyValueConverterAttribute>(false),
converter = x
})
.Where(x => x.attribute != null)
.ToArray();
_defaults = defaultConvertersWithAttributes
.Select(
x => new Tuple<IPropertyValueConverter, DefaultPropertyValueConverterAttribute>(x.converter, x.attribute))
.ToArray();
}
return _defaults;
}
}
}
}
}
@@ -137,12 +137,19 @@ namespace Umbraco.Core.Security
{
if (response == null) throw new ArgumentNullException("response");
//remove the cookie
var cookie = new CookieHeaderValue(UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName, "")
var authCookie = new CookieHeaderValue(UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName, "")
{
Expires = DateTime.Now.AddYears(-1),
Path = "/"
};
response.Headers.AddCookies(new[] { cookie });
//remove the preview cookie too
var prevCookie = new CookieHeaderValue(Constants.Web.PreviewCookieName, "")
{
Expires = DateTime.Now.AddYears(-1),
Path = "/"
};
response.Headers.AddCookies(new[] { authCookie, prevCookie });
}
/// <summary>
@@ -259,21 +266,27 @@ namespace Umbraco.Core.Security
private static void Logout(this HttpContextBase http, string cookieName)
{
if (http == null) throw new ArgumentNullException("http");
//remove from the request
http.Request.Cookies.Remove(cookieName);
//clear the preview cookie too
var cookies = new[] { cookieName, Constants.Web.PreviewCookieName };
foreach (var c in cookies)
{
//remove from the request
http.Request.Cookies.Remove(c);
//expire from the response
var formsCookie = http.Response.Cookies[c];
if (formsCookie != null)
{
//this will expire immediately and be removed from the browser
formsCookie.Expires = DateTime.Now.AddYears(-1);
}
else
{
//ensure there's def an expired cookie
http.Response.Cookies.Add(new HttpCookie(c) { Expires = DateTime.Now.AddYears(-1) });
}
}
//expire from the response
var formsCookie = http.Response.Cookies[cookieName];
if (formsCookie != null)
{
//this will expire immediately and be removed from the browser
formsCookie.Expires = DateTime.Now.AddYears(-1);
}
else
{
//ensure there's def an expired cookie
http.Response.Cookies.Add(new HttpCookie(cookieName) { Expires = DateTime.Now.AddYears(-1) });
}
}
private static FormsAuthenticationTicket GetAuthTicket(this HttpContextBase http, string cookieName)
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Security;
using Umbraco.Core.Security;
namespace Umbraco.Core.Security
{
internal static class MembershipProviderExtensions
{
/// <summary>
/// Returns true if the provider specified is a built-in Umbraco membership provider
/// </summary>
/// <param name="membershipProvider"></param>
/// <returns></returns>
public static bool IsUmbracoMembershipProvider(this MembershipProvider membershipProvider)
{
return (membershipProvider is UmbracoMembershipProviderBase);
}
public static UmbracoMembershipProviderBase AsUmbracoMembershipProvider(this MembershipProvider membershipProvider)
{
return (UmbracoMembershipProviderBase)membershipProvider;
}
}
}
@@ -1,7 +1,9 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models.Membership;
@@ -17,9 +19,15 @@ namespace Umbraco.Core.Services
/// Sends the notifications for the specified user regarding the specified node and action.
/// </summary>
/// <param name="entity"></param>
/// <param name="user"></param>
/// <param name="operatingUser"></param>
/// <param name="action"></param>
void SendNotifications(IEntity entity, IUser user, IAction action);
/// <param name="actionName"></param>
/// <param name="http"></param>
/// <param name="createSubject"></param>
/// <param name="createBody"></param>
void SendNotifications(IUser operatingUser, IUmbracoEntity entity, string action, string actionName, HttpContextBase http,
Func<IUser, string[], string> createSubject,
Func<IUser, string[], string> createBody);
/// <summary>
/// Gets the notifications for the user
@@ -28,6 +36,17 @@ namespace Umbraco.Core.Services
/// <returns></returns>
IEnumerable<Notification> GetUserNotifications(IUser user);
/// <summary>
/// Gets the notifications for the user based on the specified node path
/// </summary>
/// <param name="user"></param>
/// <param name="path"></param>
/// <returns></returns>
/// <remarks>
/// Notifications are inherited from the parent so any child node will also have notifications assigned based on it's parent (ancestors)
/// </remarks>
IEnumerable<Notification> GetUserNotifications(IUser user, string path);
/// <summary>
/// Returns the notifications for an entity
/// </summary>
+48 -6
View File
@@ -20,7 +20,7 @@ namespace Umbraco.Core.Services
/// Imports and saves package xml as <see cref="IContentType"/>
/// </summary>
/// <param name="element">Xml to import</param>
/// <param name="userId">Optional id of the User performing the operation. Default is zero (admin).</param>
/// <param name="userId">Optional id of the User performing the operation. Default is zero (admin)</param>
/// <param name="raiseEvents">Optional parameter indicating whether or not to raise events</param>
/// <returns>An enumrable list of generated ContentTypes</returns>
IEnumerable<IContentType> ImportContentTypes(XElement element, int userId = 0, bool raiseEvents = true);
@@ -30,7 +30,7 @@ namespace Umbraco.Core.Services
/// </summary>
/// <param name="element">Xml to import</param>
/// <param name="importStructure">Boolean indicating whether or not to import the </param>
/// <param name="userId">Optional id of the User performing the operation. Default is zero (admin).</param>
/// <param name="userId">Optional id of the User performing the operation. Default is zero (admin)</param>
/// <param name="raiseEvents">Optional parameter indicating whether or not to raise events</param>
/// <returns>An enumrable list of generated ContentTypes</returns>
IEnumerable<IContentType> ImportContentTypes(XElement element, bool importStructure, int userId = 0, bool raiseEvents = true);
@@ -39,7 +39,7 @@ namespace Umbraco.Core.Services
/// Imports and saves package xml as <see cref="IDataTypeDefinition"/>
/// </summary>
/// <param name="element">Xml to import</param>
/// <param name="userId"></param>
/// <param name="userId">Optional id of the User performing the operation. Default is zero (admin).</param>
/// <param name="raiseEvents">Optional parameter indicating whether or not to raise events</param>
/// <returns>An enumrable list of generated DataTypeDefinitions</returns>
IEnumerable<IDataTypeDefinition> ImportDataTypeDefinitions(XElement element, int userId = 0, bool raiseEvents = true);
@@ -56,15 +56,25 @@ namespace Umbraco.Core.Services
/// Imports and saves the 'Languages' part of a package xml as a list of <see cref="ILanguage"/>
/// </summary>
/// <param name="languageElementList">Xml to import</param>
/// <param name="userId">Optional id of the User performing the operation. Default is zero (admin)</param>
/// <param name="raiseEvents">Optional parameter indicating whether or not to raise events</param>
/// <returns>An enumerable list of generated languages</returns>
IEnumerable<ILanguage> ImportLanguages(XElement languageElementList, bool raiseEvents = true);
IEnumerable<ILanguage> ImportLanguages(XElement languageElementList, int userId = 0, bool raiseEvents = true);
/// <summary>
/// Imports and saves the 'Macros' part of a package xml as a list of <see cref="IMacro"/>
/// </summary>
/// <param name="element">Xml to import</param>
/// <param name="userId">Optional id of the User performing the operation</param>
/// <param name="raiseEvents">Optional parameter indicating whether or not to raise events</param>
/// <returns></returns>
IEnumerable<IMacro> ImportMacros(XElement element, int userId = 0, bool raiseEvents = true);
/// <summary>
/// Imports and saves package xml as <see cref="ITemplate"/>
/// </summary>
/// <param name="element">Xml to import</param>
/// <param name="userId">Optional user id</param>
/// <param name="userId">Optional id of the User performing the operation. Default is zero (admin)</param>
/// <param name="raiseEvents">Optional parameter indicating whether or not to raise events</param>
/// <returns>An enumrable list of generated Templates</returns>
IEnumerable<ITemplate> ImportTemplates(XElement element, int userId = 0, bool raiseEvents = true);
@@ -74,7 +84,7 @@ namespace Umbraco.Core.Services
/// </summary>
/// <param name="contentType">ContentType to export</param>
/// <param name="raiseEvents">Optional parameter indicating whether or not to raise events</param>
/// <returns><see cref="XElement"/> containing the xml representation of the ContentType item.</returns>
/// <returns><see cref="XElement"/> containing the xml representation of the ContentType item</returns>
XElement Export(IContentType contentType, bool raiseEvents = true);
/// <summary>
@@ -144,5 +154,37 @@ namespace Umbraco.Core.Services
/// <param name="raiseEvents">Optional parameter indicating whether or not to raise events</param>
/// <returns><see cref="XElement"/> containing the xml representation of the IDataTypeDefinition object</returns>
XElement Export(IDataTypeDefinition dataTypeDefinition, bool raiseEvents = true);
/// <summary>
/// Exports a list of <see cref="ITemplate"/> items to xml as an <see cref="XElement"/>
/// </summary>
/// <param name="templates">List of Templates to export</param>
/// <param name="raiseEvents">Optional parameter indicating whether or not to raise events</param>
/// <returns><see cref="XElement"/> containing the xml representation of the ITemplate objects</returns>
XElement Export(IEnumerable<ITemplate> templates, bool raiseEvents = true);
/// <summary>
/// Exports a single <see cref="ITemplate"/> item to xml as an <see cref="XElement"/>
/// </summary>
/// <param name="template">Template to export</param>
/// <param name="raiseEvents">Optional parameter indicating whether or not to raise events</param>
/// <returns><see cref="XElement"/> containing the xml representation of the ITemplate object</returns>
XElement Export(ITemplate template, bool raiseEvents = true);
/// <summary>
/// Exports a list of <see cref="IMacro"/> items to xml as an <see cref="XElement"/>
/// </summary>
/// <param name="macros">Macros to export</param>
/// <param name="raiseEvents">Optional parameter indicating whether or not to raise events</param>
/// <returns><see cref="XElement"/> containing the xml representation of the IMacro objects</returns>
XElement Export(IEnumerable<IMacro> macros, bool raiseEvents = true);
/// <summary>
/// Exports a single <see cref="IMacro"/> item to xml as an <see cref="XElement"/>
/// </summary>
/// <param name="macro">Macro to export</param>
/// <param name="raiseEvents">Optional parameter indicating whether or not to raise events</param>
/// <returns><see cref="XElement"/> containing the xml representation of the IMacro object</returns>
XElement Export(IMacro macro, bool raiseEvents = true);
}
}
+2 -2
View File
@@ -191,7 +191,7 @@ namespace Umbraco.Core.Services
using (var uow = _uowProvider.GetUnitOfWork())
{
var repository = _repositoryFactory.CreateMemberRepository(uow);
//NOTE What about content that has the contenttype as part of its composition?
//TODO: What about content that has the contenttype as part of its composition?
var query = Query<IMember>.Builder.Where(x => x.ContentTypeId == memberTypeId);
var members = repository.GetByQuery(query).ToArray();
@@ -635,7 +635,7 @@ namespace Umbraco.Core.Services
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateMemberRepository(uow))
{
var query = Query<IMember>.Builder.Where(x => x.Username == userName);
var query = Query<IMember>.Builder.Where(x => x.Username.Equals(userName));
var member = repository.GetByQuery(query).FirstOrDefault();
return member;
@@ -1,10 +1,20 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Mail;
using System.Text;
using System.Threading;
using System.Web;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Core.Strings;
using umbraco.interfaces;
namespace Umbraco.Core.Services
@@ -12,21 +22,67 @@ namespace Umbraco.Core.Services
internal class NotificationService : INotificationService
{
private readonly IDatabaseUnitOfWorkProvider _uowProvider;
private readonly IUserService _userService;
private readonly IContentService _contentService;
public NotificationService(IDatabaseUnitOfWorkProvider provider)
public NotificationService(IDatabaseUnitOfWorkProvider provider, IUserService userService, IContentService contentService)
{
_uowProvider = provider;
_userService = userService;
_contentService = contentService;
}
/// <summary>
/// Sends the notifications for the specified user regarding the specified node and action.
/// </summary>
/// <param name="entity"></param>
/// <param name="user"></param>
/// <param name="operatingUser"></param>
/// <param name="action"></param>
public void SendNotifications(IEntity entity, IUser user, IAction action)
/// <param name="actionName"></param>
/// <param name="http"></param>
/// <param name="createSubject"></param>
/// <param name="createBody"></param>
/// <remarks>
/// Currently this will only work for Content entities!
/// </remarks>
public void SendNotifications(IUser operatingUser, IUmbracoEntity entity, string action, string actionName, HttpContextBase http,
Func<IUser, string[], string> createSubject,
Func<IUser, string[], string> createBody)
{
throw new NotImplementedException();
if ((entity is IContent) == false)
{
throw new NotSupportedException();
}
var content = (IContent) entity;
//we'll lazily get these if we need to send notifications
IContent[] allVersions = null;
int totalUsers;
var allUsers = _userService.GetAllMembers(0, int.MaxValue, out totalUsers);
foreach (var u in allUsers)
{
if (u.IsApproved == false) continue;
var userNotifications = GetUserNotifications(u, content.Path).ToArray();
var notificationForAction = userNotifications.FirstOrDefault(x => x.Action == action);
if (notificationForAction != null)
{
//lazy load versions if notifications are required
if (allVersions == null)
{
allVersions = _contentService.GetVersions(entity.Id).ToArray();
}
try
{
SendNotification(operatingUser, u, content, allVersions, actionName, http, createSubject, createBody);
LogHelper.Debug<NotificationService>(string.Format("Notification type: {0} sent to {1} ({2})", action, u.Name, u.Email));
}
catch (Exception ex)
{
LogHelper.Error<NotificationService>("An error occurred sending notification", ex);
}
}
}
}
/// <summary>
@@ -41,6 +97,23 @@ namespace Umbraco.Core.Services
return repository.GetUserNotifications(user);
}
/// <summary>
/// Gets the notifications for the user based on the specified node path
/// </summary>
/// <param name="user"></param>
/// <param name="path"></param>
/// <returns></returns>
/// <remarks>
/// Notifications are inherited from the parent so any child node will also have notifications assigned based on it's parent (ancestors)
/// </remarks>
public IEnumerable<Notification> GetUserNotifications(IUser user, string path)
{
var userNotifications = GetUserNotifications(user).ToArray();
var pathParts = path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries);
var result = userNotifications.Where(r => pathParts.InvariantContains(r.EntityId.ToString(CultureInfo.InvariantCulture))).ToList();
return result;
}
/// <summary>
/// Deletes notifications by entity
/// </summary>
@@ -99,5 +172,255 @@ namespace Umbraco.Core.Services
var repository = new NotificationsRepository(uow);
return repository.CreateNotification(user, entity, action);
}
#region private methods
/// <summary>
/// Sends the notification
/// </summary>
/// <param name="performingUser"></param>
/// <param name="mailingUser"></param>
/// <param name="content"></param>
/// <param name="allVersions"></param>
/// <param name="actionName">The action readable name - currently an action is just a single letter, this is the name associated with the letter </param>
/// <param name="http"></param>
/// <param name="createSubject">Callback to create the mail subject</param>
/// <param name="createBody">Callback to create the mail body</param>
private void SendNotification(IUser performingUser, IUser mailingUser, IContent content, IContent[] allVersions, string actionName, HttpContextBase http,
Func<IUser, string[], string> createSubject,
Func<IUser, string[], string> createBody)
{
if (performingUser == null) throw new ArgumentNullException("performingUser");
if (mailingUser == null) throw new ArgumentNullException("mailingUser");
if (content == null) throw new ArgumentNullException("content");
if (allVersions == null) throw new ArgumentNullException("allVersions");
if (http == null) throw new ArgumentNullException("http");
if (createSubject == null) throw new ArgumentNullException("createSubject");
if (createBody == null) throw new ArgumentNullException("createBody");
int versionCount = (allVersions.Length > 1) ? (allVersions.Length - 2) : (allVersions.Length - 1);
var oldDoc = _contentService.GetByVersion(allVersions[versionCount].Version);
// build summary
var summary = new StringBuilder();
var props = content.Properties.ToArray();
foreach (var p in props)
{
var newText = p.Value != null ? p.Value.ToString() : "";
var oldText = newText;
// check if something was changed and display the changes otherwise display the fields
if (oldDoc.Properties.Contains(p.PropertyType.Alias))
{
var oldProperty = oldDoc.Properties[p.PropertyType.Alias];
oldText = oldProperty.Value != null ? oldProperty.Value.ToString() : "";
// replace html with char equivalent
ReplaceHtmlSymbols(ref oldText);
ReplaceHtmlSymbols(ref newText);
}
// make sure to only highlight changes done using TinyMCE editor... other changes will be displayed using default summary
// TODO: We should probably allow more than just tinymce??
if ((p.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.TinyMCEAlias)
&& string.CompareOrdinal(oldText, newText) != 0)
{
summary.Append("<tr>");
summary.Append("<th style='text-align: left; vertical-align: top; width: 25%;'> Note: </th>");
summary.Append(
"<td style='text-align: left; vertical-align: top;'> <span style='background-color:red;'>Red for deleted characters</span>&nbsp;<span style='background-color:yellow;'>Yellow for inserted characters</span></td>");
summary.Append("</tr>");
summary.Append("<tr>");
summary.Append("<th style='text-align: left; vertical-align: top; width: 25%;'> New " +
p.PropertyType.Name + "</th>");
summary.Append("<td style='text-align: left; vertical-align: top;'>" +
ReplaceLinks(CompareText(oldText, newText, true, false, "<span style='background-color:yellow;'>", string.Empty), http.Request) +
"</td>");
summary.Append("</tr>");
summary.Append("<tr>");
summary.Append("<th style='text-align: left; vertical-align: top; width: 25%;'> Old " +
p.PropertyType.Name + "</th>");
summary.Append("<td style='text-align: left; vertical-align: top;'>" +
ReplaceLinks(CompareText(newText, oldText, true, false, "<span style='background-color:red;'>", string.Empty), http.Request) +
"</td>");
summary.Append("</tr>");
}
else
{
summary.Append("<tr>");
summary.Append("<th style='text-align: left; vertical-align: top; width: 25%;'>" +
p.PropertyType.Name + "</th>");
summary.Append("<td style='text-align: left; vertical-align: top;'>" + newText + "</td>");
summary.Append("</tr>");
}
summary.Append(
"<tr><td colspan=\"2\" style=\"border-bottom: 1px solid #CCC; font-size: 2px;\">&nbsp;</td></tr>");
}
string protocol = GlobalSettings.UseSSL ? "https" : "http";
string[] subjectVars = {
http.Request.ServerVariables["SERVER_NAME"] + ":" +
http.Request.Url.Port +
IOHelper.ResolveUrl(SystemDirectories.Umbraco),
actionName,
content.Name
};
string[] bodyVars = {
mailingUser.Name,
actionName,
content.Name,
performingUser.Name,
http.Request.ServerVariables["SERVER_NAME"] + ":" + http.Request.Url.Port + IOHelper.ResolveUrl(SystemDirectories.Umbraco),
content.Id.ToString(CultureInfo.InvariantCulture), summary.ToString(),
string.Format("{2}://{0}/{1}",
http.Request.ServerVariables["SERVER_NAME"] + ":" + http.Request.Url.Port,
//TODO: RE-enable this so we can have a nice url
/*umbraco.library.NiceUrl(documentObject.Id))*/
content.Id + ".aspx",
protocol)
};
// create the mail message
var mail = new MailMessage(UmbracoConfig.For.UmbracoSettings().Content.NotificationEmailAddress, mailingUser.Email);
// populate the message
mail.Subject = createSubject(mailingUser, subjectVars);
if (UmbracoConfig.For.UmbracoSettings().Content.DisableHtmlEmail)
{
mail.IsBodyHtml = false;
mail.Body = createBody(mailingUser, bodyVars);
}
else
{
mail.IsBodyHtml = true;
mail.Body =
@"<html><head>
</head>
<body style='font-family: Trebuchet MS, arial, sans-serif; font-color: black;'>
" + createBody(mailingUser, bodyVars);
}
// nh, issue 30724. Due to hardcoded http strings in resource files, we need to check for https replacements here
// adding the server name to make sure we don't replace external links
if (GlobalSettings.UseSSL && string.IsNullOrEmpty(mail.Body) == false)
{
string serverName = http.Request.ServerVariables["SERVER_NAME"];
mail.Body = mail.Body.Replace(
string.Format("http://{0}", serverName),
string.Format("https://{0}", serverName));
}
// send it asynchronously, we don't want to got up all of the request time to send emails!
ThreadPool.QueueUserWorkItem(state =>
{
try
{
using (mail)
{
using (var sender = new SmtpClient())
{
sender.Send(mail);
}
}
}
catch (Exception ex)
{
LogHelper.Error<NotificationService>("An error occurred sending notification", ex);
}
});
}
private static string ReplaceLinks(string text, HttpRequestBase request)
{
string domain = GlobalSettings.UseSSL ? "https://" : "http://";
domain += request.ServerVariables["SERVER_NAME"] + ":" + request.Url.Port + "/";
text = text.Replace("href=\"/", "href=\"" + domain);
text = text.Replace("src=\"/", "src=\"" + domain);
return text;
}
/// <summary>
/// Replaces the HTML symbols with the character equivalent.
/// </summary>
/// <param name="oldString">The old string.</param>
private static void ReplaceHtmlSymbols(ref string oldString)
{
oldString = oldString.Replace("&nbsp;", " ");
oldString = oldString.Replace("&rsquo;", "'");
oldString = oldString.Replace("&amp;", "&");
oldString = oldString.Replace("&ldquo;", "“");
oldString = oldString.Replace("&rdquo;", "”");
oldString = oldString.Replace("&quot;", "\"");
}
/// <summary>
/// Compares the text.
/// </summary>
/// <param name="oldText">The old text.</param>
/// <param name="newText">The new text.</param>
/// <param name="displayInsertedText">if set to <c>true</c> [display inserted text].</param>
/// <param name="displayDeletedText">if set to <c>true</c> [display deleted text].</param>
/// <param name="insertedStyle">The inserted style.</param>
/// <param name="deletedStyle">The deleted style.</param>
/// <returns></returns>
private static string CompareText(string oldText, string newText, bool displayInsertedText,
bool displayDeletedText, string insertedStyle, string deletedStyle)
{
var sb = new StringBuilder();
var diffs = Diff.DiffText1(oldText, newText);
int pos = 0;
for (int n = 0; n < diffs.Length; n++)
{
Diff.Item it = diffs[n];
// write unchanged chars
while ((pos < it.StartB) && (pos < newText.Length))
{
sb.Append(newText[pos]);
pos++;
} // while
// write deleted chars
if (displayDeletedText && it.DeletedA > 0)
{
sb.Append(deletedStyle);
for (int m = 0; m < it.DeletedA; m++)
{
sb.Append(oldText[it.StartA + m]);
} // for
sb.Append("</span>");
}
// write inserted chars
if (displayInsertedText && pos < it.StartB + it.InsertedB)
{
sb.Append(insertedStyle);
while (pos < it.StartB + it.InsertedB)
{
sb.Append(newText[pos]);
pos++;
} // while
sb.Append("</span>");
} // if
} // while
// write rest of unchanged chars
while (pos < newText.Length)
{
sb.Append(newText[pos]);
pos++;
} // while
return sb.ToString();
}
#endregion
}
}
+326 -70
View File
@@ -4,6 +4,7 @@ using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml;
using System.Xml.Linq;
using Umbraco.Core.Configuration;
using Umbraco.Core.Events;
@@ -27,6 +28,7 @@ namespace Umbraco.Core.Services
private readonly IContentService _contentService;
private readonly IContentTypeService _contentTypeService;
private readonly IMediaService _mediaService;
private readonly IMacroService _macroService;
private readonly IDataTypeService _dataTypeService;
private readonly IFileService _fileService;
private readonly ILocalizationService _localizationService;
@@ -38,6 +40,7 @@ namespace Umbraco.Core.Services
public PackagingService(IContentService contentService,
IContentTypeService contentTypeService,
IMediaService mediaService,
IMacroService macroService,
IDataTypeService dataTypeService,
IFileService fileService,
ILocalizationService localizationService,
@@ -47,6 +50,7 @@ namespace Umbraco.Core.Services
_contentService = contentService;
_contentTypeService = contentTypeService;
_mediaService = mediaService;
_macroService = macroService;
_dataTypeService = dataTypeService;
_fileService = fileService;
_localizationService = localizationService;
@@ -67,15 +71,15 @@ namespace Umbraco.Core.Services
/// <returns><see cref="XElement"/> containing the xml representation of the Content object</returns>
public XElement Export(IContent content, bool deep = false, bool raiseEvents = true)
{
if (raiseEvents)
{
if (ExportingContent.IsRaisedEventCancelled(new SaveEventArgs<IContent>(content), this))
return default(XElement);
}
//nodeName should match Casing.SafeAliasWithForcingCheck(content.ContentType.Alias);
var nodeName = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema ? "node" : content.ContentType.Alias.ToSafeAliasWithForcingCheck();
if (raiseEvents)
{
if (ExportingContent.IsRaisedEventCancelled(new ExportEventArgs<IContent>(content, nodeName), this))
return new XElement(nodeName);
}
var xml = Export(content, nodeName);
xml.Add(new XAttribute("nodeType", content.ContentType.Id));
xml.Add(new XAttribute("creatorName", content.GetCreatorProfile().Name));
@@ -92,7 +96,7 @@ namespace Umbraco.Core.Services
}
if(raiseEvents)
ContentExported.RaiseEvent(new SaveEventArgs<XElement>(xml, false), this);
ExportedContent.RaiseEvent(new ExportEventArgs<IContent>(content, xml, false), this);
return xml;
}
@@ -110,6 +114,7 @@ namespace Umbraco.Core.Services
var xml = new XElement(nodeName,
new XAttribute("id", contentBase.Id),
new XAttribute("key", contentBase.Key.ToString("D")),
new XAttribute("parentID", contentBase.Level > 1 ? contentBase.ParentId : -1),
new XAttribute("level", contentBase.Level),
new XAttribute("creatorID", contentBase.CreatorId),
@@ -163,7 +168,7 @@ namespace Umbraco.Core.Services
{
if (raiseEvents)
{
if (ImportingContent.IsRaisedEventCancelled(new SaveEventArgs<XElement>(element), this))
if (ImportingContent.IsRaisedEventCancelled(new ImportEventArgs<IContent>(element), this))
return Enumerable.Empty<IContent>();
}
@@ -180,7 +185,7 @@ namespace Umbraco.Core.Services
_contentService.Save(contents, userId);
if(raiseEvents)
ContentImported.RaiseEvent(new SaveEventArgs<IContent>(contents, false), this);
ImportedContent.RaiseEvent(new ImportEventArgs<IContent>(contents, element, false), this);
return contents;
}
@@ -194,7 +199,7 @@ namespace Umbraco.Core.Services
_contentService.Save(contents, userId);
if(raiseEvents)
ContentImported.RaiseEvent(new SaveEventArgs<IContent>(contents, false), this);
ImportedContent.RaiseEvent(new ImportEventArgs<IContent>(contents, element, false), this);
return contents;
}
@@ -299,7 +304,7 @@ namespace Umbraco.Core.Services
if (propertyType != null && propertyType.PropertyEditorAlias == Constants.PropertyEditors.CheckBoxListAlias)
{
var database = ApplicationContext.Current.DatabaseContext.Database;
var dtos = database.Fetch<DataTypePreValueDto>("WHERE datatypeNo" + "deId = @Id", new { Id = propertyType.DataTypeDefinitionId });
var dtos = database.Fetch<DataTypePreValueDto>("WHERE datatypeNodeId = @Id", new { Id = propertyType.DataTypeDefinitionId });
var propertyValueList = new List<string>();
foreach (var preValue in propertyValue.Split(','))
@@ -331,8 +336,8 @@ namespace Umbraco.Core.Services
{
if (raiseEvents)
{
if (ExportingContentType.IsRaisedEventCancelled(new SaveEventArgs<IContentType>(contentType), this))
return default(XElement);
if (ExportingContentType.IsRaisedEventCancelled(new ExportEventArgs<IContentType>(contentType, "DocumentType"), this))
return new XElement("DocumentType");
}
var info = new XElement("Info",
@@ -397,7 +402,7 @@ namespace Umbraco.Core.Services
tabs);
if (raiseEvents)
ContentTypeExported.RaiseEvent(new SaveEventArgs<XElement>(xml, false), this);
ExportedContentType.RaiseEvent(new ExportEventArgs<IContentType>(contentType, xml, false), this);
return xml;
}
@@ -426,7 +431,7 @@ namespace Umbraco.Core.Services
{
if (raiseEvents)
{
if (ImportingContentType.IsRaisedEventCancelled(new SaveEventArgs<XElement>(element), this))
if (ImportingContentType.IsRaisedEventCancelled(new ImportEventArgs<IContentType>(element), this))
return Enumerable.Empty<IContentType>();
}
@@ -477,7 +482,7 @@ namespace Umbraco.Core.Services
}
if (raiseEvents)
ContentTypeImported.RaiseEvent(new SaveEventArgs<IContentType>(list, false), this);
ImportedContentType.RaiseEvent(new ImportEventArgs<IContentType>(list, element, false), this);
return list;
}
@@ -722,6 +727,11 @@ namespace Umbraco.Core.Services
return contentType;
}
/// <summary>
/// Used during Content import to ensure that the ContentType of a content item exists
/// </summary>
/// <param name="contentTypeAlias"></param>
/// <returns></returns>
private IContentType FindContentTypeByAlias(string contentTypeAlias)
{
using (var repository = _repositoryFactory.CreateContentTypeRepository(_uowProvider.GetUnitOfWork()))
@@ -774,12 +784,11 @@ namespace Umbraco.Core.Services
{
if (raiseEvents)
{
if (ExportingDataType.IsRaisedEventCancelled(new SaveEventArgs<IDataTypeDefinition>(dataTypeDefinition), this))
return default(XElement);
if (ExportingDataType.IsRaisedEventCancelled(new ExportEventArgs<IDataTypeDefinition>(dataTypeDefinition, "DataType"), this))
return new XElement("DataType");
}
var prevalues = new XElement("PreValues");
var prevalueList = _dataTypeService.GetPreValuesCollectionByDataTypeId(dataTypeDefinition.Id)
.FormatAsDictionary();
@@ -803,7 +812,7 @@ namespace Umbraco.Core.Services
xml.Add(new XAttribute("DatabaseType", dataTypeDefinition.DatabaseType.ToString()));
if (raiseEvents)
DataTypeExported.RaiseEvent(new SaveEventArgs<XElement>(xml, false), this);
ExportedDataType.RaiseEvent(new ExportEventArgs<IDataTypeDefinition>(dataTypeDefinition, xml, false), this);
return xml;
}
@@ -819,7 +828,7 @@ namespace Umbraco.Core.Services
{
if (raiseEvents)
{
if (ImportingDataType.IsRaisedEventCancelled(new SaveEventArgs<XElement>(element), this))
if (ImportingDataType.IsRaisedEventCancelled(new ImportEventArgs<IDataTypeDefinition>(element), this))
return Enumerable.Empty<IDataTypeDefinition>();
}
@@ -887,7 +896,7 @@ namespace Umbraco.Core.Services
}
if (raiseEvents)
DataTypeImported.RaiseEvent(new SaveEventArgs<IDataTypeDefinition>(list, false), this);
ImportedDataType.RaiseEvent(new ImportEventArgs<IDataTypeDefinition>(list, element, false), this);
return list;
}
@@ -950,8 +959,8 @@ namespace Umbraco.Core.Services
{
if (raiseEvents)
{
if (ExportingDictionaryItem.IsRaisedEventCancelled(new SaveEventArgs<IDictionaryItem>(dictionaryItem), this))
return default(XElement);
if (ExportingDictionaryItem.IsRaisedEventCancelled(new ExportEventArgs<IDictionaryItem>(dictionaryItem, "DictionaryItem"), this))
return new XElement("DictionaryItem");
}
var xml = new XElement("DictionaryItem", new XAttribute("Key", dictionaryItem.ItemKey));
@@ -973,7 +982,7 @@ namespace Umbraco.Core.Services
}
if (raiseEvents)
DictionaryItemExported.RaiseEvent(new SaveEventArgs<XElement>(xml, false), this);
ExportedDictionaryItem.RaiseEvent(new ExportEventArgs<IDictionaryItem>(dictionaryItem, xml, false), this);
return xml;
}
@@ -988,7 +997,7 @@ namespace Umbraco.Core.Services
{
if (raiseEvents)
{
if (ImportingDictionaryItem.IsRaisedEventCancelled(new SaveEventArgs<XElement>(dictionaryItemElementList), this))
if (ImportingDictionaryItem.IsRaisedEventCancelled(new ImportEventArgs<IDictionaryItem>(dictionaryItemElementList), this))
return Enumerable.Empty<IDictionaryItem>();
}
@@ -1000,15 +1009,15 @@ namespace Umbraco.Core.Services
{
var items = new List<IDictionaryItem>();
foreach (var dictionaryItemElement in dictionaryItemElementList.Elements("DictionaryItem"))
items.AddRange(ImportDictionaryItem(dictionaryItemElement, languages));
items.AddRange(ImportDictionaryItem(dictionaryItemElement, languages, raiseEvents));
if (raiseEvents)
DictionaryItemImported.RaiseEvent(new SaveEventArgs<IDictionaryItem>(items, false), this);
ImportedDictionaryItem.RaiseEvent(new ImportEventArgs<IDictionaryItem>(items, dictionaryItemElementList, false), this);
return items;
}
private IEnumerable<IDictionaryItem> ImportDictionaryItem(XElement dictionaryItemElement, List<ILanguage> languages)
private IEnumerable<IDictionaryItem> ImportDictionaryItem(XElement dictionaryItemElement, List<ILanguage> languages, bool raiseEvents)
{
var items = new List<IDictionaryItem>();
@@ -1020,7 +1029,7 @@ namespace Umbraco.Core.Services
dictionaryItem = CreateNewDictionaryItem(key, dictionaryItemElement, languages);
_localizationService.Save(dictionaryItem);
items.Add(dictionaryItem);
items.AddRange(ImportDictionaryItems(dictionaryItemElement, languages));
items.AddRange(ImportDictionaryItems(dictionaryItemElement, languages, raiseEvents));
return items;
}
@@ -1096,8 +1105,8 @@ namespace Umbraco.Core.Services
{
if (raiseEvents)
{
if (ExportingLanguage.IsRaisedEventCancelled(new SaveEventArgs<ILanguage>(language), this))
return default(XElement);
if (ExportingLanguage.IsRaisedEventCancelled(new ExportEventArgs<ILanguage>(language, "Language"), this))
return new XElement("Language");
}
var xml = new XElement("Language",
@@ -1106,7 +1115,7 @@ namespace Umbraco.Core.Services
new XAttribute("FriendlyName", language.CultureName));
if (raiseEvents)
LanguageExported.RaiseEvent(new SaveEventArgs<XElement>(xml, false), this);
ExportedLanguage.RaiseEvent(new ExportEventArgs<ILanguage>(language, xml, false), this);
return xml;
}
@@ -1115,13 +1124,14 @@ namespace Umbraco.Core.Services
/// Imports and saves the 'Languages' part of a package xml as a list of <see cref="ILanguage"/>
/// </summary>
/// <param name="languageElementList">Xml to import</param>
/// <param name="userId">Optional id of the User performing the operation</param>
/// <param name="raiseEvents">Optional parameter indicating whether or not to raise events</param>
/// <returns>An enumerable list of generated languages</returns>
public IEnumerable<ILanguage> ImportLanguages(XElement languageElementList, bool raiseEvents = true)
public IEnumerable<ILanguage> ImportLanguages(XElement languageElementList, int userId = 0, bool raiseEvents = true)
{
if (raiseEvents)
{
if (ImportingLanguage.IsRaisedEventCancelled(new SaveEventArgs<XElement>(languageElementList), this))
if (ImportingLanguage.IsRaisedEventCancelled(new ImportEventArgs<ILanguage>(languageElementList), this))
return Enumerable.Empty<ILanguage>();
}
@@ -1142,7 +1152,7 @@ namespace Umbraco.Core.Services
}
if (raiseEvents)
LanguageImported.RaiseEvent(new SaveEventArgs<ILanguage>(list, false), this);
ImportedLanguage.RaiseEvent(new ImportEventArgs<ILanguage>(list, languageElementList, false), this);
return list;
}
@@ -1150,6 +1160,171 @@ namespace Umbraco.Core.Services
#endregion
#region Macros
/// <summary>
/// Imports and saves the 'Macros' part of a package xml as a list of <see cref="IMacro"/>
/// </summary>
/// <param name="element">Xml to import</param>
/// <param name="userId">Optional id of the User performing the operation</param>
/// <param name="raiseEvents">Optional parameter indicating whether or not to raise events</param>
/// <returns></returns>
public IEnumerable<IMacro> ImportMacros(XElement element, int userId = 0, bool raiseEvents = true)
{
if (raiseEvents)
{
if (ImportingMacro.IsRaisedEventCancelled(new ImportEventArgs<IMacro>(element), this))
return Enumerable.Empty<IMacro>();
}
var name = element.Name.LocalName;
if (name.Equals("Macros") == false && name.Equals("macro") == false)
{
throw new ArgumentException("The passed in XElement is not valid! It does not contain a root element called 'Macros' for multiple imports or 'macro' for a single import.");
}
var macroElements = name.Equals("Macros")
? (from doc in element.Elements("macro") select doc).ToList()
: new List<XElement> { element };
var macros = macroElements.Select(ParseMacroElement).ToList();
foreach (var macro in macros)
{
_macroService.Save(macro, userId);
}
if (raiseEvents)
ImportedMacro.RaiseEvent(new ImportEventArgs<IMacro>(macros, element, false), this);
return macros;
}
private IMacro ParseMacroElement(XElement macroElement)
{
var macroName = macroElement.Element("name").Value;
var macroAlias = macroElement.Element("alias").Value;
var controlType = macroElement.Element("scriptType").Value;
var controlAssembly = macroElement.Element("scriptAssembly").Value;
var xsltPath = macroElement.Element("xslt").Value;
var scriptPath = macroElement.Element("scriptingFile").Value;
//Following xml elements are treated as nullable properties
var useInEditorElement = macroElement.Element("useInEditor");
var useInEditor = false;
if (useInEditorElement != null && string.IsNullOrEmpty(useInEditorElement.Value) == false)
{
useInEditor = bool.Parse(useInEditorElement.Value);
}
var cacheDurationElement = macroElement.Element("refreshRate");
var cacheDuration = 0;
if (cacheDurationElement != null && string.IsNullOrEmpty(cacheDurationElement.Value) == false)
{
cacheDuration = int.Parse(cacheDurationElement.Value);
}
var cacheByMemberElement = macroElement.Element("cacheByMember");
var cacheByMember = false;
if (cacheByMemberElement != null && string.IsNullOrEmpty(cacheByMemberElement.Value) == false)
{
cacheByMember = bool.Parse(cacheByMemberElement.Value);
}
var cacheByPageElement = macroElement.Element("cacheByPage");
var cacheByPage = false;
if (cacheByPageElement != null && string.IsNullOrEmpty(cacheByPageElement.Value) == false)
{
cacheByPage = bool.Parse(cacheByPageElement.Value);
}
var dontRenderElement = macroElement.Element("dontRender");
var dontRender = true;
if (dontRenderElement != null && string.IsNullOrEmpty(dontRenderElement.Value) == false)
{
dontRender = bool.Parse(dontRenderElement.Value);
}
var macro = new Macro(macroAlias, macroName, controlType, controlAssembly, xsltPath, scriptPath,
cacheByPage, cacheByMember, dontRender, useInEditor, cacheDuration);
var properties = macroElement.Element("properties");
if (properties != null)
{
int sortOrder = 0;
foreach (var property in properties.Elements())
{
var propertyName = property.Attribute("name").Value;
var propertyAlias = property.Attribute("alias").Value;
var editorAlias = property.Attribute("propertyType").Value;
var sortOrderAttribute = property.Attribute("sortOrder");
if (sortOrderAttribute != null)
{
sortOrder = int.Parse(sortOrderAttribute.Value);
}
macro.Properties.Add(new MacroProperty(propertyAlias, propertyName, sortOrder, editorAlias));
sortOrder++;
}
}
return macro;
}
/// <summary>
/// Exports a list of <see cref="IMacro"/> items to xml as an <see cref="XElement"/>
/// </summary>
/// <param name="macros">Macros to export</param>
/// <param name="raiseEvents">Optional parameter indicating whether or not to raise events</param>
/// <returns><see cref="XElement"/> containing the xml representation of the IMacro objects</returns>
public XElement Export(IEnumerable<IMacro> macros, bool raiseEvents = true)
{
var xml = new XElement("Macros");
foreach (var item in macros)
{
xml.Add(Export(item, raiseEvents));
}
return xml;
}
/// <summary>
/// Exports a single <see cref="IMacro"/> item to xml as an <see cref="XElement"/>
/// </summary>
/// <param name="macro">Macro to export</param>
/// <param name="raiseEvents">Optional parameter indicating whether or not to raise events</param>
/// <returns><see cref="XElement"/> containing the xml representation of the IMacro object</returns>
public XElement Export(IMacro macro, bool raiseEvents = true)
{
if (raiseEvents)
{
if (ExportingMacro.IsRaisedEventCancelled(new ExportEventArgs<IMacro>(macro, "macro"), this))
return new XElement("macro");
}
var xml = new XElement("macro");
xml.Add(new XElement("name", macro.Name));
xml.Add(new XElement("alias", macro.Alias));
xml.Add(new XElement("scriptType", macro.ControlType));
xml.Add(new XElement("scriptAssembly", macro.ControlAssembly));
xml.Add(new XElement("scriptingFile", macro.ScriptPath));
xml.Add(new XElement("xslt", macro.XsltPath));
xml.Add(new XElement("useInEditor", macro.UseInEditor.ToString()));
xml.Add(new XElement("dontRender", macro.DontRender.ToString()));
xml.Add(new XElement("refreshRate", macro.CacheDuration.ToString(CultureInfo.InvariantCulture)));
xml.Add(new XElement("cacheByMember", macro.CacheByMember.ToString()));
xml.Add(new XElement("cacheByPage", macro.CacheByPage.ToString()));
var properties = new XElement("properties");
foreach (var property in macro.Properties)
{
properties.Add(new XElement("property",
new XAttribute("name", property.Name),
new XAttribute("alias", property.Alias),
new XAttribute("sortOrder", property.SortOrder),
new XAttribute("propertyType", property.EditorAlias)));
}
xml.Add(properties);
if (raiseEvents)
ExportedMacro.RaiseEvent(new ExportEventArgs<IMacro>(macro, xml, false), this);
return xml;
}
#endregion
#region Members
@@ -1170,7 +1345,6 @@ namespace Umbraco.Core.Services
xml.Add(new XAttribute("loginName", member.Username));
xml.Add(new XAttribute("email", member.Email));
xml.Add(new XAttribute("key", member.Key));
return xml;
}
@@ -1188,15 +1362,15 @@ namespace Umbraco.Core.Services
/// <returns><see cref="XElement"/> containing the xml representation of the Media object</returns>
public XElement Export(IMedia media, bool deep = false, bool raiseEvents = true)
{
if (raiseEvents)
{
if (ExportingMedia.IsRaisedEventCancelled(new SaveEventArgs<IMedia>(media), this))
return default(XElement);
}
//nodeName should match Casing.SafeAliasWithForcingCheck(content.ContentType.Alias);
var nodeName = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema ? "node" : media.ContentType.Alias.ToSafeAliasWithForcingCheck();
if (raiseEvents)
{
if (ExportingMedia.IsRaisedEventCancelled(new ExportEventArgs<IMedia>(media, nodeName), this))
return new XElement(nodeName);
}
var xml = Export(media, nodeName);
xml.Add(new XAttribute("nodeType", media.ContentType.Id));
xml.Add(new XAttribute("writerName", media.GetCreatorProfile().Name));
@@ -1213,7 +1387,7 @@ namespace Umbraco.Core.Services
}
if(raiseEvents)
MediaExported.RaiseEvent(new SaveEventArgs<XElement>(xml, false), this);
ExportedMedia.RaiseEvent(new ExportEventArgs<IMedia>(media, xml, false), this);
return xml;
}
@@ -1321,7 +1495,7 @@ namespace Umbraco.Core.Services
{
if (raiseEvents)
{
if (ImportingTemplate.IsRaisedEventCancelled(new SaveEventArgs<XElement>(element), this))
if (ImportingTemplate.IsRaisedEventCancelled(new ImportEventArgs<ITemplate>(element), this))
return Enumerable.Empty<ITemplate>();
}
@@ -1394,7 +1568,7 @@ namespace Umbraco.Core.Services
_fileService.SaveTemplate(templates, userId);
if(raiseEvents)
TemplateImported.RaiseEvent(new SaveEventArgs<ITemplate>(templates, false), this);
ImportedTemplate.RaiseEvent(new ImportEventArgs<ITemplate>(templates, element, false), this);
return templates;
}
@@ -1415,6 +1589,58 @@ namespace Umbraco.Core.Services
return IOHelper.MapPath(SystemDirectories.Masterpages + "/" + alias.Replace(" ", "") + ".master");
}
/// <summary>
/// Exports a list of <see cref="ITemplate"/> items to xml as an <see cref="XElement"/>
/// </summary>
/// <param name="templates">List of Templates to export</param>
/// <param name="raiseEvents">Optional parameter indicating whether or not to raise events</param>
/// <returns><see cref="XElement"/> containing the xml representation of the ITemplate objects</returns>
public XElement Export(IEnumerable<ITemplate> templates, bool raiseEvents = true)
{
var xml = new XElement("Templates");
foreach (var item in templates)
{
xml.Add(Export(item, raiseEvents));
}
return xml;
}
/// <summary>
/// Exports a single <see cref="ITemplate"/> item to xml as an <see cref="XElement"/>
/// </summary>
/// <param name="template">Template to export</param>
/// <param name="raiseEvents">Optional parameter indicating whether or not to raise events</param>
/// <returns><see cref="XElement"/> containing the xml representation of the ITemplate object</returns>
public XElement Export(ITemplate template, bool raiseEvents = true)
{
if (raiseEvents)
{
if (ExportingTemplate.IsRaisedEventCancelled(new ExportEventArgs<ITemplate>(template, "Template"), this))
return new XElement("Template");
}
var xml = new XElement("Template");
xml.Add(new XElement("Name", template.Name));
xml.Add(new XElement("Alias", template.Alias));
xml.Add(new XElement("Design", new XCData(template.Content)));
var concreteTemplate = template as Template;
if (concreteTemplate != null)
{
if (concreteTemplate.MasterTemplateId.IsValueCreated &&
concreteTemplate.MasterTemplateId.Value != default(int))
{
xml.Add(new XElement("Master", concreteTemplate.MasterTemplateId.ToString()));
xml.Add(new XElement("MasterAlias", concreteTemplate.MasterTemplateAlias));
}
}
if (raiseEvents)
ExportedTemplate.RaiseEvent(new ExportEventArgs<ITemplate>(template, xml, false), this);
return xml;
}
#endregion
#region Stylesheets
@@ -1424,122 +1650,152 @@ namespace Umbraco.Core.Services
/// <summary>
/// Occurs before Importing Content
/// </summary>
public static event TypedEventHandler<IPackagingService, SaveEventArgs<XElement>> ImportingContent;
public static event TypedEventHandler<IPackagingService, ImportEventArgs<IContent>> ImportingContent;
/// <summary>
/// Occurs after Content is Imported and Saved
/// </summary>
public static event TypedEventHandler<IPackagingService, SaveEventArgs<IContent>> ContentImported;
public static event TypedEventHandler<IPackagingService, ImportEventArgs<IContent>> ImportedContent;
/// <summary>
/// Occurs before Exporting Content
/// </summary>
public static event TypedEventHandler<IPackagingService, SaveEventArgs<IContent>> ExportingContent;
public static event TypedEventHandler<IPackagingService, ExportEventArgs<IContent>> ExportingContent;
/// <summary>
/// Occurs after Content is Exported to Xml
/// </summary>
public static event TypedEventHandler<IPackagingService, SaveEventArgs<XElement>> ContentExported;
public static event TypedEventHandler<IPackagingService, ExportEventArgs<IContent>> ExportedContent;
/// <summary>
/// Occurs before Exporting Media
/// </summary>
public static event TypedEventHandler<IPackagingService, SaveEventArgs<IMedia>> ExportingMedia;
public static event TypedEventHandler<IPackagingService, ExportEventArgs<IMedia>> ExportingMedia;
/// <summary>
/// Occurs after Media is Exported to Xml
/// </summary>
public static event TypedEventHandler<IPackagingService, SaveEventArgs<XElement>> MediaExported;
public static event TypedEventHandler<IPackagingService, ExportEventArgs<IMedia>> ExportedMedia;
/// <summary>
/// Occurs before Importing ContentType
/// </summary>
public static event TypedEventHandler<IPackagingService, SaveEventArgs<XElement>> ImportingContentType;
public static event TypedEventHandler<IPackagingService, ImportEventArgs<IContentType>> ImportingContentType;
/// <summary>
/// Occurs after ContentType is Imported and Saved
/// </summary>
public static event TypedEventHandler<IPackagingService, SaveEventArgs<IContentType>> ContentTypeImported;
public static event TypedEventHandler<IPackagingService, ImportEventArgs<IContentType>> ImportedContentType;
/// <summary>
/// Occurs before Exporting ContentType
/// </summary>
public static event TypedEventHandler<IPackagingService, SaveEventArgs<IContentType>> ExportingContentType;
public static event TypedEventHandler<IPackagingService, ExportEventArgs<IContentType>> ExportingContentType;
/// <summary>
/// Occurs after ContentType is Exported to Xml
/// </summary>
public static event TypedEventHandler<IPackagingService, SaveEventArgs<XElement>> ContentTypeExported;
public static event TypedEventHandler<IPackagingService, ExportEventArgs<IContentType>> ExportedContentType;
/// <summary>
/// Occurs before Importing DataType
/// </summary>
public static event TypedEventHandler<IPackagingService, SaveEventArgs<XElement>> ImportingDataType;
public static event TypedEventHandler<IPackagingService, ImportEventArgs<IDataTypeDefinition>> ImportingDataType;
/// <summary>
/// Occurs after DataType is Imported and Saved
/// </summary>
public static event TypedEventHandler<IPackagingService, SaveEventArgs<IDataTypeDefinition>> DataTypeImported;
public static event TypedEventHandler<IPackagingService, ImportEventArgs<IDataTypeDefinition>> ImportedDataType;
/// <summary>
/// Occurs before Exporting DataType
/// </summary>
public static event TypedEventHandler<IPackagingService, SaveEventArgs<IDataTypeDefinition>> ExportingDataType;
public static event TypedEventHandler<IPackagingService, ExportEventArgs<IDataTypeDefinition>> ExportingDataType;
/// <summary>
/// Occurs after DataType is Exported to Xml
/// </summary>
public static event TypedEventHandler<IPackagingService, SaveEventArgs<XElement>> DataTypeExported;
public static event TypedEventHandler<IPackagingService, ExportEventArgs<IDataTypeDefinition>> ExportedDataType;
/// <summary>
/// Occurs before Importing DictionaryItem
/// </summary>
public static event TypedEventHandler<IPackagingService, SaveEventArgs<XElement>> ImportingDictionaryItem;
public static event TypedEventHandler<IPackagingService, ImportEventArgs<IDictionaryItem>> ImportingDictionaryItem;
/// <summary>
/// Occurs after DictionaryItem is Imported and Saved
/// </summary>
public static event TypedEventHandler<IPackagingService, SaveEventArgs<IDictionaryItem>> DictionaryItemImported;
public static event TypedEventHandler<IPackagingService, ImportEventArgs<IDictionaryItem>> ImportedDictionaryItem;
/// <summary>
/// Occurs before Exporting DictionaryItem
/// </summary>
public static event TypedEventHandler<IPackagingService, SaveEventArgs<IDictionaryItem>> ExportingDictionaryItem;
public static event TypedEventHandler<IPackagingService, ExportEventArgs<IDictionaryItem>> ExportingDictionaryItem;
/// <summary>
/// Occurs after DictionaryItem is Exported to Xml
/// </summary>
public static event TypedEventHandler<IPackagingService, SaveEventArgs<XElement>> DictionaryItemExported;
public static event TypedEventHandler<IPackagingService, ExportEventArgs<IDictionaryItem>> ExportedDictionaryItem;
/// <summary>
/// Occurs before Importing Macro
/// </summary>
public static event TypedEventHandler<IPackagingService, ImportEventArgs<IMacro>> ImportingMacro;
/// <summary>
/// Occurs after Macro is Imported and Saved
/// </summary>
public static event TypedEventHandler<IPackagingService, ImportEventArgs<IMacro>> ImportedMacro;
/// <summary>
/// Occurs before Exporting Macro
/// </summary>
public static event TypedEventHandler<IPackagingService, ExportEventArgs<IMacro>> ExportingMacro;
/// <summary>
/// Occurs after Macro is Exported to Xml
/// </summary>
public static event TypedEventHandler<IPackagingService, ExportEventArgs<IMacro>> ExportedMacro;
/// <summary>
/// Occurs before Importing Language
/// </summary>
public static event TypedEventHandler<IPackagingService, SaveEventArgs<XElement>> ImportingLanguage;
public static event TypedEventHandler<IPackagingService, ImportEventArgs<ILanguage>> ImportingLanguage;
/// <summary>
/// Occurs after Language is Imported and Saved
/// </summary>
public static event TypedEventHandler<IPackagingService, SaveEventArgs<ILanguage>> LanguageImported;
public static event TypedEventHandler<IPackagingService, ImportEventArgs<ILanguage>> ImportedLanguage;
/// <summary>
/// Occurs before Exporting Language
/// </summary>
public static event TypedEventHandler<IPackagingService, SaveEventArgs<ILanguage>> ExportingLanguage;
public static event TypedEventHandler<IPackagingService, ExportEventArgs<ILanguage>> ExportingLanguage;
/// <summary>
/// Occurs after Language is Exported to Xml
/// </summary>
public static event TypedEventHandler<IPackagingService, SaveEventArgs<XElement>> LanguageExported;
public static event TypedEventHandler<IPackagingService, ExportEventArgs<ILanguage>> ExportedLanguage;
/// <summary>
/// Occurs before Importing Template
/// </summary>
public static event TypedEventHandler<IPackagingService, SaveEventArgs<XElement>> ImportingTemplate;
public static event TypedEventHandler<IPackagingService, ImportEventArgs<ITemplate>> ImportingTemplate;
/// <summary>
/// Occurs after Template is Imported and Saved
/// </summary>
public static event TypedEventHandler<IPackagingService, SaveEventArgs<ITemplate>> TemplateImported;
public static event TypedEventHandler<IPackagingService, ImportEventArgs<ITemplate>> ImportedTemplate;
/// <summary>
/// Occurs before Exporting Template
/// </summary>
public static event TypedEventHandler<IPackagingService, ExportEventArgs<ITemplate>> ExportingTemplate;
/// <summary>
/// Occurs after Template is Exported to Xml
/// </summary>
public static event TypedEventHandler<IPackagingService, ExportEventArgs<ITemplate>> ExportedTemplate;
#endregion
}
}
+11 -11
View File
@@ -337,7 +337,7 @@ namespace Umbraco.Core.Services
uow.Commit();
}
RelationSaved.RaiseEvent(new SaveEventArgs<IRelation>(relation, false), this);
SavedRelation.RaiseEvent(new SaveEventArgs<IRelation>(relation, false), this);
return relation;
}
@@ -365,7 +365,7 @@ namespace Umbraco.Core.Services
uow.Commit();
}
RelationSaved.RaiseEvent(new SaveEventArgs<IRelation>(relation, false), this);
SavedRelation.RaiseEvent(new SaveEventArgs<IRelation>(relation, false), this);
return relation;
}
@@ -428,7 +428,7 @@ namespace Umbraco.Core.Services
uow.Commit();
}
RelationSaved.RaiseEvent(new SaveEventArgs<IRelation>(relation, false), this);
SavedRelation.RaiseEvent(new SaveEventArgs<IRelation>(relation, false), this);
}
/// <summary>
@@ -447,7 +447,7 @@ namespace Umbraco.Core.Services
uow.Commit();
}
RelationTypeSaved.RaiseEvent(new SaveEventArgs<IRelationType>(relationType, false), this);
SavedRelationType.RaiseEvent(new SaveEventArgs<IRelationType>(relationType, false), this);
}
/// <summary>
@@ -466,7 +466,7 @@ namespace Umbraco.Core.Services
uow.Commit();
}
RelationDeleted.RaiseEvent(new DeleteEventArgs<IRelation>(relation, false), this);
DeletedRelation.RaiseEvent(new DeleteEventArgs<IRelation>(relation, false), this);
}
/// <summary>
@@ -485,7 +485,7 @@ namespace Umbraco.Core.Services
uow.Commit();
}
RelationTypeDeleted.RaiseEvent(new DeleteEventArgs<IRelationType>(relationType, false), this);
DeletedRelationType.RaiseEvent(new DeleteEventArgs<IRelationType>(relationType, false), this);
}
/// <summary>
@@ -508,7 +508,7 @@ namespace Umbraco.Core.Services
uow.Commit();
}
RelationDeleted.RaiseEvent(new DeleteEventArgs<IRelation>(relations, false), this);
DeletedRelation.RaiseEvent(new DeleteEventArgs<IRelation>(relations, false), this);
}
#region Private Methods
@@ -537,7 +537,7 @@ namespace Umbraco.Core.Services
/// <summary>
/// Occurs after a Relation is Deleted
/// </summary>
public static event TypedEventHandler<IRelationService, DeleteEventArgs<IRelation>> RelationDeleted;
public static event TypedEventHandler<IRelationService, DeleteEventArgs<IRelation>> DeletedRelation;
/// <summary>
/// Occurs before Saving a Relation
@@ -547,7 +547,7 @@ namespace Umbraco.Core.Services
/// <summary>
/// Occurs after a Relation is Saved
/// </summary>
public static event TypedEventHandler<IRelationService, SaveEventArgs<IRelation>> RelationSaved;
public static event TypedEventHandler<IRelationService, SaveEventArgs<IRelation>> SavedRelation;
/// <summary>
/// Occurs before Deleting a RelationType
@@ -557,7 +557,7 @@ namespace Umbraco.Core.Services
/// <summary>
/// Occurs after a RelationType is Deleted
/// </summary>
public static event TypedEventHandler<IRelationService, DeleteEventArgs<IRelationType>> RelationTypeDeleted;
public static event TypedEventHandler<IRelationService, DeleteEventArgs<IRelationType>> DeletedRelationType;
/// <summary>
/// Occurs before Saving a RelationType
@@ -567,7 +567,7 @@ namespace Umbraco.Core.Services
/// <summary>
/// Occurs after a RelationType is Saved
/// </summary>
public static event TypedEventHandler<IRelationService, SaveEventArgs<IRelationType>> RelationTypeSaved;
public static event TypedEventHandler<IRelationService, SaveEventArgs<IRelationType>> SavedRelationType;
#endregion
}
}
+13 -1
View File
@@ -29,6 +29,7 @@ namespace Umbraco.Core.Services
private Lazy<ISectionService> _sectionService;
private Lazy<IMacroService> _macroService;
private Lazy<IMemberTypeService> _memberTypeService;
private Lazy<INotificationService> _notificationService;
/// <summary>
/// public ctor - will generally just be used for unit testing
@@ -101,6 +102,9 @@ namespace Umbraco.Core.Services
var provider = dbUnitOfWorkProvider;
var fileProvider = fileUnitOfWorkProvider;
if (_notificationService == null)
_notificationService = new Lazy<INotificationService>(() => new NotificationService(provider, _userService.Value, _contentService.Value));
if (_serverRegistrationService == null)
_serverRegistrationService = new Lazy<ServerRegistrationService>(() => new ServerRegistrationService(provider, repositoryFactory.Value));
@@ -129,7 +133,7 @@ namespace Umbraco.Core.Services
_localizationService = new Lazy<ILocalizationService>(() => new LocalizationService(provider, repositoryFactory.Value));
if (_packagingService == null)
_packagingService = new Lazy<IPackagingService>(() => new PackagingService(_contentService.Value, _contentTypeService.Value, _mediaService.Value, _dataTypeService.Value, _fileService.Value, _localizationService.Value, repositoryFactory.Value, provider));
_packagingService = new Lazy<IPackagingService>(() => new PackagingService(_contentService.Value, _contentTypeService.Value, _mediaService.Value, _macroService.Value, _dataTypeService.Value, _fileService.Value, _localizationService.Value, repositoryFactory.Value, provider));
if (_entityService == null)
_entityService = new Lazy<IEntityService>(() => new EntityService(provider, repositoryFactory.Value, _contentService.Value, _contentTypeService.Value, _mediaService.Value, _dataTypeService.Value));
@@ -153,6 +157,14 @@ namespace Umbraco.Core.Services
_tagService = new Lazy<ITagService>(() => new TagService(provider, repositoryFactory.Value));
}
/// <summary>
/// Gets the <see cref="INotificationService"/>
/// </summary>
internal INotificationService NotificationService
{
get { return _notificationService.Value; }
}
/// <summary>
/// Gets the <see cref="ServerRegistrationService"/>
/// </summary>
+1 -1
View File
@@ -152,7 +152,7 @@ namespace Umbraco.Core.Services
{
using (var repository = _repositoryFactory.CreateUserRepository(_uowProvider.GetUnitOfWork()))
{
var query = Query<IUser>.Builder.Where(x => x.Username == login);
var query = Query<IUser>.Builder.Where(x => x.Username.Equals(login));
return repository.GetByQuery(query).FirstOrDefault();
}
}
+1 -1
View File
@@ -1084,7 +1084,7 @@ namespace Umbraco.Core
var cases2 = cases.ToCleanStringType() & CleanStringType.CaseMask;
return legacy != null
? legacy.LegacyConvertStringCase(phrase, cases2)
: helper.CleanString(phrase, CleanStringType.Ascii | CleanStringType.Alias | cases2);
: helper.CleanString(phrase, CleanStringType.Ascii | CleanStringType.ConvertCase | cases2);
}
// the new methods to clean a string (to alias, url segment...)
+21 -20
View File
@@ -14,25 +14,6 @@ namespace Umbraco.Core.Strings
// note: you have 32 bits at your disposal
// 0xffffffff
// masks
/// <summary>
/// Flag mask for casing.
/// </summary>
CaseMask = 0x3f, // 0xff - 8 possible values
/// <summary>
/// Flag mask for encoding.
/// </summary>
CodeMask = 0x700, // 0xff00 - 8 possible values
/// <summary>
/// Flag mask for role.
/// </summary>
RoleMask = 0x070000, // 0xff0000 - 8 possible values
// no value
/// <summary>
@@ -43,6 +24,11 @@ namespace Umbraco.Core.Strings
// casing values
/// <summary>
/// Flag mask for casing.
/// </summary>
CaseMask = PascalCase | CamelCase | Unchanged | LowerCase | UpperCase | UmbracoCase,
/// <summary>
/// Pascal casing eg "PascalCase".
/// </summary>
@@ -78,6 +64,11 @@ namespace Umbraco.Core.Strings
// encoding values
/// <summary>
/// Flag mask for encoding.
/// </summary>
CodeMask = Unicode | Utf8 | Ascii,
/// <summary>
/// Unicode encoding.
/// </summary>
@@ -97,6 +88,11 @@ namespace Umbraco.Core.Strings
// role values
/// <summary>
/// Flag mask for role.
/// </summary>
RoleMask = UrlSegment | Alias | FileName | ConvertCase,
/// <summary>
/// Url role.
/// </summary>
@@ -110,6 +106,11 @@ namespace Umbraco.Core.Strings
/// <summary>
/// FileName role.
/// </summary>
FileName = 0x040000
FileName = 0x040000,
/// <summary>
/// ConvertCase role.
/// </summary>
ConvertCase = 0x080000
}
}
@@ -1,4 +1,8 @@
using System;
// debugging - define to write cleaning details & steps to console
#define WRTCONS
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
@@ -142,7 +146,7 @@ namespace Umbraco.Core.Strings
{
PreFilter = ApplyUrlReplaceCharacters,
IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore
StringType = CleanStringType.Utf8 | CleanStringType.LowerCase,
StringType = (UmbracoConfig.For.UmbracoSettings().RequestHandler.ConvertUrlsToAscii ? CleanStringType.Ascii : CleanStringType.Utf8) | CleanStringType.LowerCase,
BreakTermsOnUpper = false,
Separator = '-'
}).WithConfig(CleanStringType.FileName, new Config
@@ -160,6 +164,12 @@ namespace Umbraco.Core.Strings
: (char.IsLetterOrDigit(c) || c == '_'), // letter, digit or underscore
StringType = CleanStringType.Ascii | CleanStringType.UmbracoCase,
BreakTermsOnUpper = false
}).WithConfig(CleanStringType.ConvertCase, new Config
{
PreFilter = null,
IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore
StringType = CleanStringType.Ascii,
BreakTermsOnUpper = true
});
}
@@ -487,6 +497,10 @@ function validateSafeAlias(id, value, immediate, callback) {{
if (culture == null)
throw new ArgumentNullException("culture");
#if WRTCONS
Console.WriteLine("STRING TYPE {0}", stringType);
#endif
// get config
var config = GetConfig(stringType, culture);
stringType = config.StringTypeExtend(stringType);
@@ -554,6 +568,9 @@ function validateSafeAlias(id, value, immediate, callback) {{
var state = StateBreak;
caseType &= CleanStringType.CaseMask;
#if WRTCONS
Console.WriteLine("CASE {0}", caseType);
#endif
// if we apply global ToUpper or ToLower to text here
// then we cannot break words on uppercase chars
@@ -579,13 +596,13 @@ function validateSafeAlias(id, value, immediate, callback) {{
var isPair = char.IsSurrogate(c);
if (isPair)
throw new NotSupportedException("Surrogate pairs are not supported.");
//Console.WriteLine("CHAR '{0}' {1} {2} - {3} - {4}/{5} {6}",
// c,
// isTerm ? "term" : "!term", isUpper ? "upper" : "!upper",
// state,
// i, ipos, leading ? "leading" : "!leading");
#if WRTCONS
Console.WriteLine("CHAR '{0}' {1} {2} - {3} - {4}/{5} {6}",
c,
isTerm ? "term" : "!term", isUpper ? "upper" : "!upper",
state,
i, ipos, leading ? "leading" : "!leading");
#endif
switch (state)
{
// within a break
@@ -692,11 +709,12 @@ function validateSafeAlias(id, value, immediate, callback) {{
CleanStringType caseType, CultureInfo culture, bool isAcronym)
{
var term = input.Substring(ipos, len);
//Console.WriteLine("TERM \"{0}\" {1} {2}",
// term,
// isAcronym ? "acronym" : "word",
// caseType);
#if WRTCONS
Console.WriteLine("TERM \"{0}\" {1} {2}",
term,
isAcronym ? "acronym" : "word",
caseType);
#endif
if (isAcronym)
{
if ((caseType == CleanStringType.CamelCase && len <= 2 && opos > 0) ||
+510
View File
@@ -0,0 +1,510 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace Umbraco.Core.Strings
{
/// <summary>
/// This Class implements the Difference Algorithm published in
/// "An O(ND) Difference Algorithm and its Variations" by Eugene Myers
/// Algorithmica Vol. 1 No. 2, 1986, p 251.
///
/// The algorithm itself is comparing 2 arrays of numbers so when comparing 2 text documents
/// each line is converted into a (hash) number. See DiffText().
///
/// diff.cs: A port of the algorithm to C#
/// Copyright (c) by Matthias Hertel, http://www.mathertel.de
/// This work is licensed under a BSD style license. See http://www.mathertel.de/License.aspx
/// </summary>
internal class Diff
{
/// <summary>Data on one input file being compared.
/// </summary>
internal class DiffData
{
/// <summary>Number of elements (lines).</summary>
internal int Length;
/// <summary>Buffer of numbers that will be compared.</summary>
internal int[] Data;
/// <summary>
/// Array of booleans that flag for modified data.
/// This is the result of the diff.
/// This means deletedA in the first Data or inserted in the second Data.
/// </summary>
internal bool[] Modified;
/// <summary>
/// Initialize the Diff-Data buffer.
/// </summary>
/// <param name="initData">reference to the buffer</param>
internal DiffData(int[] initData)
{
Data = initData;
Length = initData.Length;
Modified = new bool[Length + 2];
} // DiffData
} // class DiffData
/// <summary>details of one difference.</summary>
public struct Item
{
/// <summary>Start Line number in Data A.</summary>
public int StartA;
/// <summary>Start Line number in Data B.</summary>
public int StartB;
/// <summary>Number of changes in Data A.</summary>
public int DeletedA;
/// <summary>Number of changes in Data B.</summary>
public int InsertedB;
} // Item
/// <summary>
/// Shortest Middle Snake Return Data
/// </summary>
private struct Smsrd
{
internal int X, Y;
// internal int u, v; // 2002.09.20: no need for 2 points
}
/// <summary>
/// Find the difference in 2 texts, comparing by textlines.
/// </summary>
/// <param name="textA">A-version of the text (usualy the old one)</param>
/// <param name="textB">B-version of the text (usualy the new one)</param>
/// <returns>Returns a array of Items that describe the differences.</returns>
public static Item[] DiffText(string textA, string textB)
{
return (DiffText(textA, textB, false, false, false));
} // DiffText
/// <summary>
/// Find the difference in 2 texts, comparing by textlines.
/// This method uses the DiffInt internally by 1st converting the string into char codes
/// then uses the diff int method
/// </summary>
/// <param name="textA">A-version of the text (usualy the old one)</param>
/// <param name="textB">B-version of the text (usualy the new one)</param>
/// <returns>Returns a array of Items that describe the differences.</returns>
public static Item[] DiffText1(string textA, string textB)
{
return DiffInt(DiffCharCodes(textA, false), DiffCharCodes(textB, false));
}
/// <summary>
/// Find the difference in 2 text documents, comparing by textlines.
/// The algorithm itself is comparing 2 arrays of numbers so when comparing 2 text documents
/// each line is converted into a (hash) number. This hash-value is computed by storing all
/// textlines into a common hashtable so i can find dublicates in there, and generating a
/// new number each time a new textline is inserted.
/// </summary>
/// <param name="textA">A-version of the text (usualy the old one)</param>
/// <param name="textB">B-version of the text (usualy the new one)</param>
/// <param name="trimSpace">When set to true, all leading and trailing whitespace characters are stripped out before the comparation is done.</param>
/// <param name="ignoreSpace">When set to true, all whitespace characters are converted to a single space character before the comparation is done.</param>
/// <param name="ignoreCase">When set to true, all characters are converted to their lowercase equivivalence before the comparation is done.</param>
/// <returns>Returns a array of Items that describe the differences.</returns>
public static Item[] DiffText(string textA, string textB, bool trimSpace, bool ignoreSpace, bool ignoreCase)
{
// prepare the input-text and convert to comparable numbers.
var h = new Hashtable(textA.Length + textB.Length);
// The A-Version of the data (original data) to be compared.
var dataA = new DiffData(DiffCodes(textA, h, trimSpace, ignoreSpace, ignoreCase));
// The B-Version of the data (modified data) to be compared.
var dataB = new DiffData(DiffCodes(textB, h, trimSpace, ignoreSpace, ignoreCase));
h = null; // free up hashtable memory (maybe)
var max = dataA.Length + dataB.Length + 1;
// vector for the (0,0) to (x,y) search
var downVector = new int[2 * max + 2];
// vector for the (u,v) to (N,M) search
var upVector = new int[2 * max + 2];
Lcs(dataA, 0, dataA.Length, dataB, 0, dataB.Length, downVector, upVector);
Optimize(dataA);
Optimize(dataB);
return CreateDiffs(dataA, dataB);
} // DiffText
/// <summary>
/// Diffs the char codes.
/// </summary>
/// <param name="aText">A text.</param>
/// <param name="ignoreCase">if set to <c>true</c> [ignore case].</param>
/// <returns></returns>
private static int[] DiffCharCodes(string aText, bool ignoreCase)
{
if (ignoreCase)
aText = aText.ToUpperInvariant();
var codes = new int[aText.Length];
for (int n = 0; n < aText.Length; n++)
codes[n] = (int)aText[n];
return (codes);
} // DiffCharCodes
/// <summary>
/// If a sequence of modified lines starts with a line that contains the same content
/// as the line that appends the changes, the difference sequence is modified so that the
/// appended line and not the starting line is marked as modified.
/// This leads to more readable diff sequences when comparing text files.
/// </summary>
/// <param name="data">A Diff data buffer containing the identified changes.</param>
private static void Optimize(DiffData data)
{
var startPos = 0;
while (startPos < data.Length)
{
while ((startPos < data.Length) && (data.Modified[startPos] == false))
startPos++;
int endPos = startPos;
while ((endPos < data.Length) && (data.Modified[endPos] == true))
endPos++;
if ((endPos < data.Length) && (data.Data[startPos] == data.Data[endPos]))
{
data.Modified[startPos] = false;
data.Modified[endPos] = true;
}
else
{
startPos = endPos;
} // if
} // while
} // Optimize
/// <summary>
/// Find the difference in 2 arrays of integers.
/// </summary>
/// <param name="arrayA">A-version of the numbers (usualy the old one)</param>
/// <param name="arrayB">B-version of the numbers (usualy the new one)</param>
/// <returns>Returns a array of Items that describe the differences.</returns>
public static Item[] DiffInt(int[] arrayA, int[] arrayB)
{
// The A-Version of the data (original data) to be compared.
var dataA = new DiffData(arrayA);
// The B-Version of the data (modified data) to be compared.
var dataB = new DiffData(arrayB);
var max = dataA.Length + dataB.Length + 1;
// vector for the (0,0) to (x,y) search
var downVector = new int[2 * max + 2];
// vector for the (u,v) to (N,M) search
var upVector = new int[2 * max + 2];
Lcs(dataA, 0, dataA.Length, dataB, 0, dataB.Length, downVector, upVector);
return CreateDiffs(dataA, dataB);
} // Diff
/// <summary>
/// This function converts all textlines of the text into unique numbers for every unique textline
/// so further work can work only with simple numbers.
/// </summary>
/// <param name="aText">the input text</param>
/// <param name="h">This extern initialized hashtable is used for storing all ever used textlines.</param>
/// <param name="trimSpace">ignore leading and trailing space characters</param>
/// <param name="ignoreSpace"></param>
/// <param name="ignoreCase"></param>
/// <returns>a array of integers.</returns>
private static int[] DiffCodes(string aText, IDictionary h, bool trimSpace, bool ignoreSpace, bool ignoreCase)
{
// get all codes of the text
var lastUsedCode = h.Count;
// strip off all cr, only use lf as textline separator.
aText = aText.Replace("\r", "");
var lines = aText.Split('\n');
var codes = new int[lines.Length];
for (int i = 0; i < lines.Length; ++i)
{
string s = lines[i];
if (trimSpace)
s = s.Trim();
if (ignoreSpace)
{
s = Regex.Replace(s, "\\s+", " "); // TODO: optimization: faster blank removal.
}
if (ignoreCase)
s = s.ToLower();
object aCode = h[s];
if (aCode == null)
{
lastUsedCode++;
h[s] = lastUsedCode;
codes[i] = lastUsedCode;
}
else
{
codes[i] = (int)aCode;
} // if
} // for
return (codes);
} // DiffCodes
/// <summary>
/// This is the algorithm to find the Shortest Middle Snake (SMS).
/// </summary>
/// <param name="dataA">sequence A</param>
/// <param name="lowerA">lower bound of the actual range in DataA</param>
/// <param name="upperA">upper bound of the actual range in DataA (exclusive)</param>
/// <param name="dataB">sequence B</param>
/// <param name="lowerB">lower bound of the actual range in DataB</param>
/// <param name="upperB">upper bound of the actual range in DataB (exclusive)</param>
/// <param name="downVector">a vector for the (0,0) to (x,y) search. Passed as a parameter for speed reasons.</param>
/// <param name="upVector">a vector for the (u,v) to (N,M) search. Passed as a parameter for speed reasons.</param>
/// <returns>a MiddleSnakeData record containing x,y and u,v</returns>
private static Smsrd Sms(DiffData dataA, int lowerA, int upperA, DiffData dataB, int lowerB, int upperB, int[] downVector, int[] upVector)
{
int max = dataA.Length + dataB.Length + 1;
int downK = lowerA - lowerB; // the k-line to start the forward search
int upK = upperA - upperB; // the k-line to start the reverse search
int delta = (upperA - lowerA) - (upperB - lowerB);
bool oddDelta = (delta & 1) != 0;
// The vectors in the publication accepts negative indexes. the vectors implemented here are 0-based
// and are access using a specific offset: UpOffset UpVector and DownOffset for DownVektor
int downOffset = max - downK;
int upOffset = max - upK;
int maxD = ((upperA - lowerA + upperB - lowerB) / 2) + 1;
// Debug.Write(2, "SMS", String.Format("Search the box: A[{0}-{1}] to B[{2}-{3}]", LowerA, UpperA, LowerB, UpperB));
// init vectors
downVector[downOffset + downK + 1] = lowerA;
upVector[upOffset + upK - 1] = upperA;
for (int d = 0; d <= maxD; d++)
{
// Extend the forward path.
Smsrd ret;
for (int k = downK - d; k <= downK + d; k += 2)
{
// Debug.Write(0, "SMS", "extend forward path " + k.ToString());
// find the only or better starting point
int x, y;
if (k == downK - d)
{
x = downVector[downOffset + k + 1]; // down
}
else
{
x = downVector[downOffset + k - 1] + 1; // a step to the right
if ((k < downK + d) && (downVector[downOffset + k + 1] >= x))
x = downVector[downOffset + k + 1]; // down
}
y = x - k;
// find the end of the furthest reaching forward D-path in diagonal k.
while ((x < upperA) && (y < upperB) && (dataA.Data[x] == dataB.Data[y]))
{
x++; y++;
}
downVector[downOffset + k] = x;
// overlap ?
if (oddDelta && (upK - d < k) && (k < upK + d))
{
if (upVector[upOffset + k] <= downVector[downOffset + k])
{
ret.X = downVector[downOffset + k];
ret.Y = downVector[downOffset + k] - k;
// ret.u = UpVector[UpOffset + k]; // 2002.09.20: no need for 2 points
// ret.v = UpVector[UpOffset + k] - k;
return (ret);
} // if
} // if
} // for k
// Extend the reverse path.
for (int k = upK - d; k <= upK + d; k += 2)
{
// Debug.Write(0, "SMS", "extend reverse path " + k.ToString());
// find the only or better starting point
int x, y;
if (k == upK + d)
{
x = upVector[upOffset + k - 1]; // up
}
else
{
x = upVector[upOffset + k + 1] - 1; // left
if ((k > upK - d) && (upVector[upOffset + k - 1] < x))
x = upVector[upOffset + k - 1]; // up
} // if
y = x - k;
while ((x > lowerA) && (y > lowerB) && (dataA.Data[x - 1] == dataB.Data[y - 1]))
{
x--; y--; // diagonal
}
upVector[upOffset + k] = x;
// overlap ?
if (!oddDelta && (downK - d <= k) && (k <= downK + d))
{
if (upVector[upOffset + k] <= downVector[downOffset + k])
{
ret.X = downVector[downOffset + k];
ret.Y = downVector[downOffset + k] - k;
// ret.u = UpVector[UpOffset + k]; // 2002.09.20: no need for 2 points
// ret.v = UpVector[UpOffset + k] - k;
return (ret);
} // if
} // if
} // for k
} // for D
throw new ApplicationException("the algorithm should never come here.");
} // SMS
/// <summary>
/// This is the divide-and-conquer implementation of the longes common-subsequence (LCS)
/// algorithm.
/// The published algorithm passes recursively parts of the A and B sequences.
/// To avoid copying these arrays the lower and upper bounds are passed while the sequences stay constant.
/// </summary>
/// <param name="dataA">sequence A</param>
/// <param name="lowerA">lower bound of the actual range in DataA</param>
/// <param name="upperA">upper bound of the actual range in DataA (exclusive)</param>
/// <param name="dataB">sequence B</param>
/// <param name="lowerB">lower bound of the actual range in DataB</param>
/// <param name="upperB">upper bound of the actual range in DataB (exclusive)</param>
/// <param name="downVector">a vector for the (0,0) to (x,y) search. Passed as a parameter for speed reasons.</param>
/// <param name="upVector">a vector for the (u,v) to (N,M) search. Passed as a parameter for speed reasons.</param>
private static void Lcs(DiffData dataA, int lowerA, int upperA, DiffData dataB, int lowerB, int upperB, int[] downVector, int[] upVector)
{
// Debug.Write(2, "LCS", String.Format("Analyse the box: A[{0}-{1}] to B[{2}-{3}]", LowerA, UpperA, LowerB, UpperB));
// Fast walkthrough equal lines at the start
while (lowerA < upperA && lowerB < upperB && dataA.Data[lowerA] == dataB.Data[lowerB])
{
lowerA++; lowerB++;
}
// Fast walkthrough equal lines at the end
while (lowerA < upperA && lowerB < upperB && dataA.Data[upperA - 1] == dataB.Data[upperB - 1])
{
--upperA; --upperB;
}
if (lowerA == upperA)
{
// mark as inserted lines.
while (lowerB < upperB)
dataB.Modified[lowerB++] = true;
}
else if (lowerB == upperB)
{
// mark as deleted lines.
while (lowerA < upperA)
dataA.Modified[lowerA++] = true;
}
else
{
// Find the middle snakea and length of an optimal path for A and B
Smsrd smsrd = Sms(dataA, lowerA, upperA, dataB, lowerB, upperB, downVector, upVector);
// Debug.Write(2, "MiddleSnakeData", String.Format("{0},{1}", smsrd.x, smsrd.y));
// The path is from LowerX to (x,y) and (x,y) to UpperX
Lcs(dataA, lowerA, smsrd.X, dataB, lowerB, smsrd.Y, downVector, upVector);
Lcs(dataA, smsrd.X, upperA, dataB, smsrd.Y, upperB, downVector, upVector); // 2002.09.20: no need for 2 points
}
} // LCS()
/// <summary>Scan the tables of which lines are inserted and deleted,
/// producing an edit script in forward order.
/// </summary>
/// dynamic array
private static Item[] CreateDiffs(DiffData dataA, DiffData dataB)
{
ArrayList a = new ArrayList();
Item aItem;
Item[] result;
int lineA = 0;
int lineB = 0;
while (lineA < dataA.Length || lineB < dataB.Length)
{
if ((lineA < dataA.Length) && (!dataA.Modified[lineA])
&& (lineB < dataB.Length) && (!dataB.Modified[lineB]))
{
// equal lines
lineA++;
lineB++;
}
else
{
// maybe deleted and/or inserted lines
int startA = lineA;
int startB = lineB;
while (lineA < dataA.Length && (lineB >= dataB.Length || dataA.Modified[lineA]))
// while (LineA < DataA.Length && DataA.modified[LineA])
lineA++;
while (lineB < dataB.Length && (lineA >= dataA.Length || dataB.Modified[lineB]))
// while (LineB < DataB.Length && DataB.modified[LineB])
lineB++;
if ((startA < lineA) || (startB < lineB))
{
// store a new difference-item
aItem = new Item();
aItem.StartA = startA;
aItem.StartB = startB;
aItem.DeletedA = lineA - startA;
aItem.InsertedB = lineB - startB;
a.Add(aItem);
} // if
} // if
} // while
result = new Item[a.Count];
a.CopyTo(result);
return (result);
}
} // class Diff
}
@@ -3316,6 +3316,243 @@ namespace Umbraco.Core.Strings
output[opos++] = '~';
break;
// BEGIN CUSTOM TRANSLITERATION OF CYRILIC CHARS
#region Cyrilic chars
// russian uppercase "А Б В Г Д Е Ё Ж З И Й К Л М Н О П Р С Т У Ф Х Ц Ч Ш Щ Ъ Ы Ь Э Ю Я"
// russian lowercase "а б в г д е ё ж з и й к л м н о п р с т у ф х ц ч ш щ ъ ы ь э ю я"
// notes
// read http://www.vesic.org/english/blog/c-sharp/transliteration-easy-way-microsoft-transliteration-utility/
// should we look into MS Transliteration Utility (http://msdn.microsoft.com/en-US/goglobal/bb688104.aspx)
// also UnicodeSharpFork https://bitbucket.org/DimaStefantsov/unidecodesharpfork
// also Transliterator http://transliterator.codeplex.com/
//
// in any case it would be good to generate all those "case" statements instead of writing them by hand
// time for a T4 template?
// also we should support extensibility so ppl can register more cases in external code
// fixme
// transliterates Анастасия as Anastasiya, and not Anastasia
// Ольга --> Ol'ga, Татьяна --> Tat'yana -- that's bad (?)
// Note: should ä (german umlaut) become a or ae ?
case '\u0410': // А
output[opos++] = 'A';
break;
case '\u0430': // а
output[opos++] = 'a';
break;
case '\u0411': // Б
output[opos++] = 'B';
break;
case '\u0431': // б
output[opos++] = 'b';
break;
case '\u0412': // В
output[opos++] = 'V';
break;
case '\u0432': // в
output[opos++] = 'v';
break;
case '\u0413': // Г
output[opos++] = 'G';
break;
case '\u0433': // г
output[opos++] = 'g';
break;
case '\u0414': // Д
output[opos++] = 'D';
break;
case '\u0434': // д
output[opos++] = 'd';
break;
case '\u0415': // Е
output[opos++] = 'E';
break;
case '\u0435': // е
output[opos++] = 'e';
break;
case '\u0401': // Ё
output[opos++] = 'E'; // alt. Yo
break;
case '\u0451': // ё
output[opos++] = 'e'; // alt. yo
break;
case '\u0416': // Ж
output[opos++] = 'Z';
output[opos++] = 'h';
break;
case '\u0436': // ж
output[opos++] = 'z';
output[opos++] = 'h';
break;
case '\u0417': // З
output[opos++] = 'Z';
break;
case '\u0437': // з
output[opos++] = 'z';
break;
case '\u0418': // И
output[opos++] = 'I';
break;
case '\u0438': // и
output[opos++] = 'i';
break;
case '\u0419': // Й
output[opos++] = 'I'; // alt. Y, J
break;
case '\u0439': // й
output[opos++] = 'i'; // alt. y, j
break;
case '\u041A': // К
output[opos++] = 'K';
break;
case '\u043A': // к
output[opos++] = 'k';
break;
case '\u041B': // Л
output[opos++] = 'L';
break;
case '\u043B': // л
output[opos++] = 'l';
break;
case '\u041C': // М
output[opos++] = 'M';
break;
case '\u043C': // м
output[opos++] = 'm';
break;
case '\u041D': // Н
output[opos++] = 'N';
break;
case '\u043D': // н
output[opos++] = 'n';
break;
case '\u041E': // О
output[opos++] = 'O';
break;
case '\u043E': // о
output[opos++] = 'o';
break;
case '\u041F': // П
output[opos++] = 'P';
break;
case '\u043F': // п
output[opos++] = 'p';
break;
case '\u0420': // Р
output[opos++] = 'R';
break;
case '\u0440': // р
output[opos++] = 'r';
break;
case '\u0421': // С
output[opos++] = 'S';
break;
case '\u0441': // с
output[opos++] = 's';
break;
case '\u0422': // Т
output[opos++] = 'T';
break;
case '\u0442': // т
output[opos++] = 't';
break;
case '\u0423': // У
output[opos++] = 'U';
break;
case '\u0443': // у
output[opos++] = 'u';
break;
case '\u0424': // Ф
output[opos++] = 'F';
break;
case '\u0444': // ф
output[opos++] = 'f';
break;
case '\u0425': // Х
output[opos++] = 'K'; // alt. X
output[opos++] = 'h';
break;
case '\u0445': // х
output[opos++] = 'k'; // alt. x
output[opos++] = 'h';
break;
case '\u0426': // Ц
output[opos++] = 'F';
break;
case '\u0446': // ц
output[opos++] = 'f';
break;
case '\u0427': // Ч
output[opos++] = 'C'; // alt. Ts, C
output[opos++] = 'h';
break;
case '\u0447': // ч
output[opos++] = 'c'; // alt. ts, c
output[opos++] = 'h';
break;
case '\u0428': // Ш
output[opos++] = 'S'; // alt. Ch, S
output[opos++] = 'h';
break;
case '\u0448': // ш
output[opos++] = 's'; // alt. ch, s
output[opos++] = 'h';
break;
case '\u0429': // Щ
output[opos++] = 'S'; // alt. Shch, Sc
output[opos++] = 'h';
break;
case '\u0449': // щ
output[opos++] = 's'; // alt. shch, sc
output[opos++] = 'h';
break;
case '\u042A': // Ъ
output[opos++] = '"'; // "
break;
case '\u044A': // ъ
output[opos++] = '"'; // "
break;
case '\u042B': // Ы
output[opos++] = 'Y';
break;
case '\u044B': // ы
output[opos++] = 'y';
break;
case '\u042C': // Ь
output[opos++] = '\''; // '
break;
case '\u044C': // ь
output[opos++] = '\''; // '
break;
case '\u042D': // Э
output[opos++] = 'E';
break;
case '\u044D': // э
output[opos++] = 'e';
break;
case '\u042E': // Ю
output[opos++] = 'Y'; // alt. Ju
output[opos++] = 'u';
break;
case '\u044E': // ю
output[opos++] = 'y'; // alt. ju
output[opos++] = 'u';
break;
case '\u042F': // Я
output[opos++] = 'Y'; // alt. Ja
output[opos++] = 'a';
break;
case '\u044F': // я
output[opos++] = 'y'; // alt. ja
output[opos++] = 'a';
break;
#endregion
// BEGIN EXTRA
/*
case '£':
+8
View File
@@ -287,6 +287,8 @@
<Compile Include="Events\CancellableObjectEventArgs.cs" />
<Compile Include="Events\DeleteRevisionsEventArgs.cs" />
<Compile Include="Events\EventExtensions.cs" />
<Compile Include="Events\ExportEventArgs.cs" />
<Compile Include="Events\ImportEventArgs.cs" />
<Compile Include="Events\MigrationEventArgs.cs" />
<Compile Include="Events\PublishEventArgs.cs" />
<Compile Include="Events\RecycleBinEventArgs.cs" />
@@ -339,6 +341,7 @@
<Compile Include="Models\PublishedContent\PublishedContentModelFactoryResolver.cs" />
<Compile Include="Models\TaggableObjectTypes.cs" />
<Compile Include="Models\TemplateNode.cs" />
<Compile Include="Models\UmbracoEntityExtensions.cs" />
<Compile Include="Packaging\PackageBinaryInspector.cs" />
<Compile Include="PropertyEditors\DefaultPropertyValueConverterAttribute.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSeven\UpdateRelatedLinksData.cs" />
@@ -952,6 +955,7 @@
<Compile Include="Models\Section.cs" />
<Compile Include="Security\AuthenticationExtensions.cs" />
<Compile Include="Security\MembershipProviderBase.cs" />
<Compile Include="Security\MembershipProviderExtensions.cs" />
<Compile Include="Security\UmbracoBackOfficeIdentity.cs" />
<Compile Include="Security\UmbracoMembershipProviderBase.cs" />
<Compile Include="Security\UserData.cs" />
@@ -1011,6 +1015,7 @@
<Compile Include="Standalone\StandaloneCoreApplication.cs" />
<Compile Include="Standalone\StandaloneCoreBootManager.cs" />
<Compile Include="Strings\ContentBaseExtensions.cs" />
<Compile Include="Strings\Diff.cs" />
<Compile Include="TopologicalSorter.cs" />
<Compile Include="Strings\DefaultUrlSegmentProvider.cs" />
<Compile Include="Strings\IUrlSegmentProvider.cs" />
@@ -1059,6 +1064,9 @@
<Compile Include="Constants-System.cs">
<DependentUpon>Constants.cs</DependentUpon>
</Compile>
<Compile Include="Constants-Web.cs">
<DependentUpon>Constants.cs</DependentUpon>
</Compile>
<Compile Include="UpgradeableReadLock.cs" />
<Compile Include="DelegateEqualityComparer.cs" />
<Compile Include="Media\IEmbedProvider.cs" />
+78 -9
View File
@@ -14,24 +14,93 @@ namespace Umbraco.Core
/// </summary>
public static class UriExtensions
{
/// <summary>
/// Checks if the current uri is a back office request
/// </summary>
/// <param name="url"></param>
/// <param name="applicationPath">
/// The current application path or VirtualPath
/// </param>
/// <returns></returns>
internal static bool IsBackOfficeRequest(this Uri url)
/// <remarks>
/// There are some special routes we need to check to properly determine this:
///
/// If any route has an extension in the path like .aspx = back office
///
/// These are def back office:
/// /Umbraco/RestServices = back office
/// /Umbraco/BackOffice = back office
/// If it's not any of the above, and there's no extension then we cannot determine if it's back office or front-end
/// so we can only assume that it is not back office. This will occur if people use an UmbracoApiController for the backoffice
/// but do not inherit from UmbracoAuthorizedApiController and do not use [IsBackOffice] attribute.
///
/// These are def front-end:
/// /Umbraco/Surface = front-end
/// /Umbraco/Api = front-end
/// But if we've got this far we'll just have to assume it's front-end anyways.
///
/// </remarks>
internal static bool IsBackOfficeRequest(this Uri url, string applicationPath)
{
var authority = url.GetLeftPart(UriPartial.Authority);
var afterAuthority = url.GetLeftPart(UriPartial.Query)
.TrimStart(authority)
.TrimStart("/");
applicationPath = applicationPath ?? string.Empty;
var fullUrlPath = url.AbsolutePath.TrimStart(new[] {'/'});
var appPath = applicationPath.TrimStart(new[] {'/'});
var urlPath = fullUrlPath.TrimStart(appPath).EnsureStartsWith('/');
//check if this is in the umbraco back office
return afterAuthority.InvariantStartsWith(GlobalSettings.Path.TrimStart("/"));
var isUmbracoPath = urlPath.InvariantStartsWith(GlobalSettings.Path.EnsureStartsWith('/'));
//if not, then def not back office
if (isUmbracoPath == false) return false;
//if its the normal /umbraco path
if (urlPath.InvariantEquals("/" + GlobalSettings.UmbracoMvcArea)
|| urlPath.InvariantEquals("/" + GlobalSettings.UmbracoMvcArea + "/"))
{
return true;
}
//check for a file extension
var extension = Path.GetExtension(url.LocalPath);
//has an extension, def back office
if (extension.IsNullOrWhiteSpace() == false) return true;
//check for special case asp.net calls like:
// /umbraco/webservices/legacyAjaxCalls.asmx/js which will return a null file extension but are still considered extension'd requests
if (urlPath.InvariantContains(".asmx/")
|| urlPath.InvariantContains(".aspx/")
|| urlPath.InvariantContains(".ashx/")
|| urlPath.InvariantContains(".axd/")
|| urlPath.InvariantContains(".svc/"))
{
return true;
}
//check for special back office paths
if (urlPath.InvariantStartsWith("/" + GlobalSettings.UmbracoMvcArea + "/BackOffice/")
|| urlPath.InvariantStartsWith("/" + GlobalSettings.UmbracoMvcArea + "/RestServices/"))
{
return true;
}
//check for special front-end paths
if (urlPath.InvariantStartsWith("/" + GlobalSettings.UmbracoMvcArea + "/Surface/")
|| urlPath.InvariantStartsWith("/" + GlobalSettings.UmbracoMvcArea + "/Api/"))
{
return false;
}
//if its none of the above, we will have to try to detect if it's a PluginController route, we can detect this by
// checking how many parts the route has, for example, all PluginController routes will be routed like
// Umbraco/MYPLUGINAREA/MYCONTROLLERNAME/{action}/{id}
// so if the path contains at a minimum 3 parts: Umbraco + MYPLUGINAREA + MYCONTROLLERNAME then we will have to assume it is a
// plugin controller for the front-end.
if (urlPath.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries).Length >= 3)
{
return false;
}
//if its anything else we can assume it's back office
return true;
}
/// <summary>
@@ -6,8 +6,12 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Moq;
using NUnit.Framework;
using umbraco;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Strings;
using Umbraco.Core.ObjectResolution;
using Umbraco.Tests.TestHelpers;
@@ -65,6 +69,13 @@ namespace Umbraco.Tests.CoreStrings
PreFilter = WhiteQuotes,
IsTerm = (c, leading) => leading ? char.IsLetter(c) : char.IsLetterOrDigit(c),
StringType = CleanStringType.UmbracoCase | CleanStringType.Ascii
})
.WithConfig(CleanStringType.ConvertCase, new DefaultShortStringHelper.Config
{
PreFilter = null,
IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore
StringType = CleanStringType.Ascii,
BreakTermsOnUpper = true
});
ShortStringHelperResolver.Reset();
@@ -98,6 +109,47 @@ namespace Umbraco.Tests.CoreStrings
return s;
}
[Test]
public void U4_4055_4056()
{
var settings = SettingsForTests.GenerateMockSettings();
var contentMock = Mock.Get(settings.RequestHandler);
contentMock.Setup(x => x.CharCollection).Returns(Enumerable.Empty<IChar>());
contentMock.Setup(x => x.ConvertUrlsToAscii).Returns(false);
SettingsForTests.ConfigureSettings(settings);
const string input = "publishedVersion";
Assert.AreEqual("PublishedVersion", input.ConvertCase(StringAliasCaseType.PascalCase)); // obsolete, use the one below
Assert.AreEqual("PublishedVersion", input.ToCleanString(CleanStringType.ConvertCase | CleanStringType.PascalCase | CleanStringType.Ascii)); // role, case and code
}
[Test]
public void U4_4056()
{
var settings = SettingsForTests.GenerateMockSettings();
var contentMock = Mock.Get(settings.RequestHandler);
contentMock.Setup(x => x.CharCollection).Returns(Enumerable.Empty<IChar>());
contentMock.Setup(x => x.ConvertUrlsToAscii).Returns(false);
SettingsForTests.ConfigureSettings(settings);
const string input = "ÆØÅ and æøå and 中文测试 and אודות האתר and größer БбДдЖж page";
var helper = new DefaultShortStringHelper().WithDefaultConfig(); // unicode
var output = helper.CleanStringForUrlSegment(input);
Assert.AreEqual("æøå-and-æøå-and-中文测试-and-אודות-האתר-and-größer-ббдджж-page", output);
helper = new DefaultShortStringHelper()
.WithConfig(CleanStringType.UrlSegment, new DefaultShortStringHelper.Config
{
IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_',
StringType = CleanStringType.LowerCase | CleanStringType.Ascii, // ascii
Separator = '-'
});
output = helper.CleanStringForUrlSegment(input);
Assert.AreEqual("aeoa-and-aeoa-and-and-and-grosser-bbddzhzh-page", output);
}
[Test]
public void CleanStringUnderscoreInTerm()
{
@@ -361,6 +413,12 @@ namespace Umbraco.Tests.CoreStrings
[Test]
public void CleanStringDefaultConfig()
{
var settings = SettingsForTests.GenerateMockSettings();
var contentMock = Mock.Get(settings.RequestHandler);
contentMock.Setup(x => x.CharCollection).Returns(Enumerable.Empty<IChar>());
contentMock.Setup(x => x.ConvertUrlsToAscii).Returns(false);
SettingsForTests.ConfigureSettings(settings);
var helper = new DefaultShortStringHelper().WithDefaultConfig();
const string input = "0123 中文测试 中文测试 léger ZÔRG (2) a?? *x";
+85 -7
View File
@@ -16,6 +16,29 @@ namespace Umbraco.Tests.Macros
Assert.AreEqual(@"<p>hello world</p>", content);
}
[Test]
public void Format_RTE_Data_For_Editor_With_Non_AlphaNumeric_Char_In_Alias()
{
var content = @"<p>asdfasdf</p>
<p>asdfsadf</p>
<?UMBRACO_MACRO macroAlias=""My.Map.isCool"" />
<p>asdfasdf</p>";
var result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary<string, string>() { { "test1", "value1" }, { "test2", "value2" } });
// Assert.AreEqual(@"<p>asdfasdf</p>
//<p>asdfsadf</p>
//<div class=""umb-macro-holder Map mceNonEditable"" test1=""value1"" test2=""value2"">
//<!-- <?UMBRACO_MACRO macroAlias=""Map"" /> -->
//<ins>Macro alias: <strong>Map</strong></ins></div>
//<p>asdfasdf</p>".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty));
Assert.AreEqual(@"<p>asdfasdf</p>
<p>asdfsadf</p>
<div class=""umb-macro-holder mceNonEditable"" test1=""value1"" test2=""value2"">
<!-- <?UMBRACO_MACRO macroAlias=""My.Map.isCool"" /> -->
<ins>Macro alias: <strong>My.Map.isCool</strong></ins></div>
<p>asdfasdf</p>".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty));
}
[Test]
public void Format_RTE_Data_For_Editor()
{
@@ -25,9 +48,16 @@ namespace Umbraco.Tests.Macros
<p>asdfasdf</p>";
var result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary<string, string>(){{"test1", "value1"},{"test2", "value2"}});
// Assert.AreEqual(@"<p>asdfasdf</p>
//<p>asdfsadf</p>
//<div class=""umb-macro-holder Map mceNonEditable"" test1=""value1"" test2=""value2"">
//<!-- <?UMBRACO_MACRO macroAlias=""Map"" /> -->
//<ins>Macro alias: <strong>Map</strong></ins></div>
//<p>asdfasdf</p>".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty));
Assert.AreEqual(@"<p>asdfasdf</p>
<p>asdfsadf</p>
<div class=""umb-macro-holder Map mceNonEditable"" test1=""value1"" test2=""value2"">
<div class=""umb-macro-holder mceNonEditable"" test1=""value1"" test2=""value2"">
<!-- <?UMBRACO_MACRO macroAlias=""Map"" /> -->
<ins>Macro alias: <strong>Map</strong></ins></div>
<p>asdfasdf</p>".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty));
@@ -42,9 +72,15 @@ namespace Umbraco.Tests.Macros
<p>asdfasdf</p>";
var result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary<string, string>() { { "test1", "value1" }, { "test2", "value2" } });
// Assert.AreEqual(@"<p>asdfasdf</p>
//<p>asdfsadf</p>
//<div class=""umb-macro-holder Map mceNonEditable"" test1=""value1"" test2=""value2"">
//<!-- <?UMBRACO_MACRO macroAlias=""Map"" /> -->
//<ins>Macro alias: <strong>Map</strong></ins></div>
//<p>asdfasdf</p>".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty));
Assert.AreEqual(@"<p>asdfasdf</p>
<p>asdfsadf</p>
<div class=""umb-macro-holder Map mceNonEditable"" test1=""value1"" test2=""value2"">
<div class=""umb-macro-holder mceNonEditable"" test1=""value1"" test2=""value2"">
<!-- <?UMBRACO_MACRO macroAlias=""Map"" /> -->
<ins>Macro alias: <strong>Map</strong></ins></div>
<p>asdfasdf</p>".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty));
@@ -59,9 +95,15 @@ namespace Umbraco.Tests.Macros
<p>asdfasdf</p>";
var result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary<string, string>() { { "test1", "value1" }, { "test2", "value2" } });
// Assert.AreEqual(@"<p>asdfasdf</p>
//<p>asdfsadf</p>
//<div class=""umb-macro-holder Map mceNonEditable"" test1=""value1"" test2=""value2"">
//<!-- <?UMBRACO_MACRO macroAlias=""Map"" test1=""value1"" test2=""value2"" /> -->
//<ins>Macro alias: <strong>Map</strong></ins></div>
//<p>asdfasdf</p>".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty));
Assert.AreEqual(@"<p>asdfasdf</p>
<p>asdfsadf</p>
<div class=""umb-macro-holder Map mceNonEditable"" test1=""value1"" test2=""value2"">
<div class=""umb-macro-holder mceNonEditable"" test1=""value1"" test2=""value2"">
<!-- <?UMBRACO_MACRO macroAlias=""Map"" test1=""value1"" test2=""value2"" /> -->
<ins>Macro alias: <strong>Map</strong></ins></div>
<p>asdfasdf</p>".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty));
@@ -76,9 +118,15 @@ namespace Umbraco.Tests.Macros
<p>asdfasdf</p>";
var result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary<string, string>() { { "test1", "value1" }, { "test2", "value2" } });
// Assert.AreEqual(@"<p>asdfasdf</p>
//<p>asdfsadf</p>
//<div class=""umb-macro-holder Map mceNonEditable"" test1=""value1"" test2=""value2"">
//<!-- <?UMBRACO_MACRO macroAlias=""Map"" test1=""value1"" test2=""value2"" /> -->
//<ins>Macro alias: <strong>Map</strong></ins></div>
//<p>asdfasdf</p>".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty));
Assert.AreEqual(@"<p>asdfasdf</p>
<p>asdfsadf</p>
<div class=""umb-macro-holder Map mceNonEditable"" test1=""value1"" test2=""value2"">
<div class=""umb-macro-holder mceNonEditable"" test1=""value1"" test2=""value2"">
<!-- <?UMBRACO_MACRO macroAlias=""Map"" test1=""value1"" test2=""value2"" /> -->
<ins>Macro alias: <strong>Map</strong></ins></div>
<p>asdfasdf</p>".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty));
@@ -93,9 +141,15 @@ namespace Umbraco.Tests.Macros
<p>asdfasdf</p>";
var result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary<string, string>() { { "test1", "value1" }, { "test2", "value2" } });
// Assert.AreEqual(@"<p>asdfasdf</p>
//<p>asdfsadf</p>
//<div class=""umb-macro-holder Map mceNonEditable"" test1=""value1"" test2=""value2"">
//<!-- <?UMBRACO_MACRO macroAlias=""Map"" test1=""value1"" test2=""value2"" /> -->
//<ins>Macro alias: <strong>Map</strong></ins></div>
//<p>asdfasdf</p>".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty));
Assert.AreEqual(@"<p>asdfasdf</p>
<p>asdfsadf</p>
<div class=""umb-macro-holder Map mceNonEditable"" test1=""value1"" test2=""value2"">
<div class=""umb-macro-holder mceNonEditable"" test1=""value1"" test2=""value2"">
<!-- <?UMBRACO_MACRO macroAlias=""Map"" test1=""value1"" test2=""value2"" /> -->
<ins>Macro alias: <strong>Map</strong></ins></div>
<p>asdfasdf</p>".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty));
@@ -104,10 +158,26 @@ namespace Umbraco.Tests.Macros
[Test]
public void Format_RTE_Data_For_Persistence()
{
// var content = @"<html>
//<body>
//<h1>asdfasdf</h1>
//<div class='umb-macro-holder Map mceNonEditable' att1='asdf' att2='asdfasdfasdf' att3=""sdfsdfd"">
//<!-- <?UMBRACO_MACRO macroAlias=""myMacro"" param1=""test1"" param2=""test2"" /> -->
//asdfasdf
//asdfas
//<span>asdfasdfasdf</span>
//<p>asdfasdf</p>
//</div>
//<span>asdfdasf</span>
//<div>
//asdfsdf
//</div>
//</body>
//</html>";
var content = @"<html>
<body>
<h1>asdfasdf</h1>
<div class='umb-macro-holder Map mceNonEditable' att1='asdf' att2='asdfasdfasdf' att3=""sdfsdfd"">
<div class='umb-macro-holder mceNonEditable' att1='asdf' att2='asdfasdfasdf' att3=""sdfsdfd"">
<!-- <?UMBRACO_MACRO macroAlias=""myMacro"" param1=""test1"" param2=""test2"" /> -->
asdfasdf
asdfas
@@ -170,7 +240,15 @@ asdfsdf
[Test]
public void Format_RTE_Data_For_Persistence_Custom_Single_Entry()
{
var content = @"<div class=""umb-macro-holder Test mceNonEditable umb-macro-mce_1""><!-- <?UMBRACO_MACRO macroAlias=""Test"" content=""1089"" textArea=""asdfasdf"" title="""" bool=""0"" number="""" contentType="""" multiContentType="""" multiProperties="""" properties="""" tabs="""" multiTabs="""" /> --><ins>
// var content = @"<div class=""umb-macro-holder Test mceNonEditable umb-macro-mce_1""><!-- <?UMBRACO_MACRO macroAlias=""Test"" content=""1089"" textArea=""asdfasdf"" title="""" bool=""0"" number="""" contentType="""" multiContentType="""" multiProperties="""" properties="""" tabs="""" multiTabs="""" /> --><ins>
//<div class=""facts-box"">
//<div class=""fatcs-box-header"">
//<h3>null</h3>
//</div>
//<div class=""fatcs-box-body"">1089</div>
//</div>
//</ins></div>";
var content = @"<div class=""umb-macro-holder mceNonEditable umb-macro-mce_1""><!-- <?UMBRACO_MACRO macroAlias=""Test"" content=""1089"" textArea=""asdfasdf"" title="""" bool=""0"" number="""" contentType="""" multiContentType="""" multiProperties="""" properties="""" tabs="""" multiTabs="""" /> --><ins>
<div class=""facts-box"">
<div class=""fatcs-box-header"">
<h3>null</h3>
+2
View File
@@ -38,6 +38,7 @@ namespace Umbraco.Tests
new Mock<IContentService>().Object,
new Mock<IContentTypeService>().Object,
new Mock<IMediaService>().Object,
new Mock<IMacroService>().Object,
new Mock<IDataTypeService>().Object,
new Mock<IFileService>().Object,
new Mock<ILocalizationService>().Object,
@@ -77,6 +78,7 @@ namespace Umbraco.Tests
new Mock<IContentService>().Object,
new Mock<IContentTypeService>().Object,
new Mock<IMediaService>().Object,
new Mock<IMacroService>().Object,
new Mock<IDataTypeService>().Object,
new Mock<IFileService>().Object,
new Mock<ILocalizationService>().Object,
@@ -234,8 +234,7 @@ namespace Umbraco.Tests.Services.Importing
Assert.That(contents.Any(), Is.True);
Assert.That(contents.Count(), Is.EqualTo(numberOfDocs));
}
[Test]
public void PackagingService_Can_Import_CheckboxList_Content_Package_Xml_With_Property_Editor_Aliases()
{
@@ -482,6 +481,51 @@ namespace Umbraco.Tests.Services.Importing
}
}
[Test]
public void PackagingService_Can_Import_Macros()
{
// Arrange
string strXml = ImportResources.uBlogsy_Package;
var xml = XElement.Parse(strXml);
var macrosElement = xml.Descendants("Macros").First();
var packagingService = ServiceContext.PackagingService;
// Act
var macros = packagingService.ImportMacros(macrosElement).ToList();
// Assert
Assert.That(macros.Any(), Is.True);
var allMacros = ServiceContext.MacroService.GetAll().ToList();
foreach (var macro in macros)
{
Assert.That(allMacros.Any(x => x.Alias == macro.Alias), Is.True);
}
}
[Test]
public void PackagingService_Can_Import_Macros_With_Properties()
{
// Arrange
string strXml = ImportResources.XsltSearch_Package;
var xml = XElement.Parse(strXml);
var macrosElement = xml.Descendants("Macros").First();
var packagingService = ServiceContext.PackagingService;
// Act
var macros = packagingService.ImportMacros(macrosElement).ToList();
// Assert
Assert.That(macros.Any(), Is.True);
Assert.That(macros.First().Properties.Any(), Is.True);
var allMacros = ServiceContext.MacroService.GetAll().ToList();
foreach (var macro in macros)
{
Assert.That(allMacros.Any(x => x.Alias == macro.Alias), Is.True);
}
}
private void AddLanguages()
{
var norwegian = new Core.Models.Language("nb-NO");
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using NUnit.Framework;
@@ -22,6 +23,23 @@ namespace Umbraco.Tests.Services
base.TearDown();
}
[Test]
public void PackagingService_Can_Export_Macro()
{
// Arrange
var macro = new Macro("test1", "Test", "~/usercontrol/blah.ascx", "MyAssembly", "test.xslt", "~/views/macropartials/test.cshtml");
ServiceContext.MacroService.Save(macro);
// Act
var element = ServiceContext.PackagingService.Export(macro);
// Assert
Assert.That(element, Is.Not.Null);
Assert.That(element.Element("name").Value, Is.EqualTo("Test"));
Assert.That(element.Element("alias").Value, Is.EqualTo("test1"));
Console.Write(element.ToString());
}
[Test]
public void PackagingService_Can_Export_DictionaryItems()
{
+26 -13
View File
@@ -10,20 +10,33 @@ namespace Umbraco.Tests
[TestFixture]
public class UriExtensionsTests
{
[TestCase("http://www.domain.com/umbraco", true)]
[TestCase("http://www.domain.com/Umbraco/", true)]
[TestCase("http://www.domain.com/umbraco/default.aspx", true)]
[TestCase("http://www.domain.com/umbraco/test/test", true)]
[TestCase("http://www.domain.com/Umbraco/test/test.aspx", true)]
[TestCase("http://www.domain.com/umbraco/test/test.js", true)]
[TestCase("http://www.domain.com/umbrac", false)]
[TestCase("http://www.domain.com/test", false)]
[TestCase("http://www.domain.com/test/umbraco", false)]
[TestCase("http://www.domain.com/test/umbraco.aspx", false)]
public void Is_Back_Office_Request(string input, bool expected)
[TestCase("http://www.domain.com/umbraco", "", true)]
[TestCase("http://www.domain.com/Umbraco/", "", true)]
[TestCase("http://www.domain.com/umbraco/default.aspx", "", true)]
[TestCase("http://www.domain.com/umbraco/test/test", "", false)]
[TestCase("http://www.domain.com/umbraco/test/test/test", "", false)]
[TestCase("http://www.domain.com/Umbraco/test/test.aspx", "", true)]
[TestCase("http://www.domain.com/umbraco/test/test.js", "", true)]
[TestCase("http://www.domain.com/umbrac", "", false)]
[TestCase("http://www.domain.com/test", "", false)]
[TestCase("http://www.domain.com/test/umbraco", "", false)]
[TestCase("http://www.domain.com/test/umbraco.aspx", "", false)]
[TestCase("http://www.domain.com/Umbraco/restServices/blah", "", true)]
[TestCase("http://www.domain.com/Umbraco/Backoffice/blah", "", true)]
[TestCase("http://www.domain.com/Umbraco/anything", "", true)]
[TestCase("http://www.domain.com/Umbraco/anything/", "", true)]
[TestCase("http://www.domain.com/Umbraco/surface/blah", "", false)]
[TestCase("http://www.domain.com/umbraco/api/blah", "", false)]
[TestCase("http://www.domain.com/myvdir/umbraco/api/blah", "myvdir", false)]
[TestCase("http://www.domain.com/MyVdir/umbraco/api/blah", "/myvdir", false)]
[TestCase("http://www.domain.com/MyVdir/Umbraco/", "myvdir", true)]
[TestCase("http://www.domain.com/MyVdir/Umbraco/restServices/blah", "/myvdir", true)]
[TestCase("http://www.domain.com/umbraco/webservices/legacyAjaxCalls.asmx/js", "", true)]
[TestCase("http://www.domain.com/umbraco/test/legacyAjaxCalls.ashx?some=query&blah=js", "", true)]
public void Is_Back_Office_Request(string input, string virtualPath, bool expected)
{
var source = new Uri(input);
Assert.AreEqual(expected, source.IsBackOfficeRequest());
var source = new Uri(input);
Assert.AreEqual(expected, source.IsBackOfficeRequest(virtualPath));
}
[TestCase("http://www.domain.com/install", true)]
@@ -364,6 +364,12 @@ Umbraco.Sys.registerNamespace("Umbraco.Application");
getRootScope().$emit("app.closeDialogs", undefined);
}
},
/* This is used for the package installer to call in order to reload all app assets so we don't have to reload the window */
_packageInstalled: function() {
var injector = getRootInjector();
var packageHelper = injector.get("packageHelper");
packageHelper.packageInstalled();
},
_debug: function(strMsg) {
if (this._isDebug) {
Sys.Debug.trace("UmbClientMgr: " + strMsg);
@@ -1,266 +0,0 @@
/**
* @ngdoc directive
* @name umbraco.directives.directive:umbCropsy
* @restrict E
* @function
* @description
* Used by editors that require naming an entity. Shows a textbox/headline with a required validator within it's own form.
**/
angular.module("umbraco.directives")
.directive('umbCropsy', function ($timeout, localizationService, $log) {
return {
restrict: 'E',
replace: true,
templateUrl: 'views/directives/umb-cropsy.html',
scope: {
src: '@',
width: '@',
height: '@',
presets: '@',
crop: "="
},
link: function(scope, element, attrs) {
scope.scale = 100;
//if image is over this, we re-calculate the editors global ratio
//this will not have an effect on the result, since that is returned in percentage
scope.maxHeight = 500;
scope.maxWidth = 600;
scope.width = 400;
scope.height = 320;
scope.dimensions = {
image: {},
cropper:{},
viewport:{},
margin: 20,
ratio: 1
};
scope.style = function () {
return {
'height': (parseInt(scope.height, 10) + 2 * scope.dimensions.margin) + 'px',
'width': (parseInt(scope.width, 10) + 2 * scope.dimensions.margin) + 'px'
};
};
//elements
var $viewport = element.find(".viewport");
var $image = element.find("img");
var $overlay = element.find(".overlay");
var $container = element.find(".crop-container");
//default constraints for drag n drop
var constraints = {left: {max: 20, min: 20}, top: {max: 20, min: 20}, };
var setDimensions = function(){
scope.dimensions.image.width = $image.width();
scope.dimensions.image.height = $image.height();
scope.dimensions.viewport.width = $viewport.width();
scope.dimensions.viewport.height = $viewport.height();
scope.dimensions.cropper.width = scope.dimensions.viewport.width - 2 * scope.dimensions.margin;
scope.dimensions.cropper.height = scope.dimensions.viewport.height - 2 * scope.dimensions.margin;
};
var setImageSize = function(width, height){
$image.width(width);
$image.height(height);
scope.dimensions.image.width = width;
scope.dimensions.image.height = height;
};
//when loading an image without any crop info, we center and fit it
var fitImage = function(){
fitToViewPort($image);
centerImage($image);
syncOverLay();
setConstraints($image);
};
//utill for centering scaled image
var centerImage = function(img) {
var image_width = img.width(),
image_height = img.height(),
mask_width = $viewport.width(),
mask_height = $viewport.height();
img.css({
'position': 'absolute',
'left': scope.dimensions.viewport.width / 2 - scope.dimensions.image.width / 2,
'top': scope.dimensions.viewport.height / 2 - scope.dimensions.image.height / 2
});
};
//utill for scaling image to fit viewport
var fitToViewPort = function(img) {
//returns size fitting the cropper
var size = calculateAspectRatioFit(
scope.dimensions.image.width,
scope.dimensions.image.height,
scope.dimensions.cropper.width,
scope.dimensions.cropper.height,
true);
//sets the image size and updates the scope
setImageSize(size.width, size.height);
scope.minScale = size.ratio;
scope.maxScale = size.ratio * 3;
scope.currentScale = scope.minScale;
scope.scale = scope.currentScale;
};
var resizeImageToScale = function(img, ratio){
//do stuff
var size = calculateSizeToRatio(scope.dimensions.image.originalWidth, scope.dimensions.image.originalHeight, ratio);
setImageSize(size.width, size.height);
centerImage(img);
scope.currentScale = scope.scale;
syncOverLay();
};
//set constaints for cropping drag and drop
var setConstraints = function(img){
//do stuff
var w = img.width(),
h = img.height(),
crop_width = $viewport.width() - 2 * 20,
crop_height = $viewport.height() - 2 * 20;
constraints.left.min = 20 + crop_width - w;
constraints.top.min = 20 + crop_height - h;
};
//utill for getting either min/max aspect ratio to scale image after
var calculateAspectRatioFit = function(srcWidth, srcHeight, maxWidth, maxHeight, maximize) {
var ratio = [maxWidth / srcWidth, maxHeight / srcHeight ];
if(maximize){
ratio = Math.max(ratio[0], ratio[1]);
}else{
ratio = Math.min(ratio[0], ratio[1]);
}
return { width:srcWidth*ratio, height:srcHeight*ratio, ratio: ratio};
};
//utill for scaling width / height given a ratio
var calculateSizeToRatio= function(srcWidth, srcHeight, ratio) {
return { width:srcWidth*ratio, height:srcHeight*ratio, ratio: ratio};
};
var calculateCropBox = function(){
scope.crop.left = Math.abs($image[0].offsetLeft - scope.dimensions.margin) / scope.dimensions.image.width;
scope.crop.top = Math.abs($image[0].offsetTop - scope.dimensions.margin) / scope.dimensions.image.height;
scope.crop.right = 1 - Math.abs(scope.dimensions.cropper.width - (scope.dimensions.image.width - scope.crop.left)) / scope.dimensions.image.width;
scope.crop.bottom = 1 - Math.abs(scope.dimensions.cropper.height - (scope.dimensions.image.height - scope.crop.top)) / scope.dimensions.image.height;
};
var calculatePosition = function(crop){
var left = (crop.left * scope.dimensions.image.originalWidth);
var top = (crop.top * scope.dimensions.image.originalHeight);
var cropped_width = scope.dimensions.image.originalWidth - left;
var ratio = cropped_width / scope.dimensions.image.originalWidth;
scope.scale = ratio;
resizeImageToScale($image, ratio);
$image.css({
"top": -top,
"left": -left
});
syncOverLay();
};
var syncOverLay = function(){
$overlay.height($image.height());
$overlay.width($image.width());
$overlay.css({
"top": $image[0].offsetTop,
"left": $image[0].offsetLeft
});
calculateCropBox();
};
//Drag and drop positioning, using jquery ui draggable
var onStartDragPosition, top, left;
$overlay.draggable({
start: function(event, ui) {
syncOverLay();
},
drag: function(event, ui) {
if(ui.position.left <= constraints.left.max && ui.position.left >= constraints.left.min){
$image.css({
'left': ui.position.left
});
}
if(ui.position.top <= constraints.top.max && ui.position.top >= constraints.top.min){
$image.css({
'top': ui.position.top
});
}
},
stop: function() {
syncOverLay();
}
});
//// INIT /////
$image.load(function(){
$timeout(function(){
$image.width("auto");
$image.height("auto");
scope.dimensions.image.originalWidth = $image.width();
scope.dimensions.image.originalHeight = $image.height();
setDimensions();
if(scope.crop && scope.crop.top){
calculatePosition(scope.crop);
}else{
fitImage();
}
scope.loaded = true;
});
});
/// WATCHERS ////
scope.$watch("scale", function(){
if(scope.loaded && scope.scale !== scope.currentScale){
resizeImageToScale($image, scope.scale);
setConstraints($image);
}
});
/// WATCHERS ////
scope.$watch("crop", function(newVal, oldVal){
if(scope.loaded && newVal !== oldVal){
calculatePosition(scope.crop);
}
});
}
};
});
@@ -34,15 +34,14 @@ angular.module("umbraco.directives.html")
var fixedRowWidth = Math.max(element.width(), minWidth);
scope.containerStyle = { width: fixedRowWidth + "px" };
scope.rows = umbPhotoFolderHelper.buildGrid(photos, fixedRowWidth, maxHeight, startingIndex, minHeight, idealImgPerRow, margin);
if (attrs.filterBy) {
scope.$watch(attrs.filterBy, function(newVal, oldVal) {
if (newVal !== oldVal) {
if (newVal && newVal !== oldVal) {
var p = $filter('filter')(photos, newVal, false);
scope.baseline = 0;
var m = umbPhotoFolderHelper.buildGrid(p, fixedRowWidth, 400);
var m = umbPhotoFolderHelper.buildGrid(p, fixedRowWidth, maxHeight, startingIndex, minHeight, idealImgPerRow, margin);
scope.rows = m;
}
});
@@ -65,6 +65,11 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se
scope.currentSection = args.value;
}
});
eventsService.on("app.reInitialize", function (e, args) {
//re-load the sections if we're re-initializing (i.e. package installed)
loadSections();
});
//on page resize
window.onresize = calculateHeight;
@@ -59,8 +59,8 @@ angular.module('umbraco.mocks').
id: 1,
active: true,
properties: [
{ alias: "list", label: "List", view: "listview", value: "", hideLabel: true },
{ alias: "media", label: "Media picker", view: "mediapicker", value: "" }
{ alias: "list", label: "List", view: "listview", value: "", hideLabel: true, config:{entityType: "content"} },
{ alias: "media", label: "Media picker", view: "mediapicker", value: "" ,config:{} }
]
},
{
@@ -72,7 +72,7 @@ angular.module('umbraco.mocks').
{ alias: "textarea", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } },
{ alias: "map", label: "Map", view: "googlemaps", value: "37.4419,-122.1419", config: { mapType: "ROADMAP", zoom: 4 } },
{ alias: "content", label: "Content picker", view: "contentpicker", value: "1234,23242,23232,23231" }
{ alias: "media", label: "Media picker", view: "mediapicker", value: "1234,23242,23232,23231", config: {multiPicker: 1} }
]
},
{
@@ -7,6 +7,17 @@ angular.module('umbraco.mocks').
return [200, nodes, null];
}
function returnNodebyIds(status, data, headers) {
var ids = mocksUtils.getParameterByName(data, "ids") || "1234,1234,4234";
var items = [];
_.each(ids, function(id){
items.push(_getNode( parseInt( id, 10 )) );
});
return [200, items, null];
}
function returnNodebyId(status, data, headers) {
if (!mocksUtils.checkAuth()) {
@@ -16,86 +27,47 @@ angular.module('umbraco.mocks').
var id = mocksUtils.getParameterByName(data, "id") || 1234;
id = parseInt(id, 10);
var node = {
name: "My content with id: " + id,
updateDate: new Date(),
publishDate: new Date(),
id: id,
parentId: 1234,
icon: "icon-file-alt",
owner: {name: "Administrator", id: 0},
updater: {name: "Per Ploug Krogslund", id: 1},
path: "-1,1234,2455",
tabs: [
{
label: "Child documents",
alias: "tab00",
id: 0,
active: true,
properties: [
{
alias: "list", label: "List", view: "listview", value: "", hideLabel: true }
]
},
{
label: "Content",
alias: "tab01",
id: 1,
properties: [
{ alias: "bodyText", label: "Body Text", description:"Here you enter the primary article contents", view: "rte", value: "<p>askjdkasj lasjd</p>" },
{ alias: "textarea", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } },
{ alias: "map", label: "Map", view: "googlemaps", value: "37.4419,-122.1419", config: { mapType: "ROADMAP", zoom: 4 } },
{ alias: "media", label: "Media picker", view: "mediapicker", value: "" },
{ alias: "content", label: "Content picker", view: "contentpicker", value: "" }
]
},
{
label: "Sample Editor",
alias: "tab02",
id: 2,
properties: [
{ alias: "datepicker", label: "Datepicker", view: "datepicker", config: { rows: 7 } },
{ alias: "tags", label: "Tags", view: "tags", value: ""}
]
},
{
label: "Grid",
alias: "tab03",
id: 3,
properties: [
{ alias: "grid", label: "Grid", view: "grid", value: "test", hideLabel: true }
]
},{
label: "WIP",
alias: "tab04",
id: 4,
properties: [
{ alias: "tes", label: "Stuff", view: "test", value: "",
config: {
fields: [
{ alias: "embedded", label: "Embbeded", view: "textstring", value: ""},
{ alias: "embedded2", label: "Embbeded 2", view: "contentpicker", value: ""},
{ alias: "embedded3", label: "Embbeded 3", view: "textarea", value: ""},
{ alias: "embedded4", label: "Embbeded 4", view: "datepicker", value: ""}
]
}
}
]
}
]
};
return [200, node, null];
return [200, _getNode(id), null];
}
function _getNode(id){
var node = {
name: "My media with id: " + id,
updateDate: new Date(),
publishDate: new Date(),
id: id,
parentId: 1234,
icon: "icon-file-alt",
owner: {name: "Administrator", id: 0},
updater: {name: "Per Ploug Krogslund", id: 1},
path: "-1,1234,2455",
tabs: [
{
label: "Media",
alias: "tab0",
id: 0,
properties: [
{ alias: "umbracoFile", label: "File", description:"Some file", view: "rte", value: "/media/1234/random.jpg" }
]
}
]
};
return node;
}
return {
register: function() {
$httpBackend
.whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Media/GetById'))
.whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Media/GetById?'))
.respond(returnNodebyId);
$httpBackend
.whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Media/GetByIds?'))
.respond(returnNodebyIds);
$httpBackend
.whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Media/GetChildren'))
.respond(returnNodeCollection);
@@ -0,0 +1,20 @@
angular.module('umbraco.mocks').
factory('imageHelperMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) {
'use strict';
function returnEntitybyIds(){
return "hello.jpg";
}
return {
register: function () {
$httpBackend
.whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Images/GetBigThumbnail'))
.respond(returnEntitybyIds);
}
};
}]);
@@ -1,7 +1,7 @@
var umbracoAppDev = angular.module('umbraco.httpbackend', ['umbraco', 'ngMockE2E', 'umbraco.mocks']);
function initBackEnd($httpBackend, contentMocks, mediaMocks, treeMocks, userMocks, contentTypeMocks, sectionMocks, entityMocks, dataTypeMocks, dashboardMocks, macroMocks, utilMocks, localizationMocks, prevaluesMocks) {
function initBackEnd($httpBackend, contentMocks, mediaMocks, treeMocks, userMocks, contentTypeMocks, sectionMocks, entityMocks, dataTypeMocks, dashboardMocks, macroMocks, utilMocks, localizationMocks, prevaluesMocks, imageHelperMocks) {
console.log("httpBackend inited");
@@ -19,6 +19,7 @@ function initBackEnd($httpBackend, contentMocks, mediaMocks, treeMocks, userMock
localizationMocks.register();
prevaluesMocks.register();
imageHelperMocks.register();
entityMocks.register();
$httpBackend.whenGET(/^views\//).passThrough();
@@ -15,6 +15,8 @@ Umbraco.Sys.ServerVariables = {
"authenticationApiBaseUrl": "/umbraco/UmbracoApi/Authentication/",
//For this we'll just provide a file that exists during the mock session since we don't really have legay js tree stuff
"legacyTreeJs": "/belle/lib/yepnope/empty.js",
"serverVarsJs": "/belle/lib/yepnope/empty.js",
"imagesApiBaseUrl": "/umbraco/UmbracoApi/Images/",
"entityApiBaseUrl": "/umbraco/UmbracoApi/Entity/",
"dashboardApiBaseUrl": "/umbraco/UmbracoApi/Dashboard/",
"updateCheckApiBaseUrl": "/umbraco/Api/UpdateCheck/",
@@ -99,16 +99,6 @@ function entityResource($q, $http, umbRequestHelper) {
'Failed to retreive entity data for id ' + id);
},
getByQuery: function (query, rootNodeId, type) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"entityApiBaseUrl",
"GetByQuery",
[{query: query},{ rootNodeId: rootNodeId}, {type: type }])),
'Failed to retreive entity data for query ' + query);
},
/**
* @ngdoc method
* @name umbraco.resources.entityResource#getByIds
@@ -41,7 +41,7 @@
* </pre>
*/
angular.module('umbraco.services')
.factory('assetsService', function ($q, $log, angularHelper, umbRequestHelper, $rootScope) {
.factory('assetsService', function ($q, $log, angularHelper, umbRequestHelper, $rootScope, $http) {
var initAssetsLoaded = false;
@@ -65,7 +65,7 @@ function eventsService($q, $rootScope) {
return $rootScope.$on(name, callback);
},
/** pass in the result of subscribe to this method, or just call the method returned from subscribe to unsubscribe */
/** pass in the result of 'on' to this method, or just call the method returned from 'on' to unsubscribe */
unsubscribe: function(handle) {
if (angular.isFunction(handle)) {
handle();
@@ -13,7 +13,7 @@ function macroService() {
/** parses the special macro syntax like <?UMBRACO_MACRO macroAlias="Map" /> and returns an object with the macro alias and it's parameters */
parseMacroSyntax: function (syntax) {
var expression = /(<\?UMBRACO_MACRO macroAlias=["'](\w+?)["'].+?)(\/>|>.*?<\/\?UMBRACO_MACRO>)/im;
var expression = /(<\?UMBRACO_MACRO macroAlias=["']([\w\.]+?)["'].+?)(\/>|>.*?<\/\?UMBRACO_MACRO>)/im;
var match = expression.exec(syntax);
if (!match || match.length < 3) {
return null;
@@ -1,5 +1,26 @@
/*Contains multiple services for various helper tasks */
function packageHelper(assetsService, treeService, eventsService) {
return {
/** Called when a package is installed, this resets a bunch of data and ensures the new package assets are loaded in */
packageInstalled: function () {
//assetsService._reloadApplicationAssets().then(function() {
// treeService.clearCache();
// //send event
// //eventsService.emit("app.reInitialize");
// //TODO: This doesn't work and will end in an infinite browser load loop, we can't really
// // re-bootstrap anyways since that would be the same as loading the whole browser window.
// //angular.bootstrap(document, ['umbraco']);
//});
}
};
}
angular.module('umbraco.services').factory('packageHelper', packageHelper);
function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, umbRequestHelper) {
return {
/** sets the image's url - will check if it is a folder or a real image */
@@ -184,51 +184,6 @@ ul.color-picker li a {
}
//
// Cropper
// -------------------------------------------------
.umb-cropper .crop-container .viewport img {
position: absolute;
top: 0;
left: 0;
}
.umb-cropper .overlay {
top: 0;
left: 0;
cursor: move;
z-index: 6001;
border: 1px solid red;
}
.umb-cropper .viewport {
overflow: hidden;
position: relative;
width: 600px;
height: 400px;
border:1px solid #ccc;
background-image: url('images/viewport_background.gif');
}
.umb-cropper .viewport:after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 5999;
-moz-opacity: .75;
opacity: .75;
filter: alpha(opacity=7);
-webkit-box-shadow: inset 0 0 0 20px white,inset 0 0 0 21px rgba(0,0,0,.1),inset 0 0 20px 21px rgba(0,0,0,.2);
-moz-box-shadow: inset 0 0 0 20px white,inset 0 0 0 21px rgba(0,0,0,.1),inset 0 0 20px 21px rgba(0,0,0,.2);
box-shadow: inset 0 0 0 20px white,inset 0 0 0 21px rgba(0,0,0,.1),inset 0 0 20px 21px rgba(0,0,0,.2);
}
//
// folder-browser
// --------------------------------------------------
@@ -1,59 +1,62 @@
angular.module("umbraco")
.controller("Umbraco.Editors.Content.CopyController",
function ($scope, eventsService, contentResource, navigationService, appState, treeService) {
var dialogOptions = $scope.$parent.dialogOptions;
$scope.dialogTreeEventHandler = $({});
var node = dialogOptions.currentNode;
$scope.dialogTreeEventHandler.bind("treeNodeSelect", function(ev, args){
args.event.preventDefault();
args.event.stopPropagation();
var dialogOptions = $scope.$parent.dialogOptions;
eventsService.emit("editors.content.copyController.select", args);
var c = $(args.event.target.parentElement);
if ($scope.selectedEl) {
$scope.selectedEl.find(".temporary").remove();
$scope.selectedEl.find("i.umb-tree-icon").show();
}
$scope.relateToOriginal = false;
$scope.dialogTreeEventHandler = $({});
var temp = "<i class='icon umb-tree-icon sprTree icon-check blue temporary'></i>";
var icon = c.find("i.umb-tree-icon");
if (icon.length > 0) {
icon.hide().after(temp);
} else {
c.prepend(temp);
}
var node = dialogOptions.currentNode;
$scope.target = args.node;
$scope.selectedEl = c;
$scope.dialogTreeEventHandler.bind("treeNodeSelect", function (ev, args) {
args.event.preventDefault();
args.event.stopPropagation();
});
eventsService.emit("editors.content.copyController.select", args);
$scope.copy = function(){
contentResource.copy({parentId: $scope.target.id, id: node.id, relateToOriginal: $scope.relate})
.then(function(path){
$scope.error = false;
$scope.success = true;
var c = $(args.event.target.parentElement);
if ($scope.selectedEl) {
$scope.selectedEl.find(".temporary").remove();
$scope.selectedEl.find("i.umb-tree-icon").show();
}
//get the currently edited node (if any)
var activeNode = appState.getTreeState("selectedNode");
//we need to do a double sync here: first sync to the copied content - but don't activate the node,
//then sync to the currenlty edited content (note: this might not be the content that was copied!!)
var temp = "<i class='icon umb-tree-icon sprTree icon-check blue temporary'></i>";
var icon = c.find("i.umb-tree-icon");
if (icon.length > 0) {
icon.hide().after(temp);
} else {
c.prepend(temp);
}
navigationService.syncTree({ tree: "content", path: path, forceReload: true, activate: false }).then(function(args) {
if (activeNode) {
var activeNodePath = treeService.getPath(activeNode).join();
//sync to this node now - depending on what was copied this might already be synced but might not be
navigationService.syncTree({ tree: "content", path: activeNodePath, forceReload: false, activate: true });
}
});
$scope.target = args.node;
$scope.selectedEl = c;
},function(err){
$scope.success = false;
$scope.error = err;
});
};
});
});
$scope.copy = function () {
contentResource.copy({ parentId: $scope.target.id, id: node.id, relateToOriginal: $scope.relateToOriginal })
.then(function (path) {
$scope.error = false;
$scope.success = true;
//get the currently edited node (if any)
var activeNode = appState.getTreeState("selectedNode");
//we need to do a double sync here: first sync to the copied content - but don't activate the node,
//then sync to the currenlty edited content (note: this might not be the content that was copied!!)
navigationService.syncTree({ tree: "content", path: path, forceReload: true, activate: false }).then(function (args) {
if (activeNode) {
var activeNodePath = treeService.getPath(activeNode).join();
//sync to this node now - depending on what was copied this might already be synced but might not be
navigationService.syncTree({ tree: "content", path: activeNodePath, forceReload: false, activate: true });
}
});
}, function (err) {
$scope.success = false;
$scope.error = err;
});
};
});
@@ -1,49 +1,52 @@
<div ng-controller="Umbraco.Editors.Content.CopyController">
<div class="umb-dialog-body form-horizontal">
<div class="umb-pane">
<div class="umb-dialog-body form-horizontal">
<div class="umb-pane">
<ng-form name="copyForm">
<p class="abstract" ng-hide="success">
Choose where to copy <strong>{{currentNode.name}}</strong> to in the tree struture below
</p>
<p class="abstract" ng-hide="success">
Choose where to copy <strong>{{currentNode.name}}</strong> to in the tree struture below
</p>
<div class="alert alert-error" ng-show="error">
<h4>{{error.errorMsg}}</h4>
<p>{{error.data.Message}}</p>
</div>
<div class="alert alert-error" ng-show="error">
<h4>{{error.errorMsg}}</h4>
<p>{{error.data.Message}}</p>
</div>
<div class="alert alert-success" ng-show="success">
<p>
<strong>{{currentNode.name}}</strong> was copied to
<strong>{{target.name}}</strong>
</p>
<div class="alert alert-success" ng-show="success">
<p><strong>{{currentNode.name}}</strong> was copied to
<strong>{{target.name}}</strong></p>
<button class="btn btn-primary" ng-click="nav.hideDialog()">Ok</button>
</div>
<button class="btn btn-primary" ng-click="nav.hideDialog()">Ok</button>
</div>
<div ng-hide="success">
<umb-tree
section="content"
hideheader="false"
hideoptions="true"
isdialog="true"
eventhandler="dialogTreeEventHandler">
</umb-tree>
<div ng-hide="success">
<umb-tree section="content"
hideheader="false"
hideoptions="true"
isdialog="true"
eventhandler="dialogTreeEventHandler">
</umb-tree>
<umb-pane>
<umb-control-group label="Relate to original">
<input type="checkbox" ng-model="relate" /> </label>
</umb-control-group>
</umb-pane>
</div>
</div>
</div>
<umb-pane>
<umb-control-group label="Relate to original">
<input type="checkbox" ng-model="$parent.$parent.relateToOriginal" />
</umb-control-group>
</umb-pane>
</div>
</ng-form>
</div>
</div>
<div class="umb-dialog-footer btn-toolbar umb-btn-toolbar" ng-hide="success">
<a class="btn btn-link" ng-click="nav.hideDialog()">
<localize key="general_cancel">Cancel</localize>
</a>
<button class="btn btn-primary" ng-click="copy()">
<localize key="actions_copy">Copy</localize>
</button>
</div>
<div class="umb-dialog-footer btn-toolbar umb-btn-toolbar" ng-hide="success">
<a class="btn btn-link" ng-click="nav.hideDialog()">
<localize key="general_cancel">Cancel</localize>
</a>
<button class="btn btn-primary" ng-click="copy()">
<localize key="actions_copy">Copy</localize>
</button>
</div>
</div>
@@ -1,32 +0,0 @@
<div class="umb-cropper" ng-show="src">
<div class="crop-container">
<div class="viewport" ng-style="style()">
<img src="{{src}}" />
<div class="overlay"></div>
</div>
</div>
<input
type="range"
min="{{minScale}}"
max="{{maxScale}}"
step="0.01"
ng-model="scale" />
<pre>
{{dimensions | json}}
</pre>
<pre>
{{box | json}}
</pre>
<!--
<ul class="crops-preview">
<li ng-repeat="preview in previews">
<div ng-style="preview.style">
<img src="{{preview.src" ng-style="preview.imageStyle">
</div>
</li>
</ul> -->
</div>
@@ -5,6 +5,15 @@ angular.module('umbraco')
function($scope, dialogService, entityResource, $log, iconHelper){
if (!$scope.model) {
$scope.model = {};
}
if (!$scope.model.value) {
$scope.model.value = {
type: "content"
};
}
if($scope.model.value.id && $scope.model.value.type !== "member"){
var ent = "Document";
if($scope.model.value.type === "media"){
@@ -13,6 +13,7 @@
<li>
<i class="icon icon-delete red hover-show pull-right" ng-click="clear()"></i>
<i class="icon {{node.icon}} hover-hide"></i>
<a href prevent-default ng-click="openContentPicker()" >{{node.name}}</a>
</li>
</ul>
@@ -23,19 +24,6 @@
<a href ng-click="openContentPicker()" prevent-default>
<localize key="general_choose">Choose</localize>...
</a>
<!--
<a href ng-click="showSearch = true"> Or search</a>-->
</li>
<li ng-show="showSearch">
<input type="text"
ng-model="model.value.query"
class="umb-textstring"
placeholder="Enter query">
<input type="radio" ng-model="model.value.scope" value="global"> Global
<input type="radio" ng-model="model.value.scope" value="current"> Current
<a href ng-click="showSearch = false"> ...Or pick a node</a>
</li>
</ul>
</div>
@@ -3,7 +3,7 @@
angular.module('umbraco')
.controller("Umbraco.PropertyEditors.ContentPickerController",
function($scope, dialogService, entityResource, editorState, $log, iconHelper){
function($scope, dialogService, entityResource, $log, iconHelper){
$scope.renderModel = [];
$scope.ids = $scope.model.value ? $scope.model.value.split(',') : [];
@@ -11,8 +11,6 @@ angular.module('umbraco')
$scope.cfg = {
multiPicker: "0",
entityType: "Document",
filterCssClass: "not-allowed not-published",
startNode:{
type: "content",
id: -1
@@ -33,23 +31,11 @@ angular.module('umbraco')
$scope.cfg.entityType = "Media";
}
//if we have a query for the startnode, we will use that.
if($scope.cfg.startNode.query){
var rootId = -1;
if($scope.cfg.startNode.scope === "current"){
rootId = editorState.current.id;
}
entityResource.getByQuery($scope.cfg.startNode.query, rootId, "Document").then(function(ent){
$scope.cfg.startNodeId = ent.id;
});
}else{
$scope.cfg.startNodeId = $scope.cfg.startNode.id;
}
$scope.cfg.callback = populate;
$scope.cfg.treeAlias = $scope.cfg.startNode.type;
$scope.cfg.section = $scope.cfg.startNode.type;
$scope.cfg.section = $scope.cfg.startNode.type;
$scope.cfg.startNodeId = $scope.cfg.startNode.id;
$scope.cfg.filterCssClass = "not-allowed not-published";
//load current data
entityResource.getByIds($scope.ids, $scope.cfg.entityType).then(function(data){
@@ -68,39 +54,36 @@ angular.module('umbraco')
$scope.remove =function(index){
$scope.renderModel.splice(index, 1);
$scope.ids.splice(index, 1);
$scope.model.value = trim($scope.ids.join(), ",");
};
$scope.add =function(item){
if($scope.ids.indexOf(item.id) < 0){
item.icon = iconHelper.convertFromLegacyIcon(item.icon);
$scope.ids.push(item.id);
$scope.renderModel.push({name: item.name, id: item.id, icon: item.icon});
$scope.model.value = trim($scope.ids.join(), ",");
}
};
$scope.clear = function() {
$scope.model.value = "";
$scope.renderModel = [];
$scope.ids = [];
};
$scope.sortableOptions = {
update: function(e, ui) {
var r = [];
angular.forEach($scope.renderModel, function(value, key){
r.push(value.id);
});
$scope.ids = r;
$scope.model.value = trim($scope.ids.join(), ",");
}
};
//We need to watch our renderModel so that we can update the underlying $scope.model.value properly, this is required
// because the ui-sortable doesn't dispatch an event after the digest of the sort operation. Any of the events for UI sortable
// occur after the DOM has updated but BEFORE the digest has occured so the model has NOT changed yet - it even states so in the docs.
// In their source code there is no event so we need to just subscribe to our model changes here.
//This also makes it easier to manage models, we update one and the rest will just work.
$scope.$watch(function() {
//return the joined Ids as a string to watch
return _.map($scope.renderModel, function(i) {
return i.id;
}).join();
}, function (newVal) {
$scope.ids = _.map($scope.renderModel, function (i) {
return i.id;
});
$scope.model.value = trim($scope.ids.join(), ",");
});
$scope.$on("formSubmitting", function (ev, args) {
$scope.model.value = trim($scope.ids.join(), ",");
@@ -1,7 +1,7 @@
<div ng-controller="Umbraco.PropertyEditors.ContentPickerController" class="umb-editor umb-contentpicker">
<ul class="unstyled list-icons"
ui-sortable="sortableOptions"
ui-sortable
ng-model="renderModel">
<li ng-repeat="node in renderModel">
@@ -15,12 +15,13 @@
</li>
</ul>
<ul class="unstyled list-icons" ng-show="cfg.multiPicker === true || renderModel.length === 0">
<li>
<i class="icon icon-add blue"></i>
<a href="#" ng-click="openContentPicker()" prevent-default>
<localize key="general_add">Add</localize>
</a>
</li>
</ul>
<ul class="unstyled list-icons" ng-show="cfg.multiPicker === true || renderModel.length === 0">
<li>
<i class="icon icon-add blue"></i>
<a href="#" ng-click="openContentPicker()" prevent-default>
<localize key="general_add">Add</localize>
</a>
</li>
</ul>
</div>
@@ -32,14 +32,6 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl
$scope.sync();
};
$scope.edit = function(image){
$scope.currentImage = image;
$scope.currentImage.crop ={};
if(!$scope.currentImage.crop){
//$scope.currentImage.crop = { "left": 0.31731772342645215, "top": 0.17420325244997603, "right": 0.36246473116627076, "bottom": 0.30226197981593617 };
}
}
$scope.add = function() {
dialogService.mediaPicker({
multiPicker: multiPicker,
@@ -65,7 +57,10 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl
$scope.sortableOptions = {
update: function(e, ui) {
var r = [];
var r = [];
//TODO: Instead of doing this with a half second delay would be better to use a watch like we do in the
// content picker. THen we don't have to worry about setting ids, render models, models, we just set one and let the
// watch do all the rest.
$timeout(function(){
angular.forEach($scope.images, function(value, key){
r.push(value.id);
@@ -33,52 +33,45 @@ angular.module('umbraco')
$scope.remove =function(index){
$scope.renderModel.splice(index, 1);
$scope.ids.splice(index, 1);
$scope.model.value = trim($scope.ids.join(), ",");
};
$scope.add =function(item){
if($scope.ids.indexOf(item.id) < 0){
item.icon = iconHelper.convertFromLegacyIcon(item.icon);
$scope.ids.push(item.id);
$scope.renderModel.push({name: item.name, id: item.id, icon: item.icon});
$scope.model.value = trim($scope.ids.join(), ",");
$scope.renderModel.push({name: item.name, id: item.id, icon: item.icon});
}
};
$scope.clear = function() {
$scope.model.value = "";
$scope.renderModel = [];
$scope.ids = [];
};
$scope.sortableOptions = {
update: function(e, ui) {
var r = [];
angular.forEach($scope.renderModel, function(value, key){
r.push(value.id);
});
$scope.ids = r;
$scope.model.value = trim($scope.ids.join(), ",");
}
};
//We need to watch our renderModel so that we can update the underlying $scope.model.value properly, this is required
// because the ui-sortable doesn't dispatch an event after the digest of the sort operation. Any of the events for UI sortable
// occur after the DOM has updated but BEFORE the digest has occured so the model has NOT changed yet - it even states so in the docs.
// In their source code there is no event so we need to just subscribe to our model changes here.
//This also makes it easier to manage models, we update one and the rest will just work.
$scope.$watch(function () {
//return the joined Ids as a string to watch
return _.map($scope.renderModel, function (i) {
return i.id;
}).join();
}, function (newVal) {
$scope.ids = _.map($scope.renderModel, function (i) {
return i.id;
});
$scope.model.value = trim($scope.ids.join(), ",");
});
$scope.$on("formSubmitting", function (ev, args) {
$scope.model.value = trim($scope.ids.join(), ",");
});
function trim(str, chr) {
var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^'+chr+'+|'+chr+'+$', 'g');
return str.replace(rgxtrim, '');
}
function populate(data){
if(angular.isArray(data)){
_.each(data, function (item, i) {
@@ -2,7 +2,7 @@
<ul class="unstyled list-icons"
ui-sortable="sortableOptions"
ui-sortable
ng-model="renderModel">
<li ng-repeat="node in renderModel">
@@ -43,7 +43,7 @@
$scope.model.value.splice(idx, 1);
};
$scope.add = function () {
$scope.add = function ($event) {
if ($scope.newCaption == "") {
$scope.hasError = true;
} else {
@@ -79,6 +79,7 @@
$scope.newInternalName = '';
}
$event.preventDefault();
};
$scope.switch = function ($event) {
@@ -51,7 +51,6 @@
</td>
</tr>
<tr>
<form ng-submit="add()">
<td><input type="text" ng-model="newCaption" placeholder="Enter a new caption" val-highlight="hasError"/></td>
<td>
<div ng-show="addExternal">
@@ -68,10 +67,9 @@
<td><input type="checkbox" ng-model="newNewWindow"/> </td>
<td>
<div class="btn-group">
<input type="submit" class="btn btn-default" value="Add"/>
<button class="btn btn-default" ng-click="add($event)">Add</button>
</div>
</td>
</form>
</tr>
</tbody>
</table>
@@ -47,7 +47,7 @@ describe('edit media controller tests', function () {
});
it('it should have a tabs collection', function () {
expect(scope.content.tabs.length).toBe(5);
expect(scope.content.tabs.length).toBe(1);
});
it('it should have a properties collection on each tab', function () {
@@ -60,7 +60,7 @@ describe('Content picker controller tests', function () {
it("Removing an item should update renderModel, ids and model.value", function(){
scope.remove(1);
scope.$apply();
expect(scope.renderModel.length).toBe(2);
expect(scope.ids.length).toBe(2);
expect(scope.model.value).toBe("1233,23121");
@@ -69,7 +69,7 @@ describe('Content picker controller tests', function () {
it("Adding an item should update renderModel, ids and model.value", function(){
scope.add({name: "meh", id: 666, icon: "woop"});
scope.$apply();
expect(scope.renderModel.length).toBe(4);
expect(scope.ids.length).toBe(4);
expect(scope.model.value).toBe("1233,1231,23121,666");
@@ -77,12 +77,14 @@ describe('Content picker controller tests', function () {
it("Adding a dublicate item should note update renderModel, ids and model.value", function(){
scope.add({name: "meh", id: 666, icon: "woop"});
scope.add({ name: "meh", id: 666, icon: "woop" });
scope.$apply();
expect(scope.renderModel.length).toBe(4);
expect(scope.ids.length).toBe(4);
expect(scope.model.value).toBe("1233,1231,23121,666");
scope.add({name: "meh 2", id: 666, icon: "woop 2"});
scope.add({ name: "meh 2", id: 666, icon: "woop 2" });
scope.$apply();
expect(scope.renderModel.length).toBe(4);
expect(scope.ids.length).toBe(4);
expect(scope.model.value).toBe("1233,1231,23121,666");
@@ -21,6 +21,20 @@ describe('macro service tests', function () {
expect(result.marcoParamsDictionary.test2).toBe("hello");
});
it('can parse syntax for macros with aliases containing dots', function () {
var result = macroService.parseMacroSyntax("<?UMBRACO_MACRO macroAlias='Map.Test' test1=\"asdf\" test2='hello' />");
expect(result).not.toBeNull();
expect(result.macroAlias).toBe("Map.Test");
expect(result.marcoParamsDictionary.test1).not.toBeUndefined();
expect(result.marcoParamsDictionary.test1).toBe("asdf");
expect(result.marcoParamsDictionary.test2).not.toBeUndefined();
expect(result.marcoParamsDictionary.test2).toBe("hello");
});
it('can parse syntax for macros with body', function () {
+15 -8
View File
@@ -115,15 +115,15 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\AutoMapper.3.0.0\lib\net40\AutoMapper.Net4.dll</HintPath>
</Reference>
<Reference Include="ClientDependency.Core, Version=1.7.1.1, Culture=neutral, processorArchitecture=MSIL">
<Reference Include="ClientDependency.Core">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\ClientDependency.1.7.1.1\lib\ClientDependency.Core.dll</HintPath>
</Reference>
<Reference Include="ClientDependency.Core.Mvc, Version=1.7.0.4, Culture=neutral, processorArchitecture=MSIL">
<Reference Include="ClientDependency.Core.Mvc">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\ClientDependency-Mvc.1.7.0.4\lib\ClientDependency.Core.Mvc.dll</HintPath>
</Reference>
<Reference Include="Examine, Version=0.1.52.2941, Culture=neutral, processorArchitecture=MSIL">
<Reference Include="Examine">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Examine.0.1.52.2941\lib\Examine.dll</HintPath>
</Reference>
@@ -616,6 +616,7 @@
<Compile Include="Umbraco\TreeInit.aspx.designer.cs">
<DependentUpon>treeInit.aspx</DependentUpon>
</Compile>
<Content Include="Umbraco\ClientRedirect.aspx" />
<Content Include="Umbraco\create.aspx" />
<Content Include="Umbraco\Logout.aspx" />
<Content Include="Umbraco\umbraco.aspx" />
@@ -2135,13 +2136,13 @@
</Content>
<Content Include="Umbraco\Scripting\templates\cshtml\MultinodeTree-picker.cshtml" />
<Content Include="Umbraco\Views\Default.cshtml" />
<Content Include="Umbraco\PartialViews\Templates\EmptyTemplate.cshtml" />
<Content Include="Umbraco\PartialViews\Templates\Empty.cshtml" />
<Content Include="Umbraco\PartialViewMacros\Templates\EditProfile.cshtml" />
<Content Include="Umbraco\PartialViewMacros\Templates\EmptyTemplate.cshtml" />
<Content Include="Umbraco\PartialViewMacros\Templates\Empty.cshtml" />
<Content Include="Umbraco\PartialViewMacros\Templates\Login.cshtml" />
<Content Include="Umbraco\PartialViewMacros\Templates\LoginStatus.cshtml" />
<Content Include="Umbraco\PartialViewMacros\Templates\RegisterMember.cshtml" />
<Content Include="Umbraco\PartialViewMacros\Templates\EmptyTemplate %28ForUseWithCustomViews%29.cshtml">
<Content Include="Umbraco\PartialViewMacros\Templates\Empty %28ForUseWithCustomViews%29.cshtml">
<SubType>Code</SubType>
</Content>
<Content Include="Umbraco\PartialViewMacros\Templates\ListAncestorsFromCurrentPage.cshtml" />
@@ -2161,7 +2162,7 @@
<Content Include="Umbraco\PartialViewMacros\Templates\Navigation.cshtml" />
<Content Include="Umbraco\PartialViewMacros\Templates\SiteMap.cshtml" />
<Content Include="Umbraco\PartialViews\Templates\Breadcrumb.cshtml" />
<Content Include="Umbraco\PartialViews\Templates\EmptyTemplate %28ForUseWithCustomViews%29.cshtml" />
<Content Include="Umbraco\PartialViews\Templates\Empty %28ForUseWithCustomViews%29.cshtml" />
<Content Include="Umbraco\PartialViews\Templates\ListAncestorsFromCurrentPage.cshtml" />
<Content Include="Umbraco\PartialViews\Templates\ListChildPagesFromCurrentPage.cshtml" />
<Content Include="Umbraco\PartialViews\Templates\ListChildPagesOrderedByDate.cshtml" />
@@ -2667,11 +2668,17 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.0\x86\*.* "$(TargetDir)x86\"
<!-- Transform the local Web.config file in Visual Studio -->
<TransformXml Source="$(ProjectDir)Web.config" Transform="$(ProjectDir)web.Template.$(Configuration).config" Destination="$(ProjectDir)Web.$(Configuration).config.transformed" Condition="$(BuildingInsideVisualStudio) == true" />
<!-- Always transform the Template file when not in VS (ie: build.bat) -->
<TransformXml Source="$(ProjectDir)Web.Template.config" Transform="$(ProjectDir)web.Template.$(Configuration).config" Destination="$(ProjectDir)Web.$(Configuration).config.transformed" Condition="$(BuildingInsideVisualStudio) != true" />
<TransformXml Source="$(ProjectDir)Web.Template.config" Transform="$(ProjectDir)web.Template.$(Configuration).config" Destination="Web.$(Configuration).config.transformed" Condition="$(BuildingInsideVisualStudio) != true" />
<!-- Only runs if the Belle build folder doesn't yet exist -->
<CallTarget Targets="BelleBuild" Condition="!Exists('$(ProjectDir)\..\Umbraco.Web.UI.Client\build')" />
</Target>
<Target Name="AfterBuild">
<Copy SourceFiles="$(ProjectDir)Web.$(Configuration).config.transformed" DestinationFiles="$(ProjectDir)Web.config" OverwriteReadOnlyFiles="true" SkipUnchangedFiles="false" Condition="$(BuildingInsideVisualStudio) == true" />
</Target>
<Target Name="BelleBuild" BeforeTargets="Rebuild">
<!-- Only runs when a Rebuild is requested -->
<Exec WorkingDirectory="$(ProjectDir)\..\..\build\" Command="BuildBelle.bat" ConsoleToMSBuild="true" IgnoreExitCode="true" ContinueOnError="WarnAndContinue" />
</Target>
<Import Project="..\packages\Microsoft.Bcl.Build.1.0.10\tools\Microsoft.Bcl.Build.targets" Condition="Exists('..\packages\Microsoft.Bcl.Build.1.0.10\tools\Microsoft.Bcl.Build.targets')" />
<Target Name="EnsureBclBuildImported" BeforeTargets="BeforeBuild" Condition="'$(BclBuildImported)' == ''">
<Error Condition="!Exists('..\packages\Microsoft.Bcl.Build.1.0.10\tools\Microsoft.Bcl.Build.targets')" Text="This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=317567." HelpKeyword="BCLBUILD2001" />
@@ -10,10 +10,10 @@
@* Lists each selected value from the picker as a link *@
<ul>
@foreach(var id in CurrentPage.PropertyWithPicker){
@foreach(var id in CurrentPage.PropertyWithPicker.Split(',')){
@*For each link, get the node, and display its name and url*@
var content = Umbraco.Content(id.InnerText);
var content = Umbraco.Content(id);
<li><a href="@content.Url">@content.Name</a></li>
}
@@ -0,0 +1 @@
@inherits Umbraco.Web.Mvc.UmbracoTemplatePage
@@ -10,10 +10,10 @@
@* Lists each selected value from the picker as a link *@
<ul>
@foreach(var id in CurrentPage.PropertyWithPicker){
@foreach(var id in CurrentPage.PropertyWithPicker.Split(',')){
@*For each link, get the node, and display its name and url*@
var content = Umbraco.Content(id.InnerText);
var content = Umbraco.Content(id);
<li><a href="@content.Url">@content.Name</a></li>
}
@@ -22,5 +22,5 @@
<cc1:Pane runat="server" CssClass="btn-toolbar umb-btn-toolbar">
<a href="#" class="btn btn-link" onclick="UmbClientMgr.closeModalWindow()"><%=umbraco.ui.Text("cancel")%></a>
<asp:Button ID="sbmt" runat="server" CssClass="btn btn-primary" Text="Save" OnClick="SubmitButton_Click"></asp:Button>
<asp:Button ID="sbmt" runat="server" CssClass="btn btn-primary" Text="Create" OnClick="SubmitButton_Click"></asp:Button>
</cc1:Pane>
@@ -1,4 +1,4 @@
using System;
using System;
using System.Linq;
using System.Web.UI;
using System.Web.UI.WebControls;
@@ -24,16 +24,16 @@ namespace Umbraco.Web.UI.Umbraco.Create
var path = IOHelper.MapPath(SystemDirectories.Umbraco + "/PartialViews/Templates/");
list.Items.Clear();
// always add the options of empty templates
list.Items.Add(new ListItem("Empty Template", "EmptyTemplate.cshtml"));
list.Items.Add(new ListItem("Empty Template (For Use With Custom Views)", "EmptyTemplate (ForUseWithCustomViews).cshtml"));
// always add the options of empty snippets
list.Items.Add(new ListItem("Empty", "Empty.cshtml"));
list.Items.Add(new ListItem("Empty (For Use With Custom Views)", "Empty (ForUseWithCustomViews).cshtml"));
if (System.IO.Directory.Exists(path))
{
const string extension = ".cshtml";
//Already adding Empty Template as the first item, so don't add it again
foreach (var fileInfo in new System.IO.DirectoryInfo(path).GetFiles("*" + extension).Where(f => f.Name.StartsWith("EmptyTemplate") == false))
//Already adding Empty as the first item, so don't add it again
foreach (var fileInfo in new System.IO.DirectoryInfo(path).GetFiles("*" + extension).Where(f => f.Name.StartsWith("Empty") == false))
{
var filename = System.IO.Path.GetFileName(fileInfo.FullName);
@@ -2,7 +2,7 @@
<%
// NH: Adds this inline check to avoid a simple codebehind file in the legacy project!
if (!umbraco.cms.helpers.url.ValidateProxyUrl(Request["url"], Request.Url.AbsoluteUri))
if (Request["url"].ToLower().Contains("booting.aspx") || !umbraco.cms.helpers.url.ValidateProxyUrl(Request["url"], Request.Url.AbsoluteUri))
{
throw new ArgumentException("Can't redirect to the requested url - it's not local or an approved proxy url",
"url");
@@ -1,6 +1,7 @@
using System;
using System.Configuration;
using System.Security.Authentication;
using System.Web;
using System.Web.Script.Serialization;
using System.Web.Script.Services;
using System.Web.Services;
@@ -97,6 +98,11 @@ namespace Umbraco.Web.UI.Install
HandleConnectionStrings();
//After upgrading we must restart the app pool - the reason is because PetaPoco caches a lot of the mapping logic
// and after we upgrade a db, some of the mapping needs to be updated so we restart the app pool to clear it's cache or
// else we can end up with YSODs
ApplicationContext.Current.RestartApplicationPool(new HttpContextWrapper(HttpContext.Current));
var js = new JavaScriptSerializer();
var jsonResult = js.Serialize(result);
return jsonResult;
@@ -161,10 +161,10 @@ namespace Umbraco.Web.UI.Install.Steps
DatabasePassword.Text = GetConnectionStringValue(connectionStringBuilder, "password");
if (string.IsNullOrEmpty(DatabasePassword.Text)) DatabasePassword.Text = GetConnectionStringValue(connectionStringBuilder, "pwd");
toggleVisible(DatabaseServerItem, !ManualConnectionString && !IsEmbeddedDatabase);
toggleVisible(DatabaseUsernameItem, !ManualConnectionString && !IsEmbeddedDatabase);
toggleVisible(DatabasePasswordItem, !ManualConnectionString && !IsEmbeddedDatabase);
toggleVisible(DatabaseNameItem, !ManualConnectionString && !IsEmbeddedDatabase);
ToggleVisible(DatabaseServerItem, !ManualConnectionString && !IsEmbeddedDatabase);
ToggleVisible(DatabaseUsernameItem, !ManualConnectionString && !IsEmbeddedDatabase);
ToggleVisible(DatabasePasswordItem, !ManualConnectionString && !IsEmbeddedDatabase);
ToggleVisible(DatabaseNameItem, !ManualConnectionString && !IsEmbeddedDatabase);
if (IsNewInstall || IsEmbeddedDatabase)
dbinit.Text = "$('#databaseOptionEmbedded').click();$('#databaseOptionEmbedded').change();";
@@ -243,15 +243,15 @@ namespace Umbraco.Web.UI.Install.Steps
/// <param name="e">The event arguments.</param>
protected void DatabaseType_SelectedIndexChanged(object sender, EventArgs e)
{
toggleVisible(DatabaseServerItem, !ManualConnectionString && !IsEmbeddedDatabase);
toggleVisible(DatabaseUsernameItem, !ManualConnectionString && !IsEmbeddedDatabase);
toggleVisible(DatabasePasswordItem, !ManualConnectionString && !IsEmbeddedDatabase);
toggleVisible(DatabaseNameItem, !ManualConnectionString && !IsEmbeddedDatabase);
ToggleVisible(DatabaseServerItem, !ManualConnectionString && !IsEmbeddedDatabase);
ToggleVisible(DatabaseUsernameItem, !ManualConnectionString && !IsEmbeddedDatabase);
ToggleVisible(DatabasePasswordItem, !ManualConnectionString && !IsEmbeddedDatabase);
ToggleVisible(DatabaseNameItem, !ManualConnectionString && !IsEmbeddedDatabase);
//toggleVisible(DatabaseConnectionString, ManualConnectionString);
}
private static void toggleVisible(HtmlGenericControl div, bool visible)
private static void ToggleVisible(HtmlGenericControl div, bool visible)
{
if (!visible)
div.Attributes["style"] = "display: none;";
@@ -1,5 +1,9 @@
using System;
using System.Web;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Web.Security;
namespace Umbraco.Web.UI.Install.Steps
{
@@ -12,15 +16,20 @@ namespace Umbraco.Web.UI.Install.Steps
{
GlobalSettings.ConfigurationStatus = UmbracoVersion.Current.ToString(3);
}
catch (Exception)
catch (Exception ex)
{
//errorLiteral.Text = ex.ToString();
LogHelper.Error<TheEnd>("An error occurred updating the config status", ex);
}
// Update ClientDependency version
var clientDependencyConfig = new ClientDependencyConfiguration();
var clientDependencyUpdated = clientDependencyConfig.IncreaseVersionNumber();
//Clear the auth cookie - this is required so that the login screen is displayed after upgrade and so the
// csrf anti-forgery tokens are created, otherwise there will just be JS errors if the user has an old
// login token from a previous version when we didn't have csrf tokens in place
var security = new WebSecurity(new HttpContextWrapper(Context), ApplicationContext.Current);
security.ClearCurrentLogin();
}
}
@@ -0,0 +1,23 @@
<%@ Page Language="C#" AutoEventWireup="true" Inherits="System.Web.UI.Page" %>
<%--
This page is required because we cannot reload the angular app with a changed Hash since it just detects the hash and doesn't reload.
So this is used purely for a full reload of an angular app with a changed hash.
--%>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Redirecting...</title>
<script type="text/javascript">
var parts = window.location.href.split("?redirectUrl=");
if (parts.length != 2) {
window.location.href = "/";
}
else {
window.location.href = parts[1];
}
</script>
</head>
<body>
<small>Redirecting...</small>
</body>
</html>
@@ -61,7 +61,7 @@
</nodeType>
<nodeType alias="users">
<header>User</header>
<usercontrol>/create/simple.ascx</usercontrol>
<usercontrol>/create/user.ascx</usercontrol>
<tasks>
<create assembly="umbraco" type="userTasks" />
<delete assembly="umbraco" type="userTasks" />
@@ -467,20 +467,21 @@
</area>
<area alias="notifications">
<key alias="editNotifications">Rediger dine notificeringer for %0%</key>
<key alias="mailBody"><![CDATA[
<key alias="mailBody">
<![CDATA[
Hej %0%
Dette er en automatisk mail for at fortælle at handlingen '%1%'
er blevet udført på siden '%2%'
af brugeren '%3%'
Gå til http://%4%/default.aspx?section=content&id=%5% for at redigere.
Gå til http://%4%/#/content/content/edit/%5% for at redigere.
Ha' en dejlig dag!
Mange hilsner fra umbraco robotten
]]></key>
<key alias="mailBodyHtml"><![CDATA[<p>Hej %0%</p> <p>Dette er en automatisk mail for at informere dig om at opgaven <strong>'%1%'</strong> er blevet udførtpå siden <a href="http://%4%/actions/preview.aspx?id=%5%"><strong>'%2%'</strong></a> af brugeren <strong>'%3%'</strong> </p> <div style="margin: 8px 0; padding: 8px; display: block;"> <br /> <a style="color: white; font-weight: bold; background-color: #66cc66; text-decoration : none; margin-right: 20px; border: 8px solid #66cc66; width: 150px;" href="http://%4%/actions/publish.aspx?id=%5%">&nbsp;&nbsp;PUBLISÉr&nbsp;&nbsp;</a> &nbsp; <a style="color: white; font-weight: bold; background-color: #5372c3; text-decoration : none; margin-right: 20px; border: 8px solid #5372c3; width: 150px;" href="http://%4%/actions/editContent.aspx?id=%5%">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RET&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</a> &nbsp; <a style="color: white; font-weight: bold; background-color: #ca4a4a; text-decoration : none; margin-right: 20px; border: 8px solid #ca4a4a; width: 150px;" href="http://%4%/actions/delete.aspx?id=%5%">&nbsp;&nbsp;&nbsp;&nbsp;SLET&nbsp;&nbsp;&nbsp;&nbsp;</a> <br /> </div> <p> <h3>Opdateringssammendrag:</h3> <table style="width: 100%;"> %6% </table> </p> <div style="margin: 8px 0; padding: 8px; display: block;"> <br /> <a style="color: white; font-weight: bold; background-color: #66cc66; text-decoration : none; margin-right: 20px; border: 8px solid #66cc66; width: 150px;" href="http://%4%/actions/publish.aspx?id=%5%">&nbsp;&nbsp;PUBLISÉR&nbsp;&nbsp;</a> &nbsp; <a style="color: white; font-weight: bold; background-color: #5372c3; text-decoration : none; margin-right: 20px; border: 8px solid #5372c3; width: 150px;" href="http://%4%/actions/editContent.aspx?id=%5%">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RET&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</a> &nbsp; <a style="color: white; font-weight: bold; background-color: #ca4a4a; text-decoration : none; margin-right: 20px; border: 8px solid #ca4a4a; width: 150px;" href="http://%4%/actions/delete.aspx?id=%5%">&nbsp;&nbsp;&nbsp;&nbsp;SLET&nbsp;&nbsp;&nbsp;&nbsp;</a> <br /> </div> <p>Hav en fortsat god dag!<br /><br /> De bedste hilsner fra umbraco robotten </p>]]></key>
<key alias="mailBodyHtml"><![CDATA[<p>Hej %0%</p> <p>Dette er en automatisk mail for at informere dig om at opgaven <strong>'%1%'</strong> er blevet udførtpå siden <a href="http://%4%/#/content/content/edit/%5%"><strong>'%2%'</strong></a> af brugeren <strong>'%3%'</strong> </p> <div style="margin: 8px 0; padding: 8px; display: block;"> <br /> <a style="color: white; font-weight: bold; background-color: #5372c3; text-decoration : none; margin-right: 20px; border: 8px solid #5372c3; width: 150px;" href="http://%4%/#/content/content/edit/%5%">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RET&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</a> &nbsp; <br /> </div> <p> <h3>Opdateringssammendrag:</h3> <table style="width: 100%;"> %6% </table> </p> <div style="margin: 8px 0; padding: 8px; display: block;"> <br /> <a style="color: white; font-weight: bold; background-color: #66cc66; text-decoration : none; margin-right: 20px; border: 8px solid #66cc66; width: 150px;" href="http://%4%/actions/publish.aspx?id=%5%">&nbsp;&nbsp;PUBLISÉR&nbsp;&nbsp;</a> &nbsp; <a style="color: white; font-weight: bold; background-color: #5372c3; text-decoration : none; margin-right: 20px; border: 8px solid #5372c3; width: 150px;" href="http://%4%/#/content/content/edit/%5%">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RET&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</a> &nbsp; <a style="color: white; font-weight: bold; background-color: #ca4a4a; text-decoration : none; margin-right: 20px; border: 8px solid #ca4a4a; width: 150px;" href="http://%4%/actions/delete.aspx?id=%5%">&nbsp;&nbsp;&nbsp;&nbsp;SLET&nbsp;&nbsp;&nbsp;&nbsp;</a> <br /> </div> <p>Hav en fortsat god dag!<br /><br /> De bedste hilsner fra umbraco robotten </p>]]></key>
<key alias="mailSubject">[%0%] Notificering om %1% udført på %2%</key>
<key alias="notifications">Notificeringer</key>
</area>

Some files were not shown because too many files have changed in this diff Show More