Compare commits

...

80 Commits

Author SHA1 Message Date
Mads Rasmussen 1dacbbdbf6 added dots to website/language selector 2016-03-16 11:09:06 +01:00
Mads Rasmussen f0b374c30b filter out selected item 2016-03-16 10:43:09 +01:00
Mads Rasmussen ec5feee1f3 WIP prototyping website/language selector 2016-03-16 09:26:34 +01:00
Simon Busborg 1d1d99ddad added variationsHelper service and toggler for content 2016-03-15 14:47:03 +01:00
Claus c306f94a6a Merge pull request #1154 from pynej/fix-stylesheet-tree-sync
Fix for issue causing the entire stylesheets tree to reload when …
2016-03-15 12:46:59 +01:00
Claus 11b2ec89fd Merge pull request #1167 from umbraco/temp-U4-8154
U4-8154 - bugfix Sql query expressions parsing
2016-03-15 12:36:52 +01:00
Claus 89596f00f1 Merge pull request #1151 from arknu/icon-transparency
Fix transparency for image icons in the tray
2016-03-15 09:54:36 +01:00
Claus 4fa6e70e72 Merge pull request #1140 from jamescoxhead/U4-7746
U4-7746 - use flat icon in partial views tree
2016-03-15 09:22:18 +01:00
Warren Buckley a5a0a84d46 Merge pull request #1163 from umbraco/temp-U4-7767
U4-7767 List View Filter needs debounce - many requests to handler me…
2016-03-14 16:12:19 +00:00
Warren Buckley 839431e6fa Merge pull request #1170 from umbraco/temp-U4-8157
U4-8157 Right-click on root node returns console error when it doesn'…
2016-03-14 14:28:39 +00:00
Stephan 5369a6fc56 Upgrade ModelsBuilder to 3.0.1 2016-03-14 13:37:49 +01:00
Claus 8a078e258f U4-8157 Right-click on root node returns console error when it doesn't has any actions
Ensured we don't use throw to stop the program flow if no menuUrl is defined on the node. It is a likely scenario that some root node don't have right click actions so its better to handle this.
2016-03-14 12:15:15 +01:00
Sebastiaan Janssen fb42574c34 Revert "U4-5334 Richtext editor inserts extra p - tags to html output" - apparently it does just as much harm as it does good
This reverts commit 9ad3b6a337.
2016-03-12 21:02:05 +01:00
Sebastiaan Janssen 9ad3b6a337 U4-5334 Richtext editor inserts extra p - tags to html output
#U4-5334 Fixed
2016-03-12 12:58:53 +01:00
Stephan 65b16fbfe5 U4-8154 - bugfix Sql query expressions parsing 2016-03-11 14:18:08 +01:00
Stephan 6ad2853bce Merge pull request #1165 from umbraco/temp-U4-7949
U4-7949 The task runner has completed YSOD when using the Umbraco Pac…
2016-03-10 18:29:08 +01:00
Sebastiaan Janssen b2407eab98 Merge pull request #1107 from bjarnef/dev-v7-tinymce-font-icons
Change icons for richtext editor config
2016-03-10 17:47:46 +01:00
Shannon 778c7a836b Merge branch 'dev-v7' of https://github.com/umbraco/Umbraco-CMS into dev-v7 2016-03-10 15:52:43 +01:00
Shannon 11f9cdead0 U4-6366 Issue assigning member roles on MemberService.Saved event 2016-03-10 15:52:34 +01:00
Sebastiaan Janssen a1076d5443 Merge pull request #1160 from umbraco/temp-U4-6438
U4-6438 - fix dynamic extension methods when type loading goes bad
2016-03-10 15:49:15 +01:00
Shannon 4a11bf68f3 U4-7949 The task runner has completed YSOD when using the Umbraco Package installer 2016-03-10 15:23:52 +01:00
Sebastiaan Janssen fa64e73a43 Merge pull request #1164 from umbraco/temp-U4-5797
U4-5797 - fix PublishedContentType cache refresh for compositions
2016-03-10 14:04:46 +01:00
Stephan adf3e2dfe0 U4-5797 - fix PublishedContentType cache refresh for compositions 2016-03-10 13:59:50 +01:00
Shannon c4c86cf5ce U4-7767 List View Filter needs debounce - many requests to handler method 2016-03-10 13:02:20 +01:00
Sebastiaan Janssen 479aba8e1d Fixes unit test.. in the correct branch this time 2016-03-10 11:47:51 +01:00
Shannon 0c474e2aba U4-7670 True/false fields on Macro Picker don't save their setting nor read existing setting. - another null check required. 2016-03-10 10:06:20 +01:00
Shannon Deminick 4f5b34cdfe Merge pull request #1162 from pynej/codemirror-js-error
Fix for missing doc.setLine() function breaking code mirror editor.
2016-03-10 09:58:16 +01:00
Warren Buckley 0cb7f220ea Merge pull request #1149 from umbraco/temp-U4-4700
U4-4700 List View Doesn't Respect User Permissions
2016-03-10 08:52:38 +00:00
Jeremy Pyne 0995b2cb5a Fix for missing doc.setLine() function breaking code mirror editor. 2016-03-09 14:55:15 -05:00
Stephan 1b37475144 U4-6438 - fix dynamic extension methods when type loading goes bad 2016-03-09 15:55:41 +01:00
Shannon be0bacd895 Ensures all angular views are cache busted so browser cache is not an issue after upgrading. 2016-03-08 17:23:54 +01:00
Shannon a2564bd0c3 ensures that requests are only made for ids that haven't already been queried. 2016-03-08 16:18:33 +01:00
Shannon 402a5c9d56 fixes bool check 2016-03-08 15:57:59 +01:00
Shannon 2a2f22c7d9 fixes js null issue with boolean.controller 2016-03-08 15:53:45 +01:00
Shannon 372a768dca U4-4700 List view doesn't respect user permissions - makes the buttons show/hide based on the selected items instead of the current item. 2016-03-08 15:53:22 +01:00
Sebastiaan Janssen f6e48243a6 U4-8088 NuGet upgrade of UmbracoCms pacakage doesn't add ModelsBuilder keys to web.config 2016-03-08 13:39:35 +01:00
Warren Buckley 94ea677ec0 Merge pull request #1146 from umbraco/temp-U4-7641
U4-7641 - enable content type copy
2016-03-08 09:38:16 +00:00
Sebastiaan Janssen 55e7159f7d Forgot to remove them from the nuspec files 2016-03-08 10:35:57 +01:00
Sebastiaan Janssen 390a94e323 U4-8054 Issue with NuGet Uninstall.ps1 (running on updates)
#U4-8054 Fixed
2016-03-08 10:34:19 +01:00
Shannon Deminick 19fb920aa1 Merge pull request #1150 from umbraco/temp-U4-7823
U4-7823 - stop flooding the log with errors getting media 0
2016-03-07 17:58:49 +01:00
Shannon Deminick f35e1604ca Merge pull request #1141 from umbraco/temp-U4-8030
U4-8030 - Fix RenderModelBinder issue with surface
2016-03-07 17:53:31 +01:00
Shannon b9765c3bec fixes RenderModelBinder to use base implementation,adds unit tests 2016-03-07 17:52:28 +01:00
Shannon d5a57c505c adds RenderModelBinderTests 2016-03-07 16:53:57 +01:00
Shannon 5f365241a8 updates RenderModelBinder to simply check for IPublishedContent or IRenderModel 2016-03-07 16:29:40 +01:00
Shannon 9c121f0044 Fixes RenderModelBinder to inherit from DefaultModelBinder and use the default logic if the model in the route values is not IRenderModel 2016-03-07 16:26:30 +01:00
Shannon f13bc07ddc Merge branch 'dev-v7' of https://github.com/umbraco/Umbraco-CMS into dev-v7 2016-03-07 15:13:41 +01:00
Shannon a707d23692 Allows for specifying explicit product names by overriding the property in MigrationStartupHander 2016-03-07 15:13:31 +01:00
Simon Busborg 0929b7d42c Fixes U4-8113 Styling for property labels affect package meta data labels 2016-03-07 14:55:18 +01:00
Simon Busborg 35a9272b68 Fixes U4-8036 Umbraco.MultiNodeTreePicker checkbox doesn't appear after selecting something from list view 2016-03-07 14:45:30 +01:00
Shannon Deminick 112453a032 Merge pull request #1144 from umbraco/temp-U4-8077
U4-8077 - ensure product name in migration event handlers
2016-03-07 14:09:29 +01:00
Simon Busborg bba7367ba6 Fixes U4-8058 Labels are changed to block-level elements 2016-03-07 12:12:35 +01:00
Jeremy Pyne 83e679ba5e Fix for issue causing the entire stylesheets tree to reload when selecting a item. 2016-03-02 16:00:33 -05:00
Asbjørn Riis-Knudsen 4d950fb4d4 Fix transparency for image icons in the tray 2016-02-28 17:40:58 +01:00
Stephan 0d191fb8bd U4-7823 - stop flooding the log with errors getting media 0 2016-02-26 11:35:24 +01:00
Warren Buckley c7a3efadb1 Remove debug JSON in the view 2016-02-25 21:09:12 +00:00
Warren Buckley 956425dfbf Now updated so the create button hides if we do not have the Create permission 'C' 2016-02-25 21:06:25 +00:00
Warren Buckley c616354522 After reading src code for contentEditorHelper.configureContentEditorButtons it makes the same assumption that if the publish action 'U' is present then the user can also unpublish aka 'Z' 2016-02-25 20:49:56 +00:00
Stephan 36246b96d4 Fix bug with permissions cache refresh 2016-02-25 17:39:34 +01:00
Stephan 7d8791ec86 Fix ActionButtonsResolver (need review) 2016-02-25 17:05:44 +01:00
Stephan f349a9ae3e U4-7641 - bugfix 2016-02-25 16:26:20 +01:00
Warren Buckley 2719e2c231 WIP - ListView that grabs the current nodes permission from the editorState & use a neater object to return to the view rather than the lovely Magic String CHARs. Need help understanding that I don't get all the permissions to be expected 2016-02-25 15:19:42 +00:00
Stephan dafde545b0 U4-7641 - bugfix + implement for media types 2016-02-25 16:02:04 +01:00
Stephan 610538261e U4-7641 - enable content type copy 2016-02-25 15:26:03 +01:00
Stephan 9f7b82e20f U4-8077 - ensure product name in migration event handlers 2016-02-24 13:59:15 +01:00
Stephan 04b3532e9d U4-8030 - Fix RenderModelBinder issue with surface 2016-02-22 18:38:06 +01:00
James Coxhead 01bcd3b623 Changed partial views icon to icon-article 2016-02-22 16:15:31 +00:00
James Coxhead 64fd95bd22 Updated tree icons for partial views and partial view macros 2016-02-22 16:13:25 +00:00
bjarnef 5176b68935 Merge remote-tracking branch 'refs/remotes/umbraco/dev-v7' into dev-v7-tinymce-font-icons 2016-02-08 15:07:36 +01:00
bjarnef 0bfc5f1272 Change icons in tinymce config 2016-02-08 15:07:16 +01:00
bjarnef 39c18d4201 Merge branch 'dev-v7' of https://github.com/bjarnef/Umbraco-CMS into dev-v7 2016-02-05 15:54:48 +01:00
bjarnef 66ea8ae604 Merge remote-tracking branch 'refs/remotes/umbraco/dev-v7' into dev-v7 2016-02-05 14:55:53 +01:00
bjarnef 6fc794ca2e Merge branch 'dev-v7' of https://github.com/bjarnef/Umbraco-CMS into dev-v7 2016-02-03 14:42:14 +01:00
bjarnef df958223b0 Merge remote-tracking branch 'refs/remotes/umbraco/dev-v7' into dev-v7 2016-01-26 18:17:00 +01:00
bjarnef 1217d9f0b3 Merge remote-tracking branch 'refs/remotes/umbraco/dev-v7' into dev-v7 2016-01-24 23:25:29 +01:00
bjarnef 1301897f39 Merge remote-tracking branch 'refs/remotes/umbraco/dev-v7' into dev-v7 2016-01-22 22:02:51 +01:00
bjarnef 6d34fe416d Merge remote-tracking branch 'refs/remotes/umbraco/dev-v7' into dev-v7 2016-01-21 20:38:52 +01:00
bjarnef a884ece8b1 Merge remote-tracking branch 'refs/remotes/umbraco/dev-v7' into dev-v7 2016-01-21 18:15:50 +01:00
bjarnef 0ffcc9b889 Merge remote-tracking branch 'refs/remotes/umbraco/dev-v7' into dev-v7 2016-01-20 19:21:58 +01:00
bjarnef ee3131b63d Merge remote-tracking branch 'refs/remotes/umbraco/dev-v7' into dev-v7 2016-01-19 21:03:42 +01:00
bjarnef 4fd7182388 Merge remote-tracking branch 'refs/remotes/umbraco/dev-v7' into dev-v7 2016-01-19 20:56:36 +01:00
98 changed files with 2957 additions and 1198 deletions
-1
View File
@@ -72,7 +72,6 @@
<file src="..\_BuildOutput\WebApp\bin\UmbracoExamine.dll" target="lib\UmbracoExamine.dll" />
<file src="..\_BuildOutput\WebApp\bin\UmbracoExamine.xml" target="lib\UmbracoExamine.xml" />
<file src="tools\install.core.ps1" target="tools\install.ps1" />
<file src="tools\uninstall.core.ps1" target="tools\uninstall.ps1" />
<!-- Added to be able to produce a symbols package -->
<file src="..\_BuildOutput\bin\SQLCE4Umbraco.pdb" target="lib" />
-1
View File
@@ -33,7 +33,6 @@
<file src="..\_BuildOutput\WebApp\umbraco\**" target="UmbracoFiles\umbraco" />
<file src="..\_BuildOutput\WebApp\umbraco_client\**" target="UmbracoFiles\umbraco_client" />
<file src="tools\install.ps1" target="tools\install.ps1" />
<file src="tools\uninstall.ps1" target="tools\uninstall.ps1" />
<file src="tools\Readme.txt" target="tools\Readme.txt" />
<file src="tools\ReadmeUpgrade.txt" target="tools\ReadmeUpgrade.txt" />
<file src="tools\Web.config.install.xdt" target="Content\Web.config.install.xdt" />
@@ -21,6 +21,8 @@
<appSettings xdt:Transform="InsertIfMissing" />
<appSettings>
<add key="owin:appStartup" value="UmbracoDefaultOwinStartup" xdt:Locator="Match(key)" xdt:Transform="InsertIfMissing" />
<add key="Umbraco.ModelsBuilder.Enable" value="false" xdt:Locator="Match(key)" xdt:Transform="InsertIfMissing" />
<add key="Umbraco.ModelsBuilder.ModelsMode" value="Nothing" xdt:Locator="Match(key)" xdt:Transform="InsertIfMissing" />
</appSettings>
<umbracoConfiguration xdt:Transform="InsertIfMissing">
-48
View File
@@ -1,48 +0,0 @@
param($installPath, $toolsPath, $package, $project)
Write-Host "installPath:" "${installPath}"
Write-Host "toolsPath:" "${toolsPath}"
Write-Host " "
if ($project) {
# Create paths and list them
$projectPath = (Get-Item $project.Properties.Item("FullPath").Value).FullName
Write-Host "projectPath:" "${projectPath}"
$backupPath = Join-Path $projectPath "App_Data\NuGetBackup"
Write-Host "backupPath:" "${backupPath}"
$umbracoBinFolder = Join-Path $projectPath "bin"
Write-Host "umbracoBinFolder:" "${umbracoBinFolder}"
# Remove backups
Write-Host "removing backups:" "${backupPath}"
if(Test-Path $backupPath) { Remove-Item -Recurse -Force $backupPath -Confirm:$false }
# Delete files Umbraco ships with
Write-Host "removing dlls:" "${umbracoBinFolder}"
if(Test-Path $umbracoBinFolder\businesslogic.dll) { Remove-Item $umbracoBinFolder\businesslogic.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\cms.dll) { Remove-Item $umbracoBinFolder\cms.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\controls.dll) { Remove-Item $umbracoBinFolder\controls.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\interfaces.dll) { Remove-Item $umbracoBinFolder\interfaces.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\log4net.dll) { Remove-Item $umbracoBinFolder\log4net.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.ApplicationBlocks.Data.dll) { Remove-Item $umbracoBinFolder\Microsoft.ApplicationBlocks.Data.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\SQLCE4Umbraco.dll) { Remove-Item $umbracoBinFolder\SQLCE4Umbraco.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Data.SqlServerCe.dll) { Remove-Item $umbracoBinFolder\System.Data.SqlServerCe.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Data.SqlServerCe.Entity.dll) { Remove-Item $umbracoBinFolder\System.Data.SqlServerCe.Entity.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\TidyNet.dll) { Remove-Item $umbracoBinFolder\TidyNet.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\umbraco.dll) { Remove-Item $umbracoBinFolder\umbraco.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Umbraco.Core.dll) { Remove-Item $umbracoBinFolder\Umbraco.Core.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\umbraco.DataLayer.dll) { Remove-Item $umbracoBinFolder\umbraco.DataLayer.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\umbraco.editorControls.dll) { Remove-Item $umbracoBinFolder\umbraco.editorControls.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\umbraco.MacroEngines.dll) { Remove-Item $umbracoBinFolder\umbraco.MacroEngines.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\umbraco.providers.dll) { Remove-Item $umbracoBinFolder\umbraco.providers.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Umbraco.Web.UI.dll) { Remove-Item $umbracoBinFolder\Umbraco.Web.UI.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\UmbracoExamine.dll) { Remove-Item $umbracoBinFolder\UmbracoExamine.dll -Force -Confirm:$false }
$amd64Folder = Join-Path $umbracoBinFolder "amd64"
if(Test-Path $amd64Folder) { Remove-Item $amd64Folder -Force -Recurse -Confirm:$false }
$x86Folder = Join-Path $umbracoBinFolder "x86"
if(Test-Path $x86Folder) { Remove-Item $x86Folder -Force -Recurse -Confirm:$false }
}
-39
View File
@@ -1,39 +0,0 @@
param($installPath, $toolsPath, $package, $project)
Write-Host "installPath:" "${installPath}"
Write-Host "toolsPath:" "${toolsPath}"
Write-Host " "
if ($project) {
# Create paths and list them
$projectPath = (Get-Item $project.Properties.Item("FullPath").Value).FullName
Write-Host "projectPath:" "${projectPath}"
$backupPath = Join-Path $projectPath "App_Data\NuGetBackup"
Write-Host "backupPath:" "${backupPath}"
$appBrowsers = Join-Path $projectPath "App_Browsers"
Write-Host "appBrowsers:" "${appBrowsers}"
$appData = Join-Path $projectPath "App_Data"
Write-Host "appData:" "${appData}"
# Remove backups
Write-Host "removing backups:" "${backupPath}"
if(Test-Path $backupPath) { Remove-Item -Recurse -Force $backupPath -Confirm:$false }
# Remove app_data files
Write-Host "removing app_data files:" "${appData}"
if(Test-Path $appData\packages) { Remove-Item $appData\packages -Recurse -Force -Confirm:$false }
Write-Host "removing app_browsers:" "${appBrowsers}"
if(Test-Path $appBrowsers\Form.browser) { Remove-Item $appBrowsers\Form.browser -Force -Confirm:$false }
if(Test-Path $appBrowsers\w3cvalidator.browser) { Remove-Item $appBrowsers\w3cvalidator.browser -Force -Confirm:$false }
# Remove umbraco and umbraco_files
$umbracoFolder = Join-Path $projectPath "Umbraco"
Write-Host "removing umbraco folder:" "${umbracoFolder}"
if(Test-Path $umbracoFolder) { Remove-Item $umbracoFolder -Recurse -Force -Confirm:$false }
$umbracoClientFolder = Join-Path $projectPath "Umbraco_Client"
Write-Host "removing umbraco client folder:" "${umbracoClientFolder}"
if(Test-Path $umbracoClientFolder) { Remove-Item $umbracoClientFolder -Recurse -Force -Confirm:$false }
}
@@ -5,8 +5,10 @@ using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Linq.Expressions;
using System.Text;
using System.Web.Services.Description;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
namespace Umbraco.Core.Dynamics
{
@@ -20,6 +22,20 @@ namespace Umbraco.Core.Dynamics
/// </summary>
private static readonly ConcurrentDictionary<Tuple<Type, string, int>, MethodInfo[]> MethodCache = new ConcurrentDictionary<Tuple<Type, string, int>, MethodInfo[]>();
private static IEnumerable<Type> GetTypes(Assembly a)
{
try
{
return TypeFinder.GetTypesWithFormattedException(a);
}
catch (ReflectionTypeLoadException ex)
{
// is this going to flood the log?
LogHelper.Error(typeof (ExtensionMethodFinder), "Failed to get types.", ex);
return Enumerable.Empty<Type>();
}
}
/// <summary>
/// Returns the enumerable of all extension method info's in the app domain = USE SPARINGLY!!!
/// </summary>
@@ -36,7 +52,7 @@ namespace Umbraco.Core.Dynamics
// assemblies that contain extension methods
.Where(a => a.IsDefined(typeof (ExtensionAttribute), false))
// types that contain extension methods
.SelectMany(a => a.GetTypes()
.SelectMany(a => GetTypes(a)
.Where(t => t.IsDefined(typeof (ExtensionAttribute), false) && t.IsSealed && t.IsGenericType == false && t.IsNested == false))
// actual extension methods
.SelectMany(t => t.GetMethods(BindingFlags.Static | BindingFlags.Public)
@@ -45,9 +61,9 @@ namespace Umbraco.Core.Dynamics
.Concat(typeof (Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public))
//If we don't do this then we'll be scanning all assemblies each time!
.ToArray(),
//only cache for 5 minutes
timeout: TimeSpan.FromMinutes(5),
timeout: TimeSpan.FromMinutes(5),
//each time this is accessed it will be for 5 minutes longer
isSliding:true);
@@ -57,7 +73,7 @@ namespace Umbraco.Core.Dynamics
/// Returns all extension methods found matching the definition
/// </summary>
/// <param name="runtimeCache">
/// The runtime cache is used to temporarily cache all extension methods found in the app domain so that
/// The runtime cache is used to temporarily cache all extension methods found in the app domain so that
/// while we search for individual extension methods, the process will be reasonably 'quick'. We then statically
/// cache the MethodInfo's that we are looking for and then the runtime cache will expire and give back all that memory.
/// </param>
@@ -78,7 +94,7 @@ namespace Umbraco.Core.Dynamics
{
var candidates = GetAllExtensionMethodsInAppDomain(runtimeCache);
// filter by name
// filter by name
var filtr1 = candidates.Where(m => m.Name == name);
// filter by args count
@@ -102,7 +118,7 @@ namespace Umbraco.Core.Dynamics
return filtr3.ToArray();
});
}
private static MethodInfo DetermineMethodFromParams(IEnumerable<MethodInfo> methods, Type genericType, IEnumerable<object> args)
@@ -123,12 +139,12 @@ namespace Umbraco.Core.Dynamics
types = method.GetParameters().Select(pi => pi.ParameterType).Skip(1)
});
//This type comparer will check
//This type comparer will check
var typeComparer = new DelegateEqualityComparer<Type>(
//Checks if the argument type passed in can be assigned from the parameter type in the method. For
//Checks if the argument type passed in can be assigned from the parameter type in the method. For
// example, if the argument type is HtmlHelper<MyModel> but the method parameter type is HtmlHelper then
// it will match because the argument is assignable to that parameter type and will be able to execute
TypeHelper.IsTypeAssignableFrom,
TypeHelper.IsTypeAssignableFrom,
//This will not ever execute but if it does we need to get the hash code of the string because the hash
// code of a type is random
type => type.FullName.GetHashCode());
@@ -159,7 +175,7 @@ namespace Umbraco.Core.Dynamics
.ToArray();
var methods = GetAllExtensionMethods(runtimeCache, thisType, name, args.Length).ToArray();
return DetermineMethodFromParams(methods, genericType, args);
}
}
+63 -24
View File
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using Semver;
using Umbraco.Core.Configuration;
using Umbraco.Core.Persistence.Migrations;
namespace Umbraco.Core.Events
@@ -13,23 +14,49 @@ namespace Umbraco.Core.Events
/// </summary>
/// <param name="eventObject"></param>
/// <param name="targetVersion"></param>
/// <param name="productName"></param>
/// <param name="canCancel"></param>
/// <param name="configuredVersion"></param>
public MigrationEventArgs(IList<IMigration> eventObject, SemVersion configuredVersion, SemVersion targetVersion, bool canCancel)
: base(eventObject, canCancel)
{
ConfiguredSemVersion = configuredVersion;
TargetSemVersion = targetVersion;
}
public MigrationEventArgs(IList<IMigration> eventObject, SemVersion configuredVersion, SemVersion targetVersion, string productName, bool canCancel)
: this(eventObject, null, configuredVersion, targetVersion, productName, canCancel)
{ }
[Obsolete("Use constructor accepting UmbracoVersion instances instead")]
/// <summary>
/// Constructor accepting multiple migrations that are used in the migration runner
/// </summary>
/// <param name="eventObject"></param>
/// <param name="targetVersion"></param>
/// <param name="canCancel"></param>
/// <param name="configuredVersion"></param>
[Obsolete("Use constructor accepting a product name instead.")]
[EditorBrowsable(EditorBrowsableState.Never)]
public MigrationEventArgs(IList<IMigration> eventObject, SemVersion configuredVersion, SemVersion targetVersion, bool canCancel)
: this(eventObject, null, configuredVersion, targetVersion, GlobalSettings.UmbracoMigrationName, canCancel)
{ }
[Obsolete("Use constructor accepting SemVersion instances and a product name instead.")]
[EditorBrowsable(EditorBrowsableState.Never)]
public MigrationEventArgs(IList<IMigration> eventObject, Version configuredVersion, Version targetVersion, bool canCancel)
: base(eventObject, canCancel)
{
ConfiguredSemVersion = new SemVersion(configuredVersion);
TargetSemVersion = new SemVersion(targetVersion);
}
: this(eventObject, null, new SemVersion(configuredVersion), new SemVersion(targetVersion), GlobalSettings.UmbracoMigrationName, canCancel)
{ }
/// <summary>
/// Constructor accepting multiple migrations that are used in the migration runner
/// </summary>
/// <param name="eventObject"></param>
/// <param name="migrationContext"></param>
/// <param name="targetVersion"></param>
/// <param name="productName"></param>
/// <param name="canCancel"></param>
/// <param name="configuredVersion"></param>
internal MigrationEventArgs(IList<IMigration> eventObject, MigrationContext migrationContext, SemVersion configuredVersion, SemVersion targetVersion, string productName, bool canCancel)
: base(eventObject, canCancel)
{
MigrationContext = migrationContext;
ConfiguredSemVersion = configuredVersion;
TargetSemVersion = targetVersion;
ProductName = productName;
}
/// <summary>
/// Constructor accepting multiple migrations that are used in the migration runner
@@ -39,12 +66,15 @@ namespace Umbraco.Core.Events
/// <param name="targetVersion"></param>
/// <param name="canCancel"></param>
/// <param name="configuredVersion"></param>
[Obsolete("Use constructor accepting a product name instead.")]
[EditorBrowsable(EditorBrowsableState.Never)]
internal MigrationEventArgs(IList<IMigration> eventObject, MigrationContext migrationContext, SemVersion configuredVersion, SemVersion targetVersion, bool canCancel)
: base(eventObject, canCancel)
{
MigrationContext = migrationContext;
ConfiguredSemVersion = configuredVersion;
TargetSemVersion = targetVersion;
ProductName = GlobalSettings.UmbracoMigrationName;
}
/// <summary>
@@ -53,21 +83,28 @@ namespace Umbraco.Core.Events
/// <param name="eventObject"></param>
/// <param name="configuredVersion"></param>
/// <param name="targetVersion"></param>
public MigrationEventArgs(IList<IMigration> eventObject, SemVersion configuredVersion, SemVersion targetVersion)
: base(eventObject)
{
ConfiguredSemVersion = configuredVersion;
TargetSemVersion = targetVersion;
}
/// <param name="productName"></param>
public MigrationEventArgs(IList<IMigration> eventObject, SemVersion configuredVersion, SemVersion targetVersion, string productName)
: this(eventObject, null, configuredVersion, targetVersion, productName, false)
{ }
[Obsolete("Use constructor accepting UmbracoVersion instances instead")]
/// <summary>
/// Constructor accepting multiple migrations that are used in the migration runner
/// </summary>
/// <param name="eventObject"></param>
/// <param name="configuredVersion"></param>
/// <param name="targetVersion"></param>
[Obsolete("Use constructor accepting a product name instead.")]
[EditorBrowsable(EditorBrowsableState.Never)]
public MigrationEventArgs(IList<IMigration> eventObject, SemVersion configuredVersion, SemVersion targetVersion)
: this(eventObject, null, configuredVersion, targetVersion, GlobalSettings.UmbracoMigrationName, false)
{ }
[Obsolete("Use constructor accepting SemVersion instances and a product name instead.")]
[EditorBrowsable(EditorBrowsableState.Never)]
public MigrationEventArgs(IList<IMigration> eventObject, Version configuredVersion, Version targetVersion)
: base(eventObject)
{
ConfiguredSemVersion = new SemVersion(configuredVersion);
TargetSemVersion = new SemVersion(targetVersion);
}
: this(eventObject, null, new SemVersion(configuredVersion), new SemVersion(targetVersion), GlobalSettings.UmbracoMigrationName, false)
{ }
/// <summary>
/// Returns all migrations that were used in the migration runner
@@ -95,6 +132,8 @@ namespace Umbraco.Core.Events
public SemVersion TargetSemVersion { get; private set; }
public string ProductName { get; private set; }
internal MigrationContext MigrationContext { get; private set; }
}
}
+7 -1
View File
@@ -7,6 +7,12 @@ namespace Umbraco.Core.Models
/// </summary
public interface IMediaType : IContentTypeComposition
{
/// <summary>
/// Creates a deep clone of the current entity with its identity/alias and it's property identities reset
/// </summary>
/// <param name="newAlias"></param>
/// <returns></returns>
IMediaType DeepCloneWithResetIdentities(string newAlias);
}
}
+25
View File
@@ -38,5 +38,30 @@ namespace Umbraco.Core.Models
: base(parent, alias)
{
}
/// <summary>
/// Creates a deep clone of the current entity with its identity/alias and it's property identities reset
/// </summary>
/// <returns></returns>
public IMediaType DeepCloneWithResetIdentities(string alias)
{
var clone = (MediaType)DeepClone();
clone.Alias = alias;
clone.Key = Guid.Empty;
foreach (var propertyGroup in clone.PropertyGroups)
{
propertyGroup.ResetIdentity();
propertyGroup.ResetDirtyProperties(false);
}
foreach (var propertyType in clone.PropertyTypes)
{
propertyType.ResetIdentity();
propertyType.ResetDirtyProperties(false);
}
clone.ResetIdentity();
clone.ResetDirtyProperties(false);
return clone;
}
}
}
@@ -114,10 +114,25 @@ namespace Umbraco.Core.Models.PublishedContent
internal static void ClearContentType(int id)
{
Logging.LogHelper.Debug<PublishedContentType>("Clear content type w/id {0}.", () => id);
// requires a predicate because the key does not contain the ID
// faster than key strings comparisons anyway
// we don't support "get all" at the moment - so, cheating
var all = ApplicationContext.Current.ApplicationCache.StaticCache.GetCacheItemsByKeySearch<PublishedContentType>("PublishedContentType_").ToArray();
// the one we want to clear
var clr = all.FirstOrDefault(x => x.Id == id);
if (clr == null) return;
// those that have that one in their composition aliases
// note: CompositionAliases contains all recursive aliases
var oth = all.Where(x => x.CompositionAliases.InvariantContains(clr.Alias)).Select(x => x.Id);
// merge ids
var ids = oth.Concat(new[] { clr.Id }).ToArray();
// clear them all at once
// we don't support "clear many at once" at the moment - so, cheating
ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheObjectTypes<PublishedContentType>(
(key, value) => value.Id == id);
(key, value) => ids.Contains(value.Id));
}
internal static void ClearDataType(int id)
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
@@ -27,18 +28,21 @@ namespace Umbraco.Core.Persistence.Migrations
private readonly IMigration[] _migrations;
[Obsolete("Use the ctor that specifies all dependencies instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
public MigrationRunner(Version currentVersion, Version targetVersion, string productName)
: this(LoggerResolver.Current.Logger, currentVersion, targetVersion, productName)
{
}
[Obsolete("Use the ctor that specifies all dependencies instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
public MigrationRunner(ILogger logger, Version currentVersion, Version targetVersion, string productName)
: this(logger, currentVersion, targetVersion, productName, null)
{
}
[Obsolete("Use the ctor that specifies all dependencies instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
public MigrationRunner(ILogger logger, Version currentVersion, Version targetVersion, string productName, params IMigration[] migrations)
: this(ApplicationContext.Current.Services.MigrationEntryService, logger, new SemVersion(currentVersion), new SemVersion(targetVersion), productName, migrations)
{
@@ -92,7 +96,7 @@ namespace Umbraco.Core.Persistence.Migrations
: OrderedDowngradeMigrations(foundMigrations).ToList();
if (Migrating.IsRaisedEventCancelled(new MigrationEventArgs(migrations, _currentVersion, _targetVersion, true), this))
if (Migrating.IsRaisedEventCancelled(new MigrationEventArgs(migrations, _currentVersion, _targetVersion, _productName, true), this))
{
_logger.Warn<MigrationRunner>("Migration was cancelled by an event");
return false;
@@ -121,7 +125,7 @@ namespace Umbraco.Core.Persistence.Migrations
throw;
}
Migrated.RaiseEvent(new MigrationEventArgs(migrations, migrationContext, _currentVersion, _targetVersion, false), this);
Migrated.RaiseEvent(new MigrationEventArgs(migrations, migrationContext, _currentVersion, _targetVersion, _productName, false), this);
return true;
}
@@ -128,6 +128,34 @@ namespace Umbraco.Core.Persistence.Querying
right = Visit(b.Right);
}
}
else if (operand == "=")
{
// deal with (x == true|false) - most common
var constRight = b.Right as ConstantExpression;
if (constRight != null && constRight.Type == typeof (bool))
return ((bool) constRight.Value) ? VisitNotNot(b.Left) : VisitNot(b.Left);
right = Visit(b.Right);
// deal with (true|false == x) - why not
var constLeft = b.Left as ConstantExpression;
if (constLeft != null && constLeft.Type == typeof (bool))
return ((bool) constLeft.Value) ? VisitNotNot(b.Right) : VisitNot(b.Right);
left = Visit(b.Left);
}
else if (operand == "<>")
{
// deal with (x != true|false) - most common
var constRight = b.Right as ConstantExpression;
if (constRight != null && constRight.Type == typeof(bool))
return ((bool) constRight.Value) ? VisitNot(b.Left) : VisitNotNot(b.Left);
right = Visit(b.Right);
// deal with (true|false != x) - why not
var constLeft = b.Left as ConstantExpression;
if (constLeft != null && constLeft.Type == typeof(bool))
return ((bool) constLeft.Value) ? VisitNot(b.Right) : VisitNotNot(b.Right);
left = Visit(b.Left);
}
else
{
left = Visit(b.Left);
@@ -231,25 +259,47 @@ namespace Umbraco.Core.Persistence.Querying
switch (u.NodeType)
{
case ExpressionType.Not:
var o = Visit(u.Operand);
//use a Not equal operator instead of <> since we don't know that <> works in all sql servers
switch (u.Operand.NodeType)
{
case ExpressionType.MemberAccess:
//In this case it wil be a false property , i.e. x => !Trashed
SqlParameters.Add(true);
return string.Format("NOT ({0} = @0)", o);
default:
//In this case it could be anything else, such as: x => !x.Path.StartsWith("-20")
return string.Format("NOT ({0})", o);
}
return VisitNot(u.Operand);
default:
return Visit(u.Operand);
}
}
private string VisitNot(Expression exp)
{
var o = Visit(exp);
// use a "NOT (...)" syntax instead of "<>" since we don't know whether "<>" works in all sql servers
// also, x.StartsWith(...) translates to "x LIKE '...%'" which we cannot "<>" and have to "NOT (...")
switch (exp.NodeType)
{
case ExpressionType.MemberAccess:
// false property , i.e. x => !Trashed
SqlParameters.Add(true);
return "NOT (" + o + " = @0)";
default:
// could be anything else, such as: x => !x.Path.StartsWith("-20")
return "NOT (" + o + ")";
}
}
private string VisitNotNot(Expression exp)
{
var o = Visit(exp);
switch (exp.NodeType)
{
case ExpressionType.MemberAccess:
// true property, i.e. x => Trashed
SqlParameters.Add(true);
return o + " = @0";
default:
// could be anything else, such as: x => x.Path.StartsWith("-20")
return o;
}
}
protected virtual string VisitNewArray(NewArrayExpression na)
{
@@ -1212,5 +1212,19 @@ AND umbracoNode.id <> @id",
{
return PerformExists(id);
}
public string GetUniqueAlias(string alias)
{
// alias is unique accross ALL content types!
var aliasColumn = SqlSyntax.GetQuotedColumnName("alias");
var aliases = Database.Fetch<string>(@"SELECT cmsContentType." + aliasColumn + @" FROM cmsContentType
INNER JOIN umbracoNode ON cmsContentType.nodeId = umbracoNode.id
WHERE cmsContentType." + aliasColumn + @" LIKE @pattern",
new { pattern = alias + "%", objectType = NodeObjectTypeId });
var i = 1;
string test;
while (aliases.Contains(test = alias + i)) i++;
return test;
}
}
}
@@ -32,5 +32,13 @@ namespace Umbraco.Core.Persistence.Repositories
/// </param>
/// <returns></returns>
IEnumerable<string> GetAllContentTypeAliases(params Guid[] objectTypes);
/// <summary>
/// Derives a unique alias from an existing alias.
/// </summary>
/// <param name="alias">The original alias.</param>
/// <returns>The original alias with a number appended to it, so that it is unique.</returns>
/// /// <remarks>Unique accross all content, media and member types.</remarks>
string GetUniqueAlias(string alias);
}
}
@@ -15,5 +15,13 @@ namespace Umbraco.Core.Persistence.Repositories
IEnumerable<IMediaType> GetByQuery(IQuery<PropertyType> query);
IEnumerable<MoveEventInfo<IMediaType>> Move(IMediaType toMove, EntityContainer container);
/// <summary>
/// Derives a unique alias from an existing alias.
/// </summary>
/// <param name="alias">The original alias.</param>
/// <returns>The original alias with a number appended to it, so that it is unique.</returns>
/// <remarks>Unique accross all content, media and member types.</remarks>
string GetUniqueAlias(string alias);
}
}
@@ -794,7 +794,7 @@ namespace Umbraco.Core.Services
//TODO: This needs to change, if we are deleting a content type, we should just delete the data,
// this method will recursively go lookup every content item, check if any of it's descendants are
// of a different type, move them to the recycle bin, then permanently delete the content items.
// of a different type, move them to the recycle bin, then permanently delete the content items.
// The main problem with this is that for every content item being deleted, events are raised...
// which we need for many things like keeping caches in sync, but we can surely do this MUCH better.
@@ -1067,6 +1067,92 @@ namespace Umbraco.Core.Services
new OperationStatus<MoveOperationStatusType>(MoveOperationStatusType.Success, evtMsgs));
}
public Attempt<OperationStatus<IMediaType, MoveOperationStatusType>> CopyMediaType(IMediaType toCopy, int containerId)
{
var evtMsgs = EventMessagesFactory.Get();
IMediaType copy;
var uow = UowProvider.GetUnitOfWork();
using (var containerRepository = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.MediaTypeContainerGuid))
using (var repository = RepositoryFactory.CreateMediaTypeRepository(uow))
{
try
{
if (containerId > 0)
{
var container = containerRepository.Get(containerId);
if (container == null)
throw new DataOperationException<MoveOperationStatusType>(MoveOperationStatusType.FailedParentNotFound);
}
var alias = repository.GetUniqueAlias(toCopy.Alias);
copy = toCopy.DeepCloneWithResetIdentities(alias);
copy.Name = copy.Name + " (copy)"; // might not be unique
// if it has a parent, and the parent is a content type, unplug composition
// all other compositions remain in place in the copied content type
if (copy.ParentId > 0)
{
var parent = repository.Get(copy.ParentId);
if (parent != null)
copy.RemoveContentType(parent.Alias);
}
copy.ParentId = containerId;
repository.AddOrUpdate(copy);
}
catch (DataOperationException<MoveOperationStatusType> ex)
{
return Attempt.Fail(new OperationStatus<IMediaType, MoveOperationStatusType>(null, ex.Operation, evtMsgs));
}
uow.Commit();
}
return Attempt.Succeed(new OperationStatus<IMediaType, MoveOperationStatusType>(copy, MoveOperationStatusType.Success, evtMsgs));
}
public Attempt<OperationStatus<IContentType, MoveOperationStatusType>> CopyContentType(IContentType toCopy, int containerId)
{
var evtMsgs = EventMessagesFactory.Get();
IContentType copy;
var uow = UowProvider.GetUnitOfWork();
using (var containerRepository = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DocumentTypeContainerGuid))
using (var repository = RepositoryFactory.CreateContentTypeRepository(uow))
{
try
{
if (containerId > 0)
{
var container = containerRepository.Get(containerId);
if (container == null)
throw new DataOperationException<MoveOperationStatusType>(MoveOperationStatusType.FailedParentNotFound);
}
var alias = repository.GetUniqueAlias(toCopy.Alias);
copy = toCopy.DeepCloneWithResetIdentities(alias);
copy.Name = copy.Name + " (copy)"; // might not be unique
// if it has a parent, and the parent is a content type, unplug composition
// all other compositions remain in place in the copied content type
if (copy.ParentId > 0)
{
var parent = repository.Get(copy.ParentId);
if (parent != null)
copy.RemoveContentType(parent.Alias);
}
copy.ParentId = containerId;
repository.AddOrUpdate(copy);
}
catch (DataOperationException<MoveOperationStatusType> ex)
{
return Attempt.Fail(new OperationStatus<IContentType, MoveOperationStatusType>(null, ex.Operation, evtMsgs));
}
uow.Commit();
}
return Attempt.Succeed(new OperationStatus<IContentType, MoveOperationStatusType>(copy, MoveOperationStatusType.Success, evtMsgs));
}
/// <summary>
/// Saves a single <see cref="IMediaType"/> object
/// </summary>
@@ -291,5 +291,7 @@ namespace Umbraco.Core.Services
Attempt<OperationStatus<MoveOperationStatusType>> MoveMediaType(IMediaType toMove, int containerId);
Attempt<OperationStatus<MoveOperationStatusType>> MoveContentType(IContentType toMove, int containerId);
Attempt<OperationStatus<IMediaType, MoveOperationStatusType>> CopyMediaType(IMediaType toCopy, int containerId);
Attempt<OperationStatus<IContentType, MoveOperationStatusType>> CopyContentType(IContentType toCopy, int containerId);
}
}
+59 -28
View File
@@ -22,7 +22,7 @@ namespace Umbraco.Core
/// <summary>
/// A utility class to find all classes of a certain type by reflection in the current bin folder
/// of the web application.
/// of the web application.
/// </summary>
public static class TypeFinder
{
@@ -34,7 +34,7 @@ namespace Umbraco.Core
/// This is a modified version of: http://www.dominicpettifer.co.uk/Blog/44/how-to-get-a-reference-to-all-assemblies-in-the--bin-folder
/// </summary>
/// <remarks>
/// We do this because we cannot use AppDomain.Current.GetAssemblies() as this will return only assemblies that have been
/// We do this because we cannot use AppDomain.Current.GetAssemblies() as this will return only assemblies that have been
/// loaded in the CLR, not all assemblies.
/// See these threads:
/// http://issues.umbraco.org/issue/U5-198
@@ -136,7 +136,7 @@ namespace Umbraco.Core
});
/// <summary>
/// Return a list of found local Assemblies excluding the known assemblies we don't want to scan
/// Return a list of found local Assemblies excluding the known assemblies we don't want to scan
/// and exluding the ones passed in and excluding the exclusion list filter, the results of this are
/// cached for perforance reasons.
/// </summary>
@@ -190,6 +190,7 @@ namespace Umbraco.Core
/// </summary>
/// <remarks>
/// NOTE the comma vs period... comma delimits the name in an Assembly FullName property so if it ends with comma then its an exact name match
/// NOTE this means that "foo." will NOT exclude "foo.dll" but only "foo.*.dll"
/// </remarks>
internal static readonly string[] KnownAssemblyExclusionFilter = new[]
{
@@ -214,7 +215,7 @@ namespace Umbraco.Core
"RouteDebugger,",
"SqlCE4Umbraco,",
"umbraco.datalayer,",
"umbraco.interfaces,",
"umbraco.interfaces,",
//"umbraco.providers,",
//"Umbraco.Web.UI,",
"umbraco.webservices",
@@ -230,9 +231,9 @@ namespace Umbraco.Core
"AutoMapper,",
"AutoMapper.",
"AzureDirectory,",
"itextsharp,",
"itextsharp,",
"UrlRewritingNet.",
"HtmlAgilityPack,",
"HtmlAgilityPack,",
"MiniProfiler,",
"Moq,",
"nunit.framework,",
@@ -371,7 +372,7 @@ namespace Umbraco.Core
var assemblyList = assemblies.ToArray();
//find all assembly references that are referencing the attribute type's assembly since we
//find all assembly references that are referencing the attribute type's assembly since we
//should only be scanning those assemblies because any other assembly will definitely not
//contain a class that has this attribute.
var referencedAssemblies = TypeHelper.GetReferencedAssemblies(attributeType, assemblyList);
@@ -418,7 +419,7 @@ namespace Umbraco.Core
foreach (var subTypesInAssembly in allAttributeTypes.GroupBy(x => x.Assembly))
{
//So that we are not scanning too much, we need to group the sub types:
//So that we are not scanning too much, we need to group the sub types:
// * if there is more than 1 sub type in the same assembly then we should only search on the 'lowest base' type.
// * We should also not search for sub types if the type is sealed since you cannot inherit from a sealed class
// * We should not search for sub types if the type is static since you cannot inherit from them.
@@ -489,7 +490,7 @@ namespace Umbraco.Core
/// <summary>
/// Finds types that are assignable from the assignTypeFrom parameter and will scan for these types in the assembly
/// list passed in, however we will only scan assemblies that have a reference to the assignTypeFrom Type or any type
/// list passed in, however we will only scan assemblies that have a reference to the assignTypeFrom Type or any type
/// deriving from the base type.
/// </summary>
/// <param name="assignTypeFrom"></param>
@@ -513,7 +514,7 @@ namespace Umbraco.Core
var assemblyList = assemblies.ToArray();
//find all assembly references that are referencing the current type's assembly since we
//find all assembly references that are referencing the current type's assembly since we
//should only be scanning those assemblies because any other assembly will definitely not
//contain sub type's of the one we're currently looking for
var referencedAssemblies = TypeHelper.GetReferencedAssemblies(assignTypeFrom, assemblyList);
@@ -560,7 +561,7 @@ namespace Umbraco.Core
foreach (var subTypesInAssembly in allSubTypes.GroupBy(x => x.Assembly))
{
//So that we are not scanning too much, we need to group the sub types:
//So that we are not scanning too much, we need to group the sub types:
// * if there is more than 1 sub type in the same assembly then we should only search on the 'lowest base' type.
// * We should also not search for sub types if the type is sealed since you cannot inherit from a sealed class
// * We should not search for sub types if the type is static since you cannot inherit from them.
@@ -602,39 +603,69 @@ namespace Umbraco.Core
return foundAssignableTypes;
}
private static IEnumerable<Type> GetTypesWithFormattedException(Assembly a)
internal static IEnumerable<Type> GetTypesWithFormattedException(Assembly a)
{
//if the assembly is dynamic, do not try to scan it
if (a.IsDynamic)
return Enumerable.Empty<Type>();
var getAll = a.GetCustomAttribute<AllowPartiallyTrustedCallersAttribute>() == null;
try
{
//we need to detect if an assembly is partially trusted, if so we cannot go interrogating all of it's types
//only its exported types, otherwise we'll get exceptions.
if (a.GetCustomAttribute<AllowPartiallyTrustedCallersAttribute>() == null)
{
return a.GetTypes();
}
else
{
return a.GetExportedTypes();
}
return getAll ? a.GetTypes() : a.GetExportedTypes();
}
catch (ReflectionTypeLoadException ex)
catch (TypeLoadException ex) // GetExportedTypes *can* throw TypeLoadException!
{
var sb = new StringBuilder();
sb.AppendLine("Could not load types from assembly " + a.FullName + ", errors:");
foreach (var loaderException in ex.LoaderExceptions.WhereNotNull())
{
sb.AppendLine("Exception: " + loaderException);
}
throw new ReflectionTypeLoadException(ex.Types, ex.LoaderExceptions, sb.ToString());
AppendCouldNotLoad(sb, a, getAll);
AppendLoaderException(sb, ex);
// rethrow as ReflectionTypeLoadException (for consistency) with new message
throw new ReflectionTypeLoadException(new Type[0], new Exception[] { ex }, sb.ToString());
}
catch (ReflectionTypeLoadException rex) // GetTypes throws ReflectionTypeLoadException
{
var sb = new StringBuilder();
AppendCouldNotLoad(sb, a, getAll);
foreach (var loaderException in rex.LoaderExceptions.WhereNotNull())
AppendLoaderException(sb, loaderException);
// rethrow with new message
throw new ReflectionTypeLoadException(rex.Types, rex.LoaderExceptions, sb.ToString());
}
}
private static void AppendCouldNotLoad(StringBuilder sb, Assembly a, bool getAll)
{
sb.Append("Could not load ");
sb.Append(getAll ? "all" : "exported");
sb.Append(" types from \"");
sb.Append(a.FullName);
sb.AppendLine("\" due to LoaderExceptions, skipping:");
}
private static void AppendLoaderException(StringBuilder sb, Exception loaderException)
{
sb.Append(". ");
sb.Append(loaderException.GetType().FullName);
var tloadex = loaderException as TypeLoadException;
if (tloadex != null)
{
sb.Append(" on ");
sb.Append(tloadex.TypeName);
}
sb.Append(": ");
sb.Append(loaderException.Message);
sb.AppendLine();
}
#endregion
public static Type GetTypeByName(string typeName)
{
@@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Moq;
using NUnit.Framework;
using Semver;
using Umbraco.Core;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Migrations;
using Umbraco.Core.Profiling;
using Umbraco.Core.Services;
using Umbraco.Web.Strategies.Migrations;
namespace Umbraco.Tests.Persistence.Migrations
{
[TestFixture]
public class MigrationStartupHandlerTests
{
[Test]
public void Executes_For_Any_Product_Name_When_Not_Specified()
{
var changed1 = new Args { CountExecuted = 0 };
var testHandler1 = new TestMigrationHandler(changed1);
testHandler1.OnApplicationStarting(Mock.Of<UmbracoApplicationBase>(), new ApplicationContext(CacheHelper.CreateDisabledCacheHelper(), new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>())));
var conn = new Mock<IDbConnection>();
conn.Setup(x => x.BeginTransaction(It.IsAny<IsolationLevel>())).Returns(Mock.Of<IDbTransaction>());
var db = new Mock<Database>(conn.Object);
var runner1 = new MigrationRunner(Mock.Of<IMigrationEntryService>(), Mock.Of<ILogger>(), new SemVersion(1), new SemVersion(2), "Test1",
new IMigration[] { Mock.Of<IMigration>() });
var result1 = runner1.Execute(db.Object, DatabaseProviders.SqlServerCE, false);
Assert.AreEqual(1, changed1.CountExecuted);
}
[Test]
public void Executes_Only_For_Specified_Product_Name()
{
var changed1 = new Args { CountExecuted = 0};
var testHandler1 = new TestMigrationHandler("Test1", changed1);
testHandler1.OnApplicationStarting(Mock.Of<UmbracoApplicationBase>(), new ApplicationContext(CacheHelper.CreateDisabledCacheHelper(), new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>())));
var changed2 = new Args { CountExecuted = 0 };
var testHandler2 = new TestMigrationHandler("Test2", changed2);
testHandler2.OnApplicationStarting(Mock.Of<UmbracoApplicationBase>(), new ApplicationContext(CacheHelper.CreateDisabledCacheHelper(), new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>())));
var conn = new Mock<IDbConnection>();
conn.Setup(x => x.BeginTransaction(It.IsAny<IsolationLevel>())).Returns(Mock.Of<IDbTransaction>());
var db = new Mock<Database>(conn.Object);
var runner1 = new MigrationRunner(Mock.Of<IMigrationEntryService>(), Mock.Of<ILogger>(), new SemVersion(1), new SemVersion(2), "Test1",
new IMigration[] { Mock.Of<IMigration>()});
var result1 = runner1.Execute(db.Object, DatabaseProviders.SqlServerCE, false);
Assert.AreEqual(1, changed1.CountExecuted);
Assert.AreEqual(0, changed2.CountExecuted);
var runner2 = new MigrationRunner(Mock.Of<IMigrationEntryService>(), Mock.Of<ILogger>(), new SemVersion(1), new SemVersion(2), "Test2",
new IMigration[] { Mock.Of<IMigration>() });
var result2 = runner2.Execute(db.Object, DatabaseProviders.SqlServerCE, false);
Assert.AreEqual(1, changed1.CountExecuted);
Assert.AreEqual(1, changed2.CountExecuted);
}
public class Args
{
public int CountExecuted { get; set; }
}
public class TestMigrationHandler : MigrationStartupHander
{
private readonly string _prodName;
private readonly Args _changed;
public TestMigrationHandler(Args changed)
{
_changed = changed;
}
public TestMigrationHandler(string prodName, Args changed)
{
_prodName = prodName;
_changed = changed;
}
protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e)
{
_changed.CountExecuted++;
}
public override string[] TargetProductNames
{
get { return _prodName.IsNullOrWhiteSpace() ? new string[] {} : new[] {_prodName}; }
}
}
}
}
@@ -51,6 +51,18 @@ namespace Umbraco.Tests.Persistence.Querying
Assert.AreEqual("-20%", sql.Arguments[1]);
}
[Test]
public void Where_Clause_With_EqualsFalse_Starts_With()
{
var level = 1;
var sql = new Sql("SELECT *").From<NodeDto>().Where<NodeDto>(x => x.Level == level && x.Path.StartsWith("-20") == false);
Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[level] = @0 AND NOT (upper([umbracoNode].[path]) LIKE upper(@1)))", sql.SQL.Replace("\n", " "));
Assert.AreEqual(2, sql.Arguments.Length);
Assert.AreEqual(level, sql.Arguments[0]);
Assert.AreEqual("-20%", sql.Arguments[1]);
}
[Test]
public void Where_Clause_With_Equals_Clause()
{
@@ -71,6 +83,16 @@ namespace Umbraco.Tests.Persistence.Querying
Assert.AreEqual(true, sql.Arguments[0]);
}
[Test]
public void Where_Clause_With_EqualsFalse_Boolean()
{
var sql = new Sql("SELECT *").From<NodeDto>().Where<NodeDto>(x => x.Trashed == false);
Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (NOT ([umbracoNode].[trashed] = @0))", sql.SQL.Replace("\n", " "));
Assert.AreEqual(1, sql.Arguments.Length);
Assert.AreEqual(true, sql.Arguments[0]);
}
[Test]
public void Where_Clause_With_Boolean()
{
+2 -2
View File
@@ -79,8 +79,8 @@ namespace Umbraco.Tests.Plugins
var originalTypesFound = TypeFinderOriginal.FindClassesOfType<IApplicationStartupHandler>(_assemblies);
Assert.AreEqual(originalTypesFound.Count(), typesFound.Count());
Assert.AreEqual(8, typesFound.Count());
Assert.AreEqual(8, originalTypesFound.Count());
Assert.AreEqual(9, typesFound.Count());
Assert.AreEqual(9, originalTypesFound.Count());
}
[Test]
+2
View File
@@ -175,6 +175,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Persistence\Migrations\MigrationStartupHandlerTests.cs" />
<Compile Include="Web\AngularIntegration\AngularAntiForgeryTests.cs" />
<Compile Include="Web\AngularIntegration\ContentModelSerializationTests.cs" />
<Compile Include="Web\AngularIntegration\JsInitializationTests.cs" />
@@ -244,6 +245,7 @@
<Compile Include="Models\UmbracoEntityTests.cs" />
<Compile Include="Models\UserTests.cs" />
<Compile Include="Models\UserTypeTests.cs" />
<Compile Include="Web\Mvc\RenderModelBinderTests.cs" />
<Compile Include="Web\Mvc\SurfaceControllerTests.cs" />
<Compile Include="Web\Mvc\UmbracoViewPageTests.cs" />
<Compile Include="BootManagers\CoreBootManagerTests.cs" />
@@ -0,0 +1,157 @@
using System.Collections.Generic;
using System.Globalization;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Moq;
using NUnit.Framework;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Web.Models;
using Umbraco.Web.Mvc;
namespace Umbraco.Tests.Web.Mvc
{
[TestFixture]
public class RenderModelBinderTests
{
[Test]
public void Returns_Binder_For_IPublishedContent_And_IRenderModel()
{
var binder = new RenderModelBinder();
var found = binder.GetBinder(typeof (IPublishedContent));
Assert.IsNotNull(found);
found = binder.GetBinder(typeof(IRenderModel));
Assert.IsNotNull(found);
found = binder.GetBinder(typeof(RenderModel));
Assert.IsNotNull(found);
found = binder.GetBinder(typeof(DynamicPublishedContent));
Assert.IsNotNull(found);
found = binder.GetBinder(typeof(MyContent));
Assert.IsNotNull(found);
found = binder.GetBinder(typeof(MyOtherContent));
Assert.IsNull(found);
}
[Test]
public void BindModel_Null_Source_Returns_Null()
{
Assert.IsNull(RenderModelBinder.BindModel(null, typeof(MyContent), CultureInfo.CurrentCulture));
}
[Test]
public void BindModel_Returns_If_Same_Type()
{
var content = new MyContent(Mock.Of<IPublishedContent>());
var bound = RenderModelBinder.BindModel(content, typeof (IPublishedContent), CultureInfo.CurrentCulture);
Assert.AreSame(content, bound);
}
[Test]
public void BindModel_RenderModel_To_IPublishedContent()
{
var content = new MyContent(Mock.Of<IPublishedContent>());
var renderModel = new RenderModel(content, CultureInfo.CurrentCulture);
var bound = RenderModelBinder.BindModel(renderModel, typeof(IPublishedContent), CultureInfo.CurrentCulture);
Assert.AreSame(content, bound);
}
[Test]
public void BindModel_IPublishedContent_To_RenderModel()
{
var content = new MyContent(Mock.Of<IPublishedContent>());
var bound = (IRenderModel)RenderModelBinder.BindModel(content, typeof(RenderModel), CultureInfo.CurrentCulture);
Assert.AreSame(content, bound.Content);
}
[Test]
public void BindModel_IPublishedContent_To_Generic_RenderModel()
{
var content = new MyContent(Mock.Of<IPublishedContent>());
var bound = (IRenderModel)RenderModelBinder.BindModel(content, typeof(RenderModel<MyContent>), CultureInfo.CurrentCulture);
Assert.AreSame(content, bound.Content);
}
[Test]
public void No_DataToken_Returns_Null()
{
var binder = new RenderModelBinder();
var routeData = new RouteData();
var result = binder.BindModel(new ControllerContext(Mock.Of<HttpContextBase>(), routeData, Mock.Of<ControllerBase>()),
new ModelBindingContext());
Assert.IsNull(result);
}
[Test]
public void Invalid_DataToken_Model_Type_Returns_Null()
{
var binder = new RenderModelBinder();
var routeData = new RouteData();
routeData.DataTokens[Core.Constants.Web.UmbracoDataToken] = "hello";
//the value provider is the default implementation
var valueProvider = new Mock<IValueProvider>();
//also IUnvalidatedValueProvider
var invalidatedValueProvider = valueProvider.As<IUnvalidatedValueProvider>();
invalidatedValueProvider.Setup(x => x.GetValue(It.IsAny<string>(), It.IsAny<bool>())).Returns(() =>
new ValueProviderResult(null, "", CultureInfo.CurrentCulture));
var controllerCtx = new ControllerContext(
Mock.Of<HttpContextBase>(http => http.Items == new Dictionary<object, object>()),
routeData,
Mock.Of<ControllerBase>());
var result = binder.BindModel(controllerCtx,
new ModelBindingContext
{
ValueProvider = valueProvider.Object,
ModelMetadata = new ModelMetadata(new EmptyModelMetadataProvider(), null, () => null, typeof(IPublishedContent), "content")
});
Assert.IsNull(result);
}
[Test]
public void IPublishedContent_DataToken_Model_Type_Uses_DefaultImplementation()
{
var content = new MyContent(Mock.Of<IPublishedContent>());
var binder = new RenderModelBinder();
var routeData = new RouteData();
routeData.DataTokens[Core.Constants.Web.UmbracoDataToken] = content;
//the value provider is the default implementation
var valueProvider = new Mock<IValueProvider>();
//also IUnvalidatedValueProvider
var invalidatedValueProvider = valueProvider.As<IUnvalidatedValueProvider>();
invalidatedValueProvider.Setup(x => x.GetValue(It.IsAny<string>(), It.IsAny<bool>())).Returns(() =>
new ValueProviderResult(content, "content", CultureInfo.CurrentCulture));
var controllerCtx = new ControllerContext(
Mock.Of<HttpContextBase>(http => http.Items == new Dictionary<object, object>()),
routeData,
Mock.Of<ControllerBase>());
var result = binder.BindModel(controllerCtx,
new ModelBindingContext
{
ValueProvider = valueProvider.Object,
ModelMetadata = new ModelMetadata(new EmptyModelMetadataProvider(), null, () => null, typeof(IPublishedContent), "content")
});
Assert.AreEqual(content, result);
}
public class MyOtherContent
{
}
public class MyContent : PublishedContentWrapped
{
public MyContent(IPublishedContent content) : base(content)
{
}
}
}
}
@@ -210,7 +210,7 @@ function submit()
if (findDepth(curLineHTML, cc) !== 0) {
// Cursor is inside a <tag>, don't set cursor:
curLineHTML = curLineHTML.replace(cc, '');
doc.setLine(pos.line, curLineHTML);
doc.getLineHandle(pos.line).text = curLineHTML;
}
// Submit HTML to TinyMCE:
+27 -7
View File
@@ -22,15 +22,35 @@ var packages = angular.module("umbraco.packages", []);
//module is initilized.
angular.module("umbraco.views", ["umbraco.viewcache"]);
angular.module("umbraco.viewcache", [])
.run(function($rootScope, $templateCache){
/** For debug mode, always clear template cache to cut down on
.run(function($rootScope, $templateCache) {
/** For debug mode, always clear template cache to cut down on
dev frustration and chrome cache on templates */
if(Umbraco.Sys.ServerVariables.isDebuggingEnabled){
//$rootScope.$on('$viewContentLoaded', function() {
$templateCache.removeAll();
//});
if (Umbraco.Sys.ServerVariables.isDebuggingEnabled) {
$templateCache.removeAll();
}
})
})
.config([
//This ensures that all of our angular views are cache busted, if the path starts with views/ and ends with .html, then
// we will append the cache busting value to it. This way all upgraded sites will not have to worry about browser cache.
"$provide", function($provide) {
return $provide.decorator("$http", [
"$delegate", function($delegate) {
var get = $delegate.get;
$delegate.get = function (url, config) {
if (Umbraco.Sys.ServerVariables.application && url.startsWith("views/") && url.endsWith(".html")) {
var rnd = Umbraco.Sys.ServerVariables.application.version + "." + Umbraco.Sys.ServerVariables.application.cdf;
var _op = (url.indexOf("?") > 0) ? "&" : "?";
url += _op + "umb__rnd=" + rnd;
}
return get(url, config);
};
return $delegate;
}
]);
}
]);
//Call a document callback if defined, this is sort of a dodgy hack to
// be able to configure angular values in the Default.cshtml
@@ -17,7 +17,7 @@
if(convert){
element.html("<i class='icon-section " + convert + "'></i>");
}else{
element.html("<img src='images/tray/" + icon + "'>");
element.html("<img class='icon-section' src='images/tray/" + icon + "'>");
}
//it's a file, normally legacy so look in the icon tray images
}
@@ -1,10 +1,13 @@
(function() {
'use strict';
function EditorHeaderDirective(iconHelper) {
function EditorHeaderDirective(iconHelper, variationsHelper) {
function link(scope, el, attr, ctrl) {
scope.showVariationsQuickSwitch = false;
scope.variations = variationsHelper.getVariations();
scope.openIconPicker = function() {
scope.dialogModel = {
view: "iconpicker",
@@ -24,6 +27,10 @@
}
};
};
scope.toggleVariationsQuickSwitch = function() {
scope.showVariationsQuickSwitch = !scope.showVariationsQuickSwitch;
};
}
var directive = {
@@ -0,0 +1,76 @@
(function() {
'use strict';
function WebsiteSelector() {
function link(scope, el, attr, ctrl) {
scope.showWebsiteDropdown = false;
scope.selectedWebsite = {};
function activate() {
for(var i = 0; i < scope.websites.length; i++) {
var website = scope.websites[i];
if(website.selected) {
scope.selectedWebsite = website;
}
}
}
scope.toggleWebsiteDropDown = function() {
scope.showWebsiteDropdown = !scope.showWebsiteDropdown;
};
scope.closeWebsiteDropDown = function() {
scope.showWebsiteDropdown = false;
};
scope.createWebsite = function() {
if(scope.onCreateWebsite) {
scope.onCreateWebsite();
scope.closeWebsiteDropDown();
}
};
scope.clickWebsite = function(website) {
if(scope.onClickWebsite) {
scope.onClickWebsite(website);
setSelectedWebsite(website, scope.websites);
scope.closeWebsiteDropDown();
}
};
function setSelectedWebsite(selectedWebsite, websites) {
for(var i = 0; i < websites.length; i++) {
var website = websites[i];
website.selected = false;
}
selectedWebsite.selected = true;
scope.selectedWebsite = selectedWebsite;
}
scope.$watch('websites', function(newValue, oldValue){
activate();
}, true);
}
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/components/umb-website-selector.html',
link: link,
scope: {
websites: "=",
onCreateWebsite: "=",
onClickWebsite: "="
}
};
return directive;
}
angular.module('umbraco.directives').directive('umbWebsiteSelector', WebsiteSelector);
})();
@@ -509,6 +509,16 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
'Failed to check permission for item ' + id);
},
getPermissions: function (nodeIds) {
return umbRequestHelper.resourcePromise(
$http.post(
umbRequestHelper.getApiUrl(
"contentApiBaseUrl",
"GetPermissions"),
nodeIds),
'Failed to get permissions');
},
/**
* @ngdoc method
* @name umbraco.resources.contentResource#save
@@ -29,7 +29,7 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter) {
filterContentTypes: filterContentTypes,
filterPropertyTypes: filterPropertyTypes
};
return umbRequestHelper.resourcePromise(
$http.post(
umbRequestHelper.getApiUrl(
@@ -201,9 +201,9 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter) {
* .then(function() {
* alert("node was moved");
* }, function(err){
* alert("node didnt move:" + err.data.Message);
* alert("node didnt move:" + err.data.Message);
* });
* </pre>
* </pre>
* @param {Object} args arguments object
* @param {Int} args.idd the ID of the node to move
* @param {Int} args.parentId the ID of the parent node to move to
@@ -230,6 +230,26 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter) {
'Failed to move content');
},
copy: function(args) {
if (!args) {
throw "args cannot be null";
}
if (!args.parentId) {
throw "args.parentId cannot be null";
}
if (!args.id) {
throw "args.id cannot be null";
}
return umbRequestHelper.resourcePromise(
$http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCopy"),
{
parentId: args.parentId,
id: args.id
}),
'Failed to copy content');
},
createContainer: function(parentId, name) {
return umbRequestHelper.resourcePromise(
@@ -237,7 +257,7 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter) {
'Failed to create a folder under parent id ' + parentId);
}
};
}
angular.module('umbraco.resources').factory('contentTypeResource', contentTypeResource);
@@ -53,7 +53,7 @@ function mediaTypeResource($q, $http, umbRequestHelper, umbDataFormatter) {
* .then(function(array) {
* $scope.type = type;
* });
* </pre>
* </pre>
* @param {Int} mediaId id of the media item to retrive allowed child types for
* @returns {Promise} resourcePromise object.
*
@@ -145,9 +145,9 @@ function mediaTypeResource($q, $http, umbRequestHelper, umbDataFormatter) {
* .then(function() {
* alert("node was moved");
* }, function(err){
* alert("node didnt move:" + err.data.Message);
* alert("node didnt move:" + err.data.Message);
* });
* </pre>
* </pre>
* @param {Object} args arguments object
* @param {Int} args.idd the ID of the node to move
* @param {Int} args.parentId the ID of the parent node to move to
@@ -174,6 +174,26 @@ function mediaTypeResource($q, $http, umbRequestHelper, umbDataFormatter) {
'Failed to move content');
},
copy: function (args) {
if (!args) {
throw "args cannot be null";
}
if (!args.parentId) {
throw "args.parentId cannot be null";
}
if (!args.id) {
throw "args.id cannot be null";
}
return umbRequestHelper.resourcePromise(
$http.post(umbRequestHelper.getApiUrl("mediaTypeApiBaseUrl", "PostCopy"),
{
parentId: args.parentId,
id: args.id
}),
'Failed to copy content');
},
createContainer: function(parentId, name) {
return umbRequestHelper.resourcePromise(
@@ -16,7 +16,7 @@ function treeResource($q, $http, umbRequestHelper) {
/** internal method to get the tree menu url */
function getTreeMenuUrl(node) {
if (!node.menuUrl) {
throw "No menuUrl property found on the tree node, cannot load menu";
return null;
}
return node.menuUrl;
}
@@ -26,10 +26,16 @@ function treeResource($q, $http, umbRequestHelper) {
/** Loads in the data to display the nodes menu */
loadMenu: function (node) {
return umbRequestHelper.resourcePromise(
$http.get(getTreeMenuUrl(node)),
"Failed to retrieve data for a node's menu " + node.id);
var treeMenuUrl = getTreeMenuUrl(node);
if (treeMenuUrl !== undefined && treeMenuUrl !== null && treeMenuUrl.length > 0) {
return umbRequestHelper.resourcePromise(
$http.get(getTreeMenuUrl(node)),
"Failed to retrieve data for a node's menu " + node.id);
} else {
return $q.reject({
errorMsg: "No tree menu url defined for node " + node.id
});
}
},
/** Loads in the data to display the nodes for an application */
@@ -1,270 +1,323 @@
// This service was based on OpenJS library available in BSD License
// http://www.openjs.com/scripts/events/keyboard_shortcuts/index.php
angular.module('umbraco.services')
.factory('keyboardService', ['$window', '$timeout', function ($window, $timeout) {
var keyboardManagerService = {};
var defaultOpt = {
'type': 'keydown',
'propagate': false,
'inputDisabled': false,
'target': $window.document,
'keyCode': false
};
var isMac = navigator.platform.toUpperCase().indexOf('MAC')>=0;
function keyboardService($window, $timeout) {
var keyboardManagerService = {};
var defaultOpt = {
'type': 'keydown',
'propagate': false,
'inputDisabled': false,
'target': $window.document,
'keyCode': false
};
// Store all keyboard combination shortcuts
keyboardManagerService.keyboardEvent = {};
// Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken
var shift_nums = {
"`": "~",
"1": "!",
"2": "@",
"3": "#",
"4": "$",
"5": "%",
"6": "^",
"7": "&",
"8": "*",
"9": "(",
"0": ")",
"-": "_",
"=": "+",
";": ":",
"'": "\"",
",": "<",
".": ">",
"/": "?",
"\\": "|"
};
// Special Keys - and their codes
var special_keys = {
'esc': 27,
'escape': 27,
'tab': 9,
'space': 32,
'return': 13,
'enter': 13,
'backspace': 8,
// Add a new keyboard combination shortcut
keyboardManagerService.bind = function (label, callback, opt) {
'scrolllock': 145,
'scroll_lock': 145,
'scroll': 145,
'capslock': 20,
'caps_lock': 20,
'caps': 20,
'numlock': 144,
'num_lock': 144,
'num': 144,
//replace ctrl key with meta key
if(isMac && label !== "ctrl+space"){
label = label.replace("ctrl","meta");
}
'pause': 19,
'break': 19,
var fct, elt, code, k;
// Initialize opt object
opt = angular.extend({}, defaultOpt, opt);
label = label.toLowerCase();
elt = opt.target;
if(typeof opt.target === 'string'){
elt = document.getElementById(opt.target);
}
'insert': 45,
'home': 36,
'delete': 46,
'end': 35,
'pageup': 33,
'page_up': 33,
'pu': 33,
fct = function (e) {
e = e || $window.event;
'pagedown': 34,
'page_down': 34,
'pd': 34,
// Disable event handler when focus input and textarea
if (opt['inputDisabled']) {
var elt;
if (e.target){
elt = e.target;
}else if (e.srcElement){
elt = e.srcElement;
}
'left': 37,
'up': 38,
'right': 39,
'down': 40,
if (elt.nodeType === 3){elt = elt.parentNode;}
if (elt.tagName === 'INPUT' || elt.tagName === 'TEXTAREA'){return;}
}
'f1': 112,
'f2': 113,
'f3': 114,
'f4': 115,
'f5': 116,
'f6': 117,
'f7': 118,
'f8': 119,
'f9': 120,
'f10': 121,
'f11': 122,
'f12': 123
};
// Find out which key is pressed
if (e.keyCode){
code = e.keyCode;
}else if (e.which){
code = e.which;
}
var isMac = navigator.platform.toUpperCase().indexOf('MAC')>=0;
var character = String.fromCharCode(code).toLowerCase();
// The event handler for bound element events
function eventHandler(e) {
e = e || $window.event;
if (code === 188){character = ",";} // If the user presses , when the type is onkeydown
if (code === 190){character = ".";} // If the user presses , when the type is onkeydown
var code, k;
var keys = label.split("+");
// Key Pressed - counts the number of valid keypresses - if it is same as the number of keys, the shortcut function is invoked
var kp = 0;
// Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken
var shift_nums = {
"`":"~",
"1":"!",
"2":"@",
"3":"#",
"4":"$",
"5":"%",
"6":"^",
"7":"&",
"8":"*",
"9":"(",
"0":")",
"-":"_",
"=":"+",
";":":",
"'":"\"",
",":"<",
".":">",
"/":"?",
"\\":"|"
};
// Special Keys - and their codes
var special_keys = {
'esc':27,
'escape':27,
'tab':9,
'space':32,
'return':13,
'enter':13,
'backspace':8,
// Find out which key is pressed
if (e.keyCode)
{
code = e.keyCode;
}
else if (e.which) {
code = e.which;
}
'scrolllock':145,
'scroll_lock':145,
'scroll':145,
'capslock':20,
'caps_lock':20,
'caps':20,
'numlock':144,
'num_lock':144,
'num':144,
var character = String.fromCharCode(code).toLowerCase();
'pause':19,
'break':19,
if (code === 188){character = ",";} // If the user presses , when the type is onkeydown
if (code === 190){character = ".";} // If the user presses , when the type is onkeydown
'insert':45,
'home':36,
'delete':46,
'end':35,
var propagate = true;
'pageup':33,
'page_up':33,
'pu':33,
//Now we need to determine which shortcut this event is for, we'll do this by iterating over each
//registered shortcut to find the match. We use Find here so that the loop exits as soon
//as we've found the one we're looking for
_.find(_.keys(keyboardManagerService.keyboardEvent), function(key) {
'pagedown':34,
'page_down':34,
'pd':34,
var shortcutLabel = key;
var shortcutVal = keyboardManagerService.keyboardEvent[key];
'left':37,
'up':38,
'right':39,
'down':40,
// Key Pressed - counts the number of valid keypresses - if it is same as the number of keys, the shortcut function is invoked
var kp = 0;
'f1':112,
'f2':113,
'f3':114,
'f4':115,
'f5':116,
'f6':117,
'f7':118,
'f8':119,
'f9':120,
'f10':121,
'f11':122,
'f12':123
};
// Some modifiers key
var modifiers = {
shift: {
wanted: false,
pressed: e.shiftKey ? true : false
},
ctrl : {
wanted: false,
pressed: e.ctrlKey ? true : false
},
alt : {
wanted: false,
pressed: e.altKey ? true : false
},
meta : { //Meta is Mac specific
wanted: false,
pressed: e.metaKey ? true : false
}
};
// Foreach keys in label (split on +)
var l = keys.length;
for (var i = 0; i < l; i++) {
// Some modifiers key
var modifiers = {
shift: {
wanted: false,
pressed: e.shiftKey ? true : false
},
ctrl: {
wanted: false,
pressed: e.ctrlKey ? true : false
},
alt: {
wanted: false,
pressed: e.altKey ? true : false
},
meta: { //Meta is Mac specific
wanted: false,
pressed: e.metaKey ? true : false
}
};
var k=keys[i];
switch (k) {
case 'ctrl':
case 'control':
kp++;
modifiers.ctrl.wanted = true;
break;
case 'shift':
case 'alt':
case 'meta':
kp++;
modifiers[k].wanted = true;
break;
}
var keys = shortcutLabel.split("+");
var opt = shortcutVal.opt;
var callback = shortcutVal.callback;
if (k.length > 1) { // If it is a special key
if(special_keys[k] === code){
kp++;
}
// Foreach keys in label (split on +)
var l = keys.length;
for (var i = 0; i < l; i++) {
} else if (opt['keyCode']) { // If a specific key is set into the config
if (opt['keyCode'] === code) {
kp++;
}
var k = keys[i];
switch (k) {
case 'ctrl':
case 'control':
kp++;
modifiers.ctrl.wanted = true;
break;
case 'shift':
case 'alt':
case 'meta':
kp++;
modifiers[k].wanted = true;
break;
}
} else { // The special keys did not match
if(character === k) {
kp++;
}else {
if(shift_nums[character] && e.shiftKey) { // Stupid Shift key bug created by using lowercase
character = shift_nums[character];
if(character === k){
kp++;
}
}
}
}
if (k.length > 1) { // If it is a special key
if (special_keys[k] === code) {
kp++;
}
}
else if (opt['keyCode']) { // If a specific key is set into the config
if (opt['keyCode'] === code) {
kp++;
}
}
else { // The special keys did not match
if (character === k) {
kp++;
}
else {
if (shift_nums[character] && e.shiftKey) { // Stupid Shift key bug created by using lowercase
character = shift_nums[character];
if (character === k) {
kp++;
}
}
}
}
} //for end
} //for end
if(kp === keys.length &&
modifiers.ctrl.pressed === modifiers.ctrl.wanted &&
modifiers.shift.pressed === modifiers.shift.wanted &&
modifiers.alt.pressed === modifiers.alt.wanted &&
modifiers.meta.pressed === modifiers.meta.wanted) {
$timeout(function() {
callback(e);
}, 1);
if (kp === keys.length &&
modifiers.ctrl.pressed === modifiers.ctrl.wanted &&
modifiers.shift.pressed === modifiers.shift.wanted &&
modifiers.alt.pressed === modifiers.alt.wanted &&
modifiers.meta.pressed === modifiers.meta.wanted) {
if(!opt['propagate']) { // Stop the event
// e.cancelBubble is supported by IE - this will kill the bubbling process.
e.cancelBubble = true;
e.returnValue = false;
//found the right callback!
// e.stopPropagation works in Firefox.
if (e.stopPropagation) {
e.stopPropagation();
e.preventDefault();
}
return false;
}
}
};
// Store shortcut
keyboardManagerService.keyboardEvent[label] = {
'callback': fct,
'target': elt,
'event': opt['type']
};
// Disable event handler when focus input and textarea
if (opt['inputDisabled']) {
var elt;
if (e.target) {
elt = e.target;
} else if (e.srcElement) {
elt = e.srcElement;
}
//Attach the function with the event
if(elt.addEventListener){
elt.addEventListener(opt['type'], fct, false);
}else if(elt.attachEvent){
elt.attachEvent('on' + opt['type'], fct);
}else{
elt['on' + opt['type']] = fct;
}
};
// Remove the shortcut - just specify the shortcut and I will remove the binding
keyboardManagerService.unbind = function (label) {
label = label.toLowerCase();
var binding = keyboardManagerService.keyboardEvent[label];
delete(keyboardManagerService.keyboardEvent[label]);
if (elt.nodeType === 3) { elt = elt.parentNode; }
if (elt.tagName === 'INPUT' || elt.tagName === 'TEXTAREA') {
//This exits the Find loop
return true;
}
}
if(!binding){return;}
$timeout(function () {
callback(e);
}, 1);
var type = binding['event'],
if (!opt['propagate']) { // Stop the event
propagate = false;
}
//This exits the Find loop
return true;
}
//we haven't found one so continue looking
return false;
});
// Stop the event if required
if (!propagate) {
// e.cancelBubble is supported by IE - this will kill the bubbling process.
e.cancelBubble = true;
e.returnValue = false;
// e.stopPropagation works in Firefox.
if (e.stopPropagation) {
e.stopPropagation();
e.preventDefault();
}
return false;
}
}
// Store all keyboard combination shortcuts
keyboardManagerService.keyboardEvent = {};
// Add a new keyboard combination shortcut
keyboardManagerService.bind = function (label, callback, opt) {
//replace ctrl key with meta key
if(isMac && label !== "ctrl+space"){
label = label.replace("ctrl","meta");
}
var elt;
// Initialize opt object
opt = angular.extend({}, defaultOpt, opt);
label = label.toLowerCase();
elt = opt.target;
if(typeof opt.target === 'string'){
elt = document.getElementById(opt.target);
}
//Ensure we aren't double binding to the same element + type otherwise we'll end up multi-binding
// and raising events for now reason. So here we'll check if the event is already registered for the element
var boundValues = _.values(keyboardManagerService.keyboardEvent);
var found = _.find(boundValues, function (i) {
return i.target === elt && i.event === opt['type'];
});
// Store shortcut
keyboardManagerService.keyboardEvent[label] = {
'callback': callback,
'target': elt,
'opt': opt['type']
};
if (!found) {
//Attach the function with the event
if (elt.addEventListener) {
elt.addEventListener(opt['type'], eventHandler, false);
} else if (elt.attachEvent) {
elt.attachEvent('on' + opt['type'], eventHandler);
} else {
elt['on' + opt['type']] = eventHandler;
}
}
};
// Remove the shortcut - just specify the shortcut and I will remove the binding
keyboardManagerService.unbind = function (label) {
label = label.toLowerCase();
var binding = keyboardManagerService.keyboardEvent[label];
delete(keyboardManagerService.keyboardEvent[label]);
if(!binding){return;}
var type = binding['event'],
elt = binding['target'],
callback = binding['callback'];
if(elt.detachEvent){
elt.detachEvent('on' + type, callback);
}else if(elt.removeEventListener){
elt.removeEventListener(type, callback, false);
}else{
elt['on'+type] = false;
}
};
//
if(elt.detachEvent){
elt.detachEvent('on' + type, callback);
}else if(elt.removeEventListener){
elt.removeEventListener(type, callback, false);
}else{
elt['on'+type] = false;
}
};
//
return keyboardManagerService;
}]);
return keyboardManagerService;
}
@@ -1,288 +1,319 @@
(function() {
'use strict';
(function () {
'use strict';
function listViewHelper(localStorageService) {
function listViewHelper(localStorageService) {
var firstSelectedIndex = 0;
var localStorageKey = "umblistViewLayout";
var firstSelectedIndex = 0;
var localStorageKey = "umblistViewLayout";
function getLayout(nodeId, availableLayouts) {
function getLayout(nodeId, availableLayouts) {
var storedLayouts = [];
var storedLayouts = [];
if(localStorageService.get(localStorageKey)) {
storedLayouts = localStorageService.get(localStorageKey);
}
if (storedLayouts && storedLayouts.length > 0) {
for (var i = 0; storedLayouts.length > i; i++) {
var layout = storedLayouts[i];
if (layout.nodeId === nodeId) {
return setLayout(nodeId, layout, availableLayouts);
}
}
}
return getFirstAllowedLayout(availableLayouts);
}
function setLayout(nodeId, selectedLayout, availableLayouts) {
var activeLayout = {};
var layoutFound = false;
for (var i = 0; availableLayouts.length > i; i++) {
var layout = availableLayouts[i];
if (layout.path === selectedLayout.path) {
activeLayout = layout;
layout.active = true;
layoutFound = true;
} else {
layout.active = false;
}
}
if(!layoutFound) {
activeLayout = getFirstAllowedLayout(availableLayouts);
}
saveLayoutInLocalStorage(nodeId, activeLayout);
return activeLayout;
}
function saveLayoutInLocalStorage(nodeId, selectedLayout) {
var layoutFound = false;
var storedLayouts = [];
if(localStorageService.get(localStorageKey)) {
storedLayouts = localStorageService.get(localStorageKey);
}
if(storedLayouts.length > 0) {
for(var i = 0; storedLayouts.length > i; i++) {
var layout = storedLayouts[i];
if(layout.nodeId === nodeId) {
layout.path = selectedLayout.path;
layoutFound = true;
}
}
}
if(!layoutFound) {
var storageObject = {
"nodeId": nodeId,
"path": selectedLayout.path
};
storedLayouts.push(storageObject);
}
localStorageService.set(localStorageKey, storedLayouts);
}
function getFirstAllowedLayout(layouts) {
var firstAllowedLayout = {};
for (var i = 0; layouts.length > i; i++) {
var layout = layouts[i];
if (layout.selected === true) {
firstAllowedLayout = layout;
break;
if (localStorageService.get(localStorageKey)) {
storedLayouts = localStorageService.get(localStorageKey);
}
}
return firstAllowedLayout;
}
if (storedLayouts && storedLayouts.length > 0) {
for (var i = 0; storedLayouts.length > i; i++) {
var layout = storedLayouts[i];
if (layout.nodeId === nodeId) {
return setLayout(nodeId, layout, availableLayouts);
}
}
function selectHandler(selectedItem, selectedIndex, items, selection, $event) {
}
var start = 0;
var end = 0;
var item = null;
return getFirstAllowedLayout(availableLayouts);
if ($event.shiftKey === true) {
}
if(selectedIndex > firstSelectedIndex) {
function setLayout(nodeId, selectedLayout, availableLayouts) {
start = firstSelectedIndex;
end = selectedIndex;
var activeLayout = {};
var layoutFound = false;
for (; end >= start; start++) {
item = items[start];
selectItem(item, selection);
}
for (var i = 0; availableLayouts.length > i; i++) {
var layout = availableLayouts[i];
if (layout.path === selectedLayout.path) {
activeLayout = layout;
layout.active = true;
layoutFound = true;
} else {
layout.active = false;
}
}
if (!layoutFound) {
activeLayout = getFirstAllowedLayout(availableLayouts);
}
saveLayoutInLocalStorage(nodeId, activeLayout);
return activeLayout;
}
function saveLayoutInLocalStorage(nodeId, selectedLayout) {
var layoutFound = false;
var storedLayouts = [];
if (localStorageService.get(localStorageKey)) {
storedLayouts = localStorageService.get(localStorageKey);
}
if (storedLayouts.length > 0) {
for (var i = 0; storedLayouts.length > i; i++) {
var layout = storedLayouts[i];
if (layout.nodeId === nodeId) {
layout.path = selectedLayout.path;
layoutFound = true;
}
}
}
if (!layoutFound) {
var storageObject = {
"nodeId": nodeId,
"path": selectedLayout.path
};
storedLayouts.push(storageObject);
}
localStorageService.set(localStorageKey, storedLayouts);
}
function getFirstAllowedLayout(layouts) {
var firstAllowedLayout = {};
for (var i = 0; layouts.length > i; i++) {
var layout = layouts[i];
if (layout.selected === true) {
firstAllowedLayout = layout;
break;
}
}
return firstAllowedLayout;
}
function selectHandler(selectedItem, selectedIndex, items, selection, $event) {
var start = 0;
var end = 0;
var item = null;
if ($event.shiftKey === true) {
if (selectedIndex > firstSelectedIndex) {
start = firstSelectedIndex;
end = selectedIndex;
for (; end >= start; start++) {
item = items[start];
selectItem(item, selection);
}
} else {
start = firstSelectedIndex;
end = selectedIndex;
for (; end <= start; start--) {
item = items[start];
selectItem(item, selection);
}
}
} else {
start = firstSelectedIndex;
end = selectedIndex;
if (selectedItem.selected) {
deselectItem(selectedItem, selection);
} else {
selectItem(selectedItem, selection);
}
for (; end <= start; start--) {
item = items[start];
selectItem(item, selection);
}
firstSelectedIndex = selectedIndex;
}
} else {
}
if(selectedItem.selected) {
deselectItem(selectedItem, selection);
} else {
selectItem(selectedItem, selection);
function selectItem(item, selection) {
var isSelected = false;
for (var i = 0; selection.length > i; i++) {
var selectedItem = selection[i];
if (item.id === selectedItem.id) {
isSelected = true;
}
}
if (!isSelected) {
selection.push({ id: item.id });
item.selected = true;
}
}
function deselectItem(item, selection) {
for (var i = 0; selection.length > i; i++) {
var selectedItem = selection[i];
if (item.id === selectedItem.id) {
selection.splice(i, 1);
item.selected = false;
}
}
}
function clearSelection(items, folders, selection) {
var i = 0;
selection.length = 0;
if (angular.isArray(items)) {
for (i = 0; items.length > i; i++) {
var item = items[i];
item.selected = false;
}
}
firstSelectedIndex = selectedIndex;
}
}
function selectItem(item, selection) {
var isSelected = false;
for (var i = 0; selection.length > i; i++) {
var selectedItem = selection[i];
if (item.id === selectedItem.id) {
isSelected = true;
if (angular.isArray(items)) {
for (i = 0; folders.length > i; i++) {
var folder = folders[i];
folder.selected = false;
}
}
}
if(!isSelected) {
selection.push({id: item.id});
item.selected = true;
}
}
}
function deselectItem(item, selection) {
for (var i = 0; selection.length > i; i++) {
var selectedItem = selection[i];
if (item.id === selectedItem.id) {
selection.splice(i, 1);
item.selected = false;
function selectAllItems(items, selection, $event) {
var checkbox = $event.target;
var clearSelection = false;
if (!angular.isArray(items)) {
return;
}
}
}
function clearSelection(items, folders, selection) {
selection.length = 0;
var i = 0;
for (var i = 0; i < items.length; i++) {
selection.length = 0;
var item = items[i];
if (checkbox.checked) {
selection.push({ id: item.id });
} else {
clearSelection = true;
}
item.selected = checkbox.checked;
if(angular.isArray(items)) {
for(i = 0; items.length > i; i++) {
var item = items[i];
item.selected = false;
}
}
if(angular.isArray(items)) {
for(i = 0; folders.length > i; i++) {
var folder = folders[i];
folder.selected = false;
if (clearSelection) {
selection.length = 0;
}
}
}
function selectAllItems(items, selection, $event) {
}
var checkbox = $event.target;
var clearSelection = false;
function isSelectedAll(items, selection) {
if (!angular.isArray(items)) {
return;
}
var numberOfSelectedItem = 0;
selection.length = 0;
for (var itemIndex = 0; items.length > itemIndex; itemIndex++) {
var item = items[itemIndex];
for (var i = 0; i < items.length; i++) {
for (var selectedIndex = 0; selection.length > selectedIndex; selectedIndex++) {
var selectedItem = selection[selectedIndex];
var item = items[i];
if (item.id === selectedItem.id) {
numberOfSelectedItem++;
}
}
if (checkbox.checked) {
selection.push({id: item.id});
} else {
clearSelection = true;
}
}
item.selected = checkbox.checked;
if (numberOfSelectedItem === items.length) {
return true;
}
}
}
if (clearSelection) {
selection.length = 0;
}
function setSortingDirection(col, direction, options) {
return options.orderBy.toUpperCase() === col.toUpperCase() && options.orderDirection === direction;
}
}
function setSorting(field, allow, options) {
if (allow) {
options.orderBy = field;
function isSelectedAll(items, selection) {
if (options.orderDirection === "desc") {
options.orderDirection = "asc";
} else {
options.orderDirection = "desc";
}
}
}
//This takes in a dictionary of Ids with Permissions and determines
// the intersect of all permissions to return an object representing the
// listview button permissions
function getButtonPermissions(unmergedPermissions, currentIdsWithPermissions) {
if (currentIdsWithPermissions == null) {
currentIdsWithPermissions = {};
}
var numberOfSelectedItem = 0;
//merge the newly retrieved permissions to the main dictionary
_.each(unmergedPermissions, function (value, key, list) {
currentIdsWithPermissions[key] = value;
});
for(var itemIndex = 0; items.length > itemIndex; itemIndex++) {
var item = items[itemIndex];
//get the intersect permissions
var arr = [];
_.each(currentIdsWithPermissions, function (value, key, list) {
arr.push(value);
});
for(var selectedIndex = 0; selection.length > selectedIndex; selectedIndex++) {
var selectedItem = selection[selectedIndex];
//we need to use 'apply' to call intersection with an array of arrays,
//see: http://stackoverflow.com/a/16229480/694494
var intersectPermissions = _.intersection.apply(_, arr);
if(item.id === selectedItem.id) {
numberOfSelectedItem++;
}
}
return {
canCopy: _.contains(intersectPermissions, 'O'), //Magic Char = O
canCreate: _.contains(intersectPermissions, 'C'), //Magic Char = C
canDelete: _.contains(intersectPermissions, 'D'), //Magic Char = D
canMove: _.contains(intersectPermissions, 'M'), //Magic Char = M
canPublish: _.contains(intersectPermissions, 'U'), //Magic Char = U
canUnpublish: _.contains(intersectPermissions, 'U'), //Magic Char = Z (however UI says it can't be set, so if we can publish 'U' we can unpublish)
};
}
}
var service = {
getLayout: getLayout,
getFirstAllowedLayout: getFirstAllowedLayout,
setLayout: setLayout,
saveLayoutInLocalStorage: saveLayoutInLocalStorage,
selectHandler: selectHandler,
selectItem: selectItem,
deselectItem: deselectItem,
clearSelection: clearSelection,
selectAllItems: selectAllItems,
isSelectedAll: isSelectedAll,
setSortingDirection: setSortingDirection,
setSorting: setSorting,
getButtonPermissions: getButtonPermissions
};
if(numberOfSelectedItem === items.length) {
return true;
}
return service;
}
}
function setSortingDirection(col, direction, options) {
return options.orderBy.toUpperCase() === col.toUpperCase() && options.orderDirection === direction;
}
function setSorting(field, allow, options) {
if (allow) {
options.orderBy = field;
if (options.orderDirection === "desc") {
options.orderDirection = "asc";
} else {
options.orderDirection = "desc";
}
}
}
var service = {
getLayout: getLayout,
getFirstAllowedLayout: getFirstAllowedLayout,
setLayout: setLayout,
saveLayoutInLocalStorage: saveLayoutInLocalStorage,
selectHandler: selectHandler,
selectItem: selectItem,
deselectItem: deselectItem,
clearSelection: clearSelection,
selectAllItems: selectAllItems,
isSelectedAll: isSelectedAll,
setSortingDirection: setSortingDirection,
setSorting: setSorting
};
return service;
}
angular.module('umbraco.services').factory('listViewHelper', listViewHelper);
angular.module('umbraco.services').factory('listViewHelper', listViewHelper);
})();
@@ -21,6 +21,7 @@ function packageHelper(assetsService, treeService, eventsService, $templateCache
}
angular.module('umbraco.services').factory('packageHelper', packageHelper);
//TODO: I believe this is obsolete
function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, mediaHelper, umbRequestHelper) {
return {
/** sets the image's url, thumbnail and if its a folder */
@@ -319,7 +320,6 @@ function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, me
}
};
}
angular.module("umbraco.services").factory("umbPhotoFolderHelper", umbPhotoFolderHelper);
/**
@@ -0,0 +1,65 @@
(function() {
'use strict';
function variationsHelper() {
var variations = [];
function setMaster(master) {
master.master = true;
variations.push(master);
}
function getVariations() {
return variations;
}
function createVariation(variation) {
var newVariation = angular.copy(variation);
newVariation.master = false;
variations.push(newVariation);
}
function saveVariation(updatedVariation) {
for(var i = 0; i < variations.length; i++) {
var variation = variations[i];
if(variation.id === updatedVariation) {
variation.name = updatedVariation.name;
variation.description = updatedVariation.description;
}
}
}
function cloneVariation(variation) {
var variationClone = angular.copy(variation);
variationClone.master = false;
variationClone.published = false;
variations.push(variationClone);
console.log(variations);
}
function deleteVariation(variation, event, index) {
variations.splice(index, 1);
}
var service = {
setMaster: setMaster,
getVariations: getVariations,
createVariation: createVariation,
saveVariation: saveVariation,
cloneVariation: cloneVariation,
deleteVariation: deleteVariation
};
return service;
}
angular.module('umbraco.services').factory('variationsHelper', variationsHelper);
})();
@@ -155,6 +155,53 @@ function NavigationController($scope, $rootScope, $location, $log, $routeParams,
}
};
$scope.websites = [
{
name: "English",
language: "en-UK",
selected: true
},
{
name: "Dansk",
language: "da-DK",
selected: false
}
];
$scope.createWebsite = function() {
$scope.createWebsiteOverlay = {
view: "createwebsite",
title: "Create website",
submitButtonLabel: "Create website",
show: true,
submit: function(model) {
// deselct all current sites
for(var i = 0; i < $scope.websites.length; i++) {
var website = $scope.websites[i];
website.selected = false;
}
// set new site to selected and push to scope
model.website.selected = true;
$scope.websites.push(model.website);
// clsoe overlay
$scope.createWebsiteOverlay.show = false;
$scope.createWebsiteOverlay = null;
},
close: function() {
$scope.createWebsiteOverlay.show = false;
$scope.createWebsiteOverlay = null;
}
};
};
$scope.clickWebsite = function(website) {
};
//ensure to unregister from all events!
$scope.$on('$destroy', function () {
for (var e in evts) {
@@ -108,7 +108,8 @@
@import "components/umb-empty-state.less";
@import "components/umb-property-editor.less";
@import "components/umb-iconpicker.less";
@import "components/umb-website-selector.less";
@import "components/umb-dots.less";
@import "components/buttons/umb-button.less";
@import "components/buttons/umb-button-group.less";
@@ -152,6 +152,30 @@
}
}
/* ---------- OVERLAY TREE ---------- */
.umb-overlay.umb-overlay-tree {
position: absolute;
width: 500px;
top: 0;
left: 100%;
bottom: 0;
border: none;
//box-shadow: 0 0 20px rgba(0,0,0,0.19), 0 0 6px rgba(0,0,0,0.23);
box-shadow: 3px 0 7px #dbdbdb;
display: inline-block;
}
.umb-overlay.umb-overlay-tree .umb-overlay-header {
flex-basis: 100px;
padding: 20px;
box-sizing: border-box;
}
.umb-overlay.umb-overlay-tree .umb-overlay-container {
padding: 20px;
}
/* ---------- OVERLAY ITEM DETAILS ---------- */
.umb-overlay__item-details {
position: absolute;
@@ -0,0 +1,26 @@
.umb-dots {
position: relative;
width: 5px;
height: 5px;
background-color: @gray;
border-radius: 50%;
}
.umb-dots:before,
.umb-dots:after {
display: block;
position: absolute;
content: '';
width: 5px;
height: 5px;
background-color: @gray;
border-radius: 50%;
}
.umb-dots:before {
transform: translateX(-8px);
}
.umb-dots:after {
transform: translateX(8px);
}
@@ -0,0 +1,109 @@
.umb-website-selector {
border-bottom: 1px solid #d9d9d9;
display: flex;
align-items: stretch;
height: 50px;
position: relative;
}
.umb-website-selector__create-website {
display: flex;
width: 100%;
justify-content: center;
align-items: center;
color: @blue;
}
.umb-website-selector__dropdown-toggle {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: 100%;
box-sizing: border-box;
padding-left: 20px;
padding-right: 20px;
font-weight: bold;
}
.umb-website-selector__dropdown-toggle:hover,
.umb-website-selector__dropdown-toggle:focus {
text-decoration: none;
}
.umb-website-selector__dropdown-toggle-content {
display: flex;
align-items: center;
}
.umb-website-selector__dropdown-toggle-name {
margin-right: 5px;
}
.umb-website-selector__dropdown {
position: absolute;
top: 50px;
left: 0;
right: 0;
z-index: 1;
background: #ffffff;
}
.umb-website-selector__websites {
margin-left: 0;
margin-bottom: 0;
list-style: none;
max-height: 300px;
overflow-y: auto;
}
.umb-website-selector__website-link {
display: flex;
align-items: center;
justify-content: space-between;
padding-top: 10px;
padding-right: 20px;
padding-bottom: 10px;
padding-left: 38px;
}
.umb-website-selector__website-link:hover,
.umb-website-selector__website-link.-selected {
background-color: @grayLighter;
text-decoration: none;
}
.umb-website-selector__dropdown-action {
display: flex;
justify-content: center;
align-items: center;
padding-top: 10px;
padding-bottom: 10px;
color: @blue;
font-size: 12px;
}
.umb-website-selector__dropdown-arrow {
width: 0;
height: 0;
margin-right: 10px;
}
.umb-website-selector__dropdown-arrow.-down {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid @gray;
}
.umb-website-selector__dropdown-arrow.-up {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-bottom: 4px solid @gray;
}
.umb-website-selector__dropdown-action:hover {
text-decoration: none;
background: @blue;
color: #ffffff;
}
@@ -16,12 +16,12 @@ label small, .guiDialogTiny {
font-size: 11px
}
label.control-label {
label.control-label, .control-label {
padding: 0 10px 0 0 !important;
font-weight: bold;
}
.umb-status-label{
color: @gray !important;
}
@@ -164,7 +164,7 @@ textarea {
// Identify controls by their labels
label {
display: block;
display: inline-block;
margin-bottom: 5px;
}
+1 -1
View File
@@ -139,7 +139,7 @@ h5.-black {
}
/* LABELS*/
.umb-control-group label.control-label {
.umb-control-group label.control-label, .umb-control-group .control-label {
text-align: left;
}
.umb-control-group label.control-label > div > label {
@@ -493,3 +493,66 @@ input.umb-panel-header-description {
.umb-editor-drawer-content__left-side {
margin-right: auto;
}
/* --- VARIATIONS --- */
.umb-variations-quick-switcher__toggle {
position: absolute;
top: 6px;
right: 10px;
font-size: 16px;
}
.umb-variations-quick-switcher_variations {
position: absolute;
top: 40px;
left: 0;
right: 0;
z-index: 20;
background-color: #ffffff;
max-height: 200px;
overflow-y: scroll;
}
.umb-variations-quick-switcher__variation {
display: flex;
flex-direction: row;
align-items: center;
padding: 10px 15px;
}
.umb-variations-quick-switcher__variation:last-child {
border-bottom: none;
}
.umb-variations-quick-switcher__variation:hover,
.umb-variations-quick-switcher__variation:focus {
background-color: @grayLighter;
text-decoration: none;
outline: none;
}
.umb-variations-quick-switcher__variation-published-state {
display: block;
width: 8px;
height: 8px;
border-radius: 50%;
background-color: @grayLight;
margin-right: 10px;
}
.umb-variations-quick-switcher__variation-published-state.-published {
background-color: @green;
}
.umb-variations-quick-switcher__variation-name {
font-size: 12px;
font-weight: bold;
}
.umb-variations-quick-switcher__variation-description {
font-size: 12px;
color: @gray;
margin-left: 10px;
}
@@ -81,6 +81,9 @@ div.umb-codeeditor .umb-btn-toolbar {
font-size:16px !important;
}
/* pre-value editor */
.rte-editor-preval .control-group .controls > div > label .mce-ico { line-height: 20px; }
//
// Color picker
@@ -19,7 +19,8 @@ ul.sections li {
}
ul.sections li [class^="icon-"]:before,
ul.sections li [class*=" icon-"]:before{
ul.sections li [class*=" icon-"]:before,
ul.sections li img.icon-section {
font-size: 30px;
margin: 1px 0 0 0;
opacity: 0.4;
@@ -29,7 +30,8 @@ ul.sections li [class*=" icon-"]:before{
}
ul.sections:hover li [class^="icon-"]:before,
ul.sections:hover li [class*=" icon-"]:before {
ul.sections:hover li [class*=" icon-"]:before,
ul.sections:hover li img.icon-section {
opacity: 1
}
@@ -0,0 +1,15 @@
(function() {
"use strict";
function CreateWebsiteOverlay($scope) {
var vm = this;
$scope.model.website = {};
}
angular.module("umbraco").controller("Umbraco.Overlay.CreateWebsiteOverlay", CreateWebsiteOverlay);
})();
@@ -0,0 +1,26 @@
<div ng-controller="Umbraco.Overlay.CreateWebsiteOverlay as vm">
<div class="umb-control-group control-group">
<label>Website name</label>
<input type="text" ng-model="model.website.name" />
</div>
<div class="umb-control-group control-group">
<label>Website language</label>
<select ng-model="model.website.language">
<option value="aa-DJ">Afar (Djinouti) [aa-DJ]</option>
<option value="aa-ER">Afar (Eritrea) [aa-ER]</option>
<option value="aa-ET">Afar (Ethiopia) [aa-ET]</option>
</select>
</div>
<div class="umb-control-group control-group">
<label>Create blank</label>
<input type="checkbox" ng-model="model.website.blank" />
<label>Copy all</label>
<input type="checkbox" ng-model="model.website.copyAll" />
<label>Copy selected</label>
<input type="checkbox" ng-model="model.website.copySelected" />
</div>
</div>
@@ -65,8 +65,14 @@
</div>
<umb-website-selector
websites="websites"
on-create-website="createWebsite"
on-click-website="clickWebsite">
</umb-website-selector>
<!-- the tree -->
<div id="tree" class="umb-modalcolumn-body" ng-if="authenticated">
<div id="tree" ng-if="authenticated && websites.length > 0">
<umb-tree
cachekey="_"
eventhandler="treeEventHandler"
@@ -87,6 +93,12 @@
</umb-context-menu>
</div>
<umb-overlay
ng-if="createWebsiteOverlay.show"
model="createWebsiteOverlay"
view="createWebsiteOverlay.view"
position="tree">
</umb-overlay>
<!-- Tree dialogs -->
<div id="dialog" class='umb-modalcolumn fill shadow'
@@ -31,10 +31,14 @@
<div class="umb-panel-header-name" ng-if="nameLocked">{{ name }}</div>
<umb-generate-alias class="umb-panel-header-alias"
ng-if="!hideAlias"
alias="$parent.alias"
alias-from="$parent.name"
<a class="umb-variations-quick-switcher__toggle" href="" ng-click="toggleVariationsQuickSwitch()">
<i class="icon-documents"></i>
</a>
<umb-generate-alias class="umb-panel-header-alias"
ng-if="!hideAlias"
alias="$parent.alias"
alias-from="$parent.name"
enable-lock="true"
server-validation-field="Alias"></umb-generate-alias>
@@ -1,19 +1,23 @@
<div>
<ul class="umb-tree">
<li class="root">
<ul class="umb-search-group">
<li ng-repeat="result in results">
<div style="padding-left: 20px">
<a class="umb-search-group-item-link" ng-class="{first:$first}" ng-click="selectResultCallback($event, result)">
<div class="umb-search-group-item-name">
<i class="icon umb-tree-icon sprTree {{result.icon}}"></i>
{{result.name}}
</div>
<small class="search-subtitle" ng-if="result.subTitle">{{result.subTitle}}</small>
</a>
</div>
</li>
</ul>
</li>
</ul>
</div>
<ul class="umb-tree">
<li class="root">
<ul class="umb-search-group">
<li ng-repeat="result in results">
<div style="padding-left: 20px" ng-class="{'umb-tree-node-checked' : result.selected}">
<a class="umb-search-group-item-link" ng-class="{first:$first}" ng-click="selectResultCallback($event, result)">
<div class="umb-search-group-item-name">
<i class="icon umb-tree-icon sprTree {{result.icon}}"></i>
{{result.name}}
</div>
<small class="search-subtitle" ng-if="result.subTitle">{{result.subTitle}}</small>
</a>
</div>
</li>
</ul>
</li>
</ul>
@@ -0,0 +1,32 @@
<div class="umb-website-selector" on-outside-click="closeWebsiteDropDown()">
<a class="umb-website-selector__create-website" ng-if="websites.length === 0" href="" ng-click="createWebsite()">
Create website
</span>
<a class="umb-website-selector__dropdown-toggle" ng-if="websites.length > 0" href="" ng-click="toggleWebsiteDropDown()">
<div class="umb-website-selector__dropdown-toggle-content">
<span ng-if="showWebsiteDropdown" class="umb-website-selector__dropdown-arrow -up"></span>
<span ng-if="!showWebsiteDropdown" class="umb-website-selector__dropdown-arrow -down"></span>
<span class="umb-website-selector__dropdown-toggle-name">{{ selectedWebsite.name }}</span>
<small>({{ selectedWebsite.language }})</small>
</div>
<div class="umb-dots"></div>
</a>
<div class="umb-website-selector__dropdown shadow-depth-3" ng-show="showWebsiteDropdown">
<ul class="umb-website-selector__websites">
<li class="umb-website-selector__website" ng-repeat="website in websites | filter:{ selected: '!'+true }">
<a class="umb-website-selector__website-link" ng-click="clickWebsite(website)" href="" ng-class="{'-selecetd': website.selected}">
<div>
<span class="umb-website-selector__website-name">{{ website.name}}</span>
<small>({{ website.language }})</small>
</div>
<div class="umb-dots"></div>
</a>
</li>
</ul>
<a class="umb-website-selector__dropdown-action" ng-click="createWebsite()" href="">Create website</a>
</div>
</div>
@@ -0,0 +1,63 @@
angular.module("umbraco")
.controller("Umbraco.Editors.DocumentTypes.CopyController",
function ($scope, contentTypeResource, treeService, navigationService, notificationsService, appState, eventsService) {
var dialogOptions = $scope.dialogOptions;
$scope.dialogTreeEventHandler = $({});
function nodeSelectHandler(ev, args) {
args.event.preventDefault();
args.event.stopPropagation();
if ($scope.target) {
//un-select if there's a current one selected
$scope.target.selected = false;
}
$scope.target = args.node;
$scope.target.selected = true;
}
$scope.copy = function () {
$scope.busy = true;
$scope.error = false;
contentTypeResource.copy({ parentId: $scope.target.id, id: dialogOptions.currentNode.id })
.then(function (path) {
$scope.error = false;
$scope.success = true;
$scope.busy = false;
//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: "documentTypes", 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: "documentTypes", path: activeNodePath, forceReload: false, activate: true });
}
});
}, function (err) {
$scope.success = false;
$scope.error = err;
$scope.busy = false;
//show any notifications
if (angular.isArray(err.data.notifications)) {
for (var i = 0; i < err.data.notifications.length; i++) {
notificationsService.showNotification(err.data.notifications[i]);
}
}
});
};
$scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
$scope.$on('$destroy', function () {
$scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
});
});
@@ -0,0 +1,51 @@
<div class="umb-dialog umb-pane" ng-controller="Umbraco.Editors.DocumentTypes.CopyController">
<div class="umb-dialog-body">
<div class="umb-pane">
<p class="abstract" ng-hide="success">
<localize key="contentTypeEditor_folderToCopy">Select the folder to copy</localize> <strong>{{currentNode.name}}</strong>&nbsp;<localize key="contentTypeEditor_structureBelow">to in the tree structure below</localize>
</p>
<div class="umb-loader-wrapper" ng-show="busy">
<div class="umb-loader"></div>
</div>
<div ng-show="error">
<h5 class="text-error">{{error.errorMsg}}</h5>
<p class="text-error">{{error.data.message}}</p>
</div>
<div ng-show="success">
<h5 class="text-success">
<strong>{{currentNode.name}}</strong> <localize key="contentTypeEditor_copiedUnderneath">was copied underneath</localize>&nbsp;<strong>{{target.name}}</strong></h5>
<button class="btn btn-primary" ng-click="nav.hideDialog()">Ok</button>
</div>
<div ng-hide="success">
<div>
<umb-tree section="settings"
treealias="documentTypes"
customtreeparams="foldersonly=1"
hideheader="false"
hideoptions="true"
isdialog="true"
eventhandler="dialogTreeEventHandler"
enablecheckboxes="true">
</umb-tree>
</div>
</div>
</div>
</div>
<div class="umb-dialog-footer btn-toolbar umb-btn-toolbar" ng-hide="success">
<a class="btn btn-link" ng-click="nav.hideDialog()" ng-if="!busy">
<localize key="general_cancel">Cancel</localize>
</a>
<button class="btn btn-primary" ng-click="copy()" ng-disabled="busy">
<localize key="actions_copy">Copy</localize>
</button>
</div>
</div>
@@ -0,0 +1,63 @@
angular.module("umbraco")
.controller("Umbraco.Editors.MediaTypes.CopyController",
function ($scope, mediaTypeResource, treeService, navigationService, notificationsService, appState, eventsService) {
var dialogOptions = $scope.dialogOptions;
$scope.dialogTreeEventHandler = $({});
function nodeSelectHandler(ev, args) {
args.event.preventDefault();
args.event.stopPropagation();
if ($scope.target) {
//un-select if there's a current one selected
$scope.target.selected = false;
}
$scope.target = args.node;
$scope.target.selected = true;
}
$scope.copy = function () {
$scope.busy = true;
$scope.error = false;
mediaTypeResource.copy({ parentId: $scope.target.id, id: dialogOptions.currentNode.id })
.then(function (path) {
$scope.error = false;
$scope.success = true;
$scope.busy = false;
//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: "mediaTypes", 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: "mediaTypes", path: activeNodePath, forceReload: false, activate: true });
}
});
}, function (err) {
$scope.success = false;
$scope.error = err;
$scope.busy = false;
//show any notifications
if (angular.isArray(err.data.notifications)) {
for (var i = 0; i < err.data.notifications.length; i++) {
notificationsService.showNotification(err.data.notifications[i]);
}
}
});
};
$scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
$scope.$on('$destroy', function () {
$scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
});
});
@@ -0,0 +1,51 @@
<div class="umb-dialog umb-pane" ng-controller="Umbraco.Editors.MediaTypes.CopyController">
<div class="umb-dialog-body">
<div class="umb-pane">
<p class="abstract" ng-hide="success">
<localize key="contentTypeEditor_folderToCopy">Select the folder to copy</localize> <strong>{{currentNode.name}}</strong>&nbsp;<localize key="contentTypeEditor_structureBelow">to in the tree structure below</localize>
</p>
<div class="umb-loader-wrapper" ng-show="busy">
<div class="umb-loader"></div>
</div>
<div ng-show="error">
<h5 class="text-error">{{error.errorMsg}}</h5>
<p class="text-error">{{error.data.message}}</p>
</div>
<div ng-show="success">
<h5 class="text-success">
<strong>{{currentNode.name}}</strong> <localize key="contentTypeEditor_copiedUnderneath">was copied underneath</localize>&nbsp;<strong>{{target.name}}</strong></h5>
<button class="btn btn-primary" ng-click="nav.hideDialog()">Ok</button>
</div>
<div ng-hide="success">
<div>
<umb-tree section="settings"
treealias="mediaTypes"
customtreeparams="foldersonly=1"
hideheader="false"
hideoptions="true"
isdialog="true"
eventhandler="dialogTreeEventHandler"
enablecheckboxes="true">
</umb-tree>
</div>
</div>
</div>
</div>
<div class="umb-dialog-footer btn-toolbar umb-btn-toolbar" ng-hide="success">
<a class="btn btn-link" ng-click="nav.hideDialog()" ng-if="!busy">
<localize key="general_cancel">Cancel</localize>
</a>
<button class="btn btn-primary" ng-click="copy()" ng-disabled="busy">
<localize key="actions_copy">Copy</localize>
</button>
</div>
</div>
@@ -4,8 +4,8 @@ function booleanEditorController($scope, $rootScope, assetsService) {
$scope.renderModel = {
value: false
};
if($scope.model.config.default.toString() === "1" && $scope.model && !$scope.model.value) {
if ($scope.model.config && $scope.model.config.default && $scope.model.config.default.toString() === "1" && $scope.model && !$scope.model.value) {
$scope.renderModel.value = true;
}
@@ -1,4 +1,4 @@
function listViewController($rootScope, $scope, $routeParams, $injector, $cookieStore, notificationsService, iconHelper, dialogService, editorState, localizationService, $location, appState, $timeout, $q, mediaResource, listViewHelper) {
function listViewController($rootScope, $scope, $routeParams, $injector, $cookieStore, notificationsService, iconHelper, dialogService, editorState, localizationService, $location, appState, $timeout, $q, mediaResource, listViewHelper, userService) {
//this is a quick check to see if we're in create mode, if so just exit - we cannot show children for content
// that isn't created yet, if we continue this will use the parent id in the route params which isn't what
@@ -58,6 +58,63 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie
totalPages: 0,
items: []
};
//when this is null, we don't check permissions
$scope.buttonPermissions = null;
//When we are dealing with 'content', we need to deal with permissions on child nodes.
// Currently there is no real good way to
if ($scope.entityType === "content") {
var idsWithPermissions = null;
$scope.buttonPermissions = {
canCopy: true,
canCreate: true,
canDelete: true,
canMove: true,
canPublish: true,
canUnpublish: true
};
$scope.$watch(function() {
return $scope.selection.length;
}, function(newVal, oldVal) {
if ((idsWithPermissions == null && newVal > 0) || (idsWithPermissions != null)) {
//get all of the selected ids
var ids = _.map($scope.selection, function(i) {
return i.id.toString();
});
//remove the dictionary items that don't have matching ids
var filtered = {};
_.each(idsWithPermissions, function (value, key, list) {
if (_.contains(ids, key)) {
filtered[key] = value;
}
});
idsWithPermissions = filtered;
//find all ids that we haven't looked up permissions for
var existingIds = _.keys(idsWithPermissions);
var missingLookup = _.map(_.difference(ids, existingIds), function (i) {
return Number(i);
});
if (missingLookup.length > 0) {
contentResource.getPermissions(missingLookup).then(function(p) {
$scope.buttonPermissions = listViewHelper.getButtonPermissions(p, idsWithPermissions);
});
}
else {
$scope.buttonPermissions = listViewHelper.getButtonPermissions({}, idsWithPermissions);
}
}
});
}
$scope.options = {
displayAtTabNumber: $scope.model.config.displayAtTabNumber ? $scope.model.config.displayAtTabNumber : 1,
@@ -166,9 +223,8 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie
getListResultsCallback(id, $scope.options).then(function(data) {
$scope.actionInProgress = false;
$scope.listViewResultSet = data;
//update all values for display
if ($scope.listViewResultSet.items) {
_.each($scope.listViewResultSet.items, function(e, index) {
@@ -10,7 +10,7 @@
<umb-editor-sub-header-content-left>
<umb-editor-sub-header-section ng-if="listViewAllowedTypes && listViewAllowedTypes.length > 0 && !isAnythingSelected()">
<umb-editor-sub-header-section ng-if="(listViewAllowedTypes && listViewAllowedTypes.length > 0 && !isAnythingSelected()) && currentNodePermissions.canCreate">
<div class="btn-group">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
<localize key="actions_create">Create</localize>
@@ -82,7 +82,7 @@
<umb-editor-sub-header-section ng-if="isAnythingSelected()">
<umb-button
ng-if="options.allowBulkPublish"
ng-if="options.allowBulkPublish && (buttonPermissions == null || buttonPermissions.canPublish)"
type="button"
button-style="link"
label="Publish"
@@ -93,7 +93,7 @@
</umb-button>
<umb-button
ng-if="options.allowBulkUnpublish"
ng-if="options.allowBulkUnpublish && (buttonPermissions == null || buttonPermissions.canUnpublish)"
type="button"
button-style="link"
label="Unpublish"
@@ -104,7 +104,7 @@
</umb-button>
<umb-button
ng-if="options.allowBulkCopy"
ng-if="options.allowBulkCopy && (buttonPermissions == null || buttonPermissions.canCopy)"
type="button"
button-style="link"
label="Copy"
@@ -115,7 +115,7 @@
</umb-button>
<umb-button
ng-if="options.allowBulkMove"
ng-if="options.allowBulkMove && (buttonPermissions == null || buttonPermissions.canMove)"
type="button"
button-style="link"
label="Move"
@@ -126,7 +126,7 @@
</umb-button>
<umb-button
ng-if="options.allowBulkDelete"
ng-if="options.allowBulkDelete && (buttonPermissions == null || buttonPermissions.canDelete)"
type="button"
button-style="link"
label="Delete"
@@ -261,7 +261,7 @@ angular.module("umbraco")
};
});
//Create the embedded plugin
tinyMceService.createInsertEmbeddedMedia(editor, $scope, function() {
@@ -1,5 +1,5 @@
angular.module("umbraco").controller("Umbraco.PrevalueEditors.RteController",
function ($scope, $timeout, $log, tinyMceService, stylesheetResource) {
function ($scope, $timeout, $log, tinyMceService, stylesheetResource, assetsService) {
var cfg = tinyMceService.defaultPrevalues();
if($scope.model.value){
@@ -23,6 +23,14 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.RteController",
tinyMceService.configuration().then(function(config){
$scope.tinyMceConfig = config;
// extend commands with properties for font-icon and if it is a custom command
$scope.tinyMceConfig.commands = _.map($scope.tinyMceConfig.commands, function (obj) {
var icon = getFontIcon(obj.frontEndCommand);
return angular.extend(obj, {
fontIcon: icon.name,
isCustom: icon.isCustom
});
});
});
stylesheetResource.getAll().then(function(stylesheets){
@@ -57,6 +65,43 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.RteController",
$scope.model.value.stylesheets.splice(index, 1);
}
};
// map properties for specific commands
function getFontIcon(alias) {
var icon = { name: alias, isCustom: false };
switch (alias) {
case "codemirror":
icon.name = "code";
icon.isCustom = false;
break;
case "styleselect":
icon.name = "icon-list";
icon.isCustom = true;
break;
case "umbembeddialog":
icon.name = "icon-tv";
icon.isCustom = true;
break;
case "umbmediapicker":
icon.name = "icon-picture";
icon.isCustom = true;
break;
case "umbmacro":
icon.name = "icon-settings-alt";
icon.isCustom = true;
break;
case "umbmacro":
icon.name = "icon-settings-alt";
icon.isCustom = true;
break;
default:
icon.name = alias;
icon.isCustom = false;
}
return icon;
}
var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
@@ -65,9 +110,11 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.RteController",
});
//when the scope is destroyed we need to unsubscribe
// when the scope is destroyed we need to unsubscribe
$scope.$on('$destroy', function () {
unsubscribe();
});
});
// load TinyMCE skin which contains css for font-icons
assetsService.loadCss("lib/tinymce/skins/umbraco/skin.min.css");
});
@@ -1,4 +1,4 @@
<div ng-controller="Umbraco.PrevalueEditors.RteController">
<div ng-controller="Umbraco.PrevalueEditors.RteController" class="rte-editor-preval">
<umb-control-group label="Toolbar" hide-label="false">
<div ng-repeat="cmd in tinyMceConfig.commands">
@@ -8,9 +8,10 @@
ng-model="cmd.selected"
ng-change="selectCommand(cmd)" />
<img ng-src="{{cmd.icon}}" />
<!--<img ng-src="{{cmd.icon}}" />-->
<i class="mce-ico" ng-class="(cmd.isCustom ? ' mce-i-custom ' : ' mce-i-') + cmd.fontIcon"></i>
{{cmd.frontEndCommand}}
{{cmd.name}}
</label>
</div>
</umb-control-group>
@@ -27,12 +28,12 @@
</div>
</umb-control-group>
<umb-control-group label="Dimensions" description="Width x Height">
<umb-control-group label="Dimensions" description="Width x Height">
<div class="vertical-align-items">
<input type="number" min="0" ng-model="model.value.dimensions.width" class="umb-editor-tiny" placeholder="Width" /> &times;
<input type="number" min="0" ng-model="model.value.dimensions.width" class="umb-editor-tiny" placeholder="Width" /> &times;
<input type="number" min="0" ng-model="model.value.dimensions.height" class="umb-editor-tiny" placeholder="Height" /> Pixels
</div>
</umb-control-group>
</div>
</umb-control-group>
<umb-control-group label="Maximum size for inserted images" description="0 to disable resizing">
<div class="vertical-align-items">
@@ -5,7 +5,8 @@ var app = angular.module('umbraco', [
'umbraco.services',
'umbraco.mocks',
'umbraco.security',
'ngCookies'
'ngCookies',
'LocalStorageModule'
]);
/* For Angular 1.2: we need to load in Routing separately
@@ -26,7 +26,7 @@ module.exports = function(karma) {
'lib/../build/belle/lib/underscore/underscore-min.js',
'lib/umbraco/Extensions.js',
'lib/../build/belle/lib/rgrove-lazyload/lazyload.js',
'lib/../build/belle/lib/angular-local-storage/angular-local-storage.min.js',
'test/config/app.unit.js',
'src/common/mocks/umbraco.servervariables.js',
@@ -0,0 +1,48 @@
describe('list view helper tests', function () {
var $scope, listViewHelper;
beforeEach(module('LocalStorageModule'));
beforeEach(module('umbraco.services'));
beforeEach(inject(function ($injector) {
$scope = $injector.get('$rootScope');
listViewHelper = $injector.get('listViewHelper');
}));
describe('getButtonPermissions', function () {
it('should update the currentIdsWithPermissions dictionary', function () {
var currentIdsWithPermissions = {};
var result = listViewHelper.getButtonPermissions({ "1234": ["A", "B", "C"] }, currentIdsWithPermissions);
expect(_.has(currentIdsWithPermissions, "1234")).toBe(true);
expect(_.keys(currentIdsWithPermissions).length).toEqual(1);
});
it('returns button permissions', function () {
var currentIdsWithPermissions = {};
var result1 = listViewHelper.getButtonPermissions({ "1234": ["O", "C", "D", "M", "U"] }, currentIdsWithPermissions);
expect(result1["canCopy"]).toBe(true);
expect(result1["canCreate"]).toBe(true);
expect(result1["canDelete"]).toBe(true);
expect(result1["canMove"]).toBe(true);
expect(result1["canPublish"]).toBe(true);
expect(result1["canUnpublish"]).toBe(true);
var result2 = listViewHelper.getButtonPermissions({ "1234": ["A", "B"] }, currentIdsWithPermissions);
expect(result2["canCopy"]).toBe(false);
expect(result2["canCreate"]).toBe(false);
expect(result2["canDelete"]).toBe(false);
expect(result2["canMove"]).toBe(false);
expect(result2["canPublish"]).toBe(false);
expect(result2["canUnpublish"]).toBe(false);
});
});
});
@@ -7,9 +7,18 @@
'Failed to generate.')
.then(function (result) {
$scope.generating = false;
$scope.dashboard = result;
});
};
$scope.reload = function () {
$scope.ready = false;
modelsBuilderResource.getDashboard().then(function (result) {
$scope.dashboard = result;
$scope.ready = true;
});
};
function init() {
modelsBuilderResource.getDashboard().then(function(result) {
$scope.dashboard = result;
@@ -1,5 +1,9 @@
<div id="modelsBuilder" ng-controller="Umbraco.Dashboard.ModelsBuilderController">
<div ng-show="ready" style="float: right;">
<button type="button" class="btn" ng-click="reload()"><span>Reload</span></button>
</div>
<h3>Models Builder</h3>
<div ng-show="!ready">
@@ -10,7 +14,8 @@
<div ng-bind-html-unsafe="dashboard.text"></div>
<div ng-if="dashboard.outOfDateModels">
<p>Models are <strong>out-of-date</strong>.</p>
<p>Models are <strong>out-of-date</strong>.
</p>
</div>
<div ng-if="dashboard.canGenerate">
@@ -26,6 +31,11 @@
<div class="umb-loader"></div>
</div>
</div>
<div ng-if="dashboard.lastError" style="margin-top: 32px;" ng-show="!generating">
<span style="color: red; font-weight: bold;">Last generation failed with the following error:</span>
<pre style="width: 80%; white-space: pre-line; background: #f8f8f8; padding: 4px; font-size: small;">{{dashboard.lastError}}</pre>
</div>
</div>
</div>
+2 -2
View File
@@ -348,8 +348,8 @@
<Name>umbraco.providers</Name>
</ProjectReference>
<Reference Include="System.Xml.Linq" />
<Reference Include="Umbraco.ModelsBuilder, Version=3.0.0.90, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Umbraco.ModelsBuilder.3.0.0\lib\Umbraco.ModelsBuilder.dll</HintPath>
<Reference Include="Umbraco.ModelsBuilder, Version=3.0.1.92, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Umbraco.ModelsBuilder.3.0.1\lib\Umbraco.ModelsBuilder.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="UrlRewritingNet.UrlRewriter, Version=2.0.7.0, Culture=neutral, processorArchitecture=MSIL">
@@ -2,221 +2,246 @@
<!-- Any changes to this file requires the application (umbraco) to restart -->
<!-- This can be done by touching the web.config or recycling the application pool -->
<tinymceConfig>
<commands>
<command>
<umbracoAlias>code</umbracoAlias>
<icon>images/editor/code.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="code">code</tinyMceCommand>
<priority>1</priority>
</command>
<command>
<commands>
<command>
<umbracoAlias>code</umbracoAlias>
<name>Code</name>
<icon>images/editor/code.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="code">code</tinyMceCommand>
<priority>1</priority>
</command>
<command>
<umbracoAlias>codemirror</umbracoAlias>
<name>Code mirror</name>
<icon>images/editor/code.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="codemirror">codemirror</tinyMceCommand>
<priority>1</priority>
</command>
<command>
<umbracoAlias>removeformat</umbracoAlias>
<icon>images/editor/removeformat.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="removeformat">removeformat</tinyMceCommand>
<priority>2</priority>
</command>
<command>
<umbracoAlias>Undo</umbracoAlias>
<icon>images/editor/undo.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="undo">undo</tinyMceCommand>
<priority>11</priority>
</command>
<command>
<umbracoAlias>Redo</umbracoAlias>
<icon>images/editor/redo.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="redo">redo</tinyMceCommand>
<priority>12</priority>
</command>
<command>
<umbracoAlias>Cut</umbracoAlias>
<icon>images/editor/cut.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="cut">cut</tinyMceCommand>
<priority>13</priority>
</command>
<command>
<umbracoAlias>Copy</umbracoAlias>
<icon>images/editor/copy.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="copy">copy</tinyMceCommand>
<priority>14</priority>
</command>
<command>
<umbracoAlias>styleselect</umbracoAlias>
<icon>images/editor/showStyles.png</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="styleselect">styleselect</tinyMceCommand>
<priority>20</priority>
</command>
<command>
<umbracoAlias>bold</umbracoAlias>
<icon>images/editor/bold.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="bold">bold</tinyMceCommand>
<priority>21</priority>
</command>
<command>
<umbracoAlias>italic</umbracoAlias>
<icon>images/editor/italic.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="italic">italic</tinyMceCommand>
<priority>22</priority>
</command>
<command>
<umbracoAlias>Underline</umbracoAlias>
<icon>images/editor/underline.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="underline">underline</tinyMceCommand>
<priority>23</priority>
</command>
<command>
<umbracoAlias>Strikethrough</umbracoAlias>
<icon>images/editor/strikethrough.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="strikethrough">strikethrough</tinyMceCommand>
<priority>24</priority>
</command>
<command>
<umbracoAlias>JustifyLeft</umbracoAlias>
<icon>images/editor/justifyleft.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="alignleft">justifyleft</tinyMceCommand>
<priority>31</priority>
</command>
<command>
<umbracoAlias>JustifyCenter</umbracoAlias>
<icon>images/editor/justifycenter.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="aligncenter">justifycenter</tinyMceCommand>
<priority>32</priority>
</command>
<command>
<umbracoAlias>JustifyRight</umbracoAlias>
<icon>images/editor/justifyright.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="alignright">justifyright</tinyMceCommand>
<priority>33</priority>
</command>
<command>
<umbracoAlias>JustifyFull</umbracoAlias>
<icon>images/editor/justifyfull.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="alignjustify">alignjustify</tinyMceCommand>
<priority>34</priority>
</command>
<command>
<umbracoAlias>bullist</umbracoAlias>
<icon>images/editor/bullist.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="bullist">bullist</tinyMceCommand>
<priority>41</priority>
</command>
<command>
<umbracoAlias>numlist</umbracoAlias>
<icon>images/editor/numlist.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="numlist">numlist</tinyMceCommand>
<priority>42</priority>
</command>
<command>
<umbracoAlias>Outdent</umbracoAlias>
<icon>images/editor/outdent.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="outdent">outdent</tinyMceCommand>
<priority>43</priority>
</command>
<command>
<umbracoAlias>Indent</umbracoAlias>
<icon>images/editor/indent.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="indent">indent</tinyMceCommand>
<priority>44</priority>
</command>
<command>
<umbracoAlias>mceLink</umbracoAlias>
<icon>images/editor/link.gif</icon>
<tinyMceCommand value="" userInterface="true" frontendCommand="link">link</tinyMceCommand>
<priority>51</priority>
</command>
<command>
<umbracoAlias>unlink</umbracoAlias>
<icon>images/editor/unLink.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="unlink">unlink</tinyMceCommand>
<priority>52</priority>
</command>
<command>
<umbracoAlias>mceInsertAnchor</umbracoAlias>
<icon>images/editor/anchor.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="anchor">anchor</tinyMceCommand>
<priority>53</priority>
</command>
<command>
<umbracoAlias>mceImage</umbracoAlias>
<icon>images/editor/image.gif</icon>
<tinyMceCommand value="" userInterface="true" frontendCommand="umbmediapicker">image</tinyMceCommand>
<priority>61</priority>
</command>
<command>
<umbracoAlias>umbracomacro</umbracoAlias>
<icon>images/editor/insMacro.gif</icon>
<tinyMceCommand value="" userInterface="true" frontendCommand="umbmacro">umbracomacro</tinyMceCommand>
<priority>62</priority>
</command>
<command>
<umbracoAlias>mceInsertTable</umbracoAlias>
<icon>images/editor/table.gif</icon>
<tinyMceCommand value="" userInterface="true" frontendCommand="table">table</tinyMceCommand>
<priority>63</priority>
</command>
<command>
<umbracoAlias>umbracoembed</umbracoAlias>
<icon>images/editor/media.gif</icon>
<tinyMceCommand value="" userInterface="true" frontendCommand="umbembeddialog">umbracoembed</tinyMceCommand>
<priority>66</priority>
</command>
<command>
<umbracoAlias>inserthorizontalrule</umbracoAlias>
<icon>images/editor/hr.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="hr">hr</tinyMceCommand>
<priority>71</priority>
</command>
<command>
<umbracoAlias>subscript</umbracoAlias>
<icon>images/editor/sub.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="subscript">subscript</tinyMceCommand>
<priority>72</priority>
</command>
<command>
<umbracoAlias>superscript</umbracoAlias>
<icon>images/editor/sup.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="superscript">superscript</tinyMceCommand>
<priority>73</priority>
</command>
<command>
<umbracoAlias>mceCharMap</umbracoAlias>
<icon>images/editor/charmap.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="charmap">charmap</tinyMceCommand>
<priority>74</priority>
</command>
</commands>
<plugins>
<umbracoAlias>removeformat</umbracoAlias>
<name>Remove format</name>
<icon>images/editor/removeformat.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="removeformat">removeformat</tinyMceCommand>
<priority>2</priority>
</command>
<command>
<umbracoAlias>undo</umbracoAlias>
<name>Undo</name>
<name>Remove Format</name>
<icon>images/editor/undo.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="undo">undo</tinyMceCommand>
<priority>11</priority>
</command>
<command>
<umbracoAlias>redo</umbracoAlias>
<name>Redo</name>
<icon>images/editor/redo.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="redo">redo</tinyMceCommand>
<priority>12</priority>
</command>
<command>
<umbracoAlias>cut</umbracoAlias>
<name>Cut</name>
<icon>images/editor/cut.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="cut">cut</tinyMceCommand>
<priority>13</priority>
</command>
<command>
<umbracoAlias>copy</umbracoAlias>
<name>Copy</name>
<icon>images/editor/copy.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="copy">copy</tinyMceCommand>
<priority>14</priority>
</command>
<command>
<umbracoAlias>paste</umbracoAlias>
<name>Paste</name>
<icon>images/editor/paste.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="paste">paste</tinyMceCommand>
<priority>15</priority>
</command>
<command>
<umbracoAlias>styleselect</umbracoAlias>
<name>Style select</name>
<icon>images/editor/showStyles.png</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="styleselect">styleselect</tinyMceCommand>
<priority>20</priority>
</command>
<command>
<umbracoAlias>bold</umbracoAlias>
<name>Bold</name>
<icon>images/editor/bold.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="bold">bold</tinyMceCommand>
<priority>21</priority>
</command>
<command>
<umbracoAlias>italic</umbracoAlias>
<name>Italic</name>
<icon>images/editor/italic.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="italic">italic</tinyMceCommand>
<priority>22</priority>
</command>
<command>
<umbracoAlias>underline</umbracoAlias>
<name>Underline</name>
<icon>images/editor/underline.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="underline">underline</tinyMceCommand>
<priority>23</priority>
</command>
<command>
<umbracoAlias>strikethrough</umbracoAlias>
<name>Strikethrough</name>
<icon>images/editor/strikethrough.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="strikethrough">strikethrough</tinyMceCommand>
<priority>24</priority>
</command>
<command>
<umbracoAlias>justifyleft</umbracoAlias>
<name>Justify left</name>
<icon>images/editor/justifyleft.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="alignleft">justifyleft</tinyMceCommand>
<priority>31</priority>
</command>
<command>
<umbracoAlias>justifycenter</umbracoAlias>
<name>Justify center</name>
<icon>images/editor/justifycenter.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="aligncenter">justifycenter</tinyMceCommand>
<priority>32</priority>
</command>
<command>
<umbracoAlias>justifyright</umbracoAlias>
<name>Justify right</name>
<icon>images/editor/justifyright.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="alignright">justifyright</tinyMceCommand>
<priority>33</priority>
</command>
<command>
<umbracoAlias>justifyfull</umbracoAlias>
<name>Justify full</name>
<icon>images/editor/justifyfull.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="alignjustify">alignjustify</tinyMceCommand>
<priority>34</priority>
</command>
<command>
<umbracoAlias>bullist</umbracoAlias>
<name>Bullet list</name>
<icon>images/editor/bullist.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="bullist">bullist</tinyMceCommand>
<priority>41</priority>
</command>
<command>
<umbracoAlias>numlist</umbracoAlias>
<name>Numbered list</name>
<icon>images/editor/numlist.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="numlist">numlist</tinyMceCommand>
<priority>42</priority>
</command>
<command>
<umbracoAlias>outdent</umbracoAlias>
<name>Decrease indent</name>
<icon>images/editor/outdent.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="outdent">outdent</tinyMceCommand>
<priority>43</priority>
</command>
<command>
<umbracoAlias>indent</umbracoAlias>
<name>Increase indent</name>
<icon>images/editor/indent.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="indent">indent</tinyMceCommand>
<priority>44</priority>
</command>
<command>
<umbracoAlias>mceLink</umbracoAlias>
<name>Insert/edit link</name>
<icon>images/editor/link.gif</icon>
<tinyMceCommand value="" userInterface="true" frontendCommand="link">link</tinyMceCommand>
<priority>51</priority>
</command>
<command>
<umbracoAlias>unlink</umbracoAlias>
<name>Remove link</name>
<icon>images/editor/unLink.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="unlink">unlink</tinyMceCommand>
<priority>52</priority>
</command>
<command>
<umbracoAlias>mceInsertAnchor</umbracoAlias>
<name>Anchor</name>
<icon>images/editor/anchor.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="anchor">anchor</tinyMceCommand>
<priority>53</priority>
</command>
<command>
<umbracoAlias>mceImage</umbracoAlias>
<name>Image</name>
<icon>images/editor/image.gif</icon>
<tinyMceCommand value="" userInterface="true" frontendCommand="umbmediapicker">image</tinyMceCommand>
<priority>61</priority>
</command>
<command>
<umbracoAlias>umbracomacro</umbracoAlias>
<name>Macro</name>
<icon>images/editor/insMacro.gif</icon>
<tinyMceCommand value="" userInterface="true" frontendCommand="umbmacro">umbracomacro</tinyMceCommand>
<priority>62</priority>
</command>
<command>
<umbracoAlias>mceInsertTable</umbracoAlias>
<name>Table</name>
<icon>images/editor/table.gif</icon>
<tinyMceCommand value="" userInterface="true" frontendCommand="table">table</tinyMceCommand>
<priority>63</priority>
</command>
<command>
<umbracoAlias>umbracoembed</umbracoAlias>
<name>Embed</name>
<icon>images/editor/media.gif</icon>
<tinyMceCommand value="" userInterface="true" frontendCommand="umbembeddialog">umbracoembed</tinyMceCommand>
<priority>66</priority>
</command>
<command>
<umbracoAlias>inserthorizontalrule</umbracoAlias>
<name>Horizontal rule</name>
<icon>images/editor/hr.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="hr">hr</tinyMceCommand>
<priority>71</priority>
</command>
<command>
<umbracoAlias>subscript</umbracoAlias>
<name>Subscript</name>
<icon>images/editor/sub.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="subscript">subscript</tinyMceCommand>
<priority>72</priority>
</command>
<command>
<umbracoAlias>superscript</umbracoAlias>
<name>Superscript</name>
<icon>images/editor/sup.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="superscript">superscript</tinyMceCommand>
<priority>73</priority>
</command>
<command>
<umbracoAlias>mceCharMap</umbracoAlias>
<name>Character map</name>
<icon>images/editor/charmap.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="charmap">charmap</tinyMceCommand>
<priority>74</priority>
</command>
</commands>
<plugins>
<plugin loadOnFrontend="true">code</plugin>
<plugin loadOnFrontend="true">codemirror</plugin>
<plugin loadOnFrontend="true">paste</plugin>
<plugin loadOnFrontend="true">umbracolink</plugin>
<plugin loadOnFrontend="true">anchor</plugin>
<plugin loadOnFrontend="true">charmap</plugin>
<plugin loadOnFrontend="true">table</plugin>
<plugin loadOnFrontend="true">codemirror</plugin>
<plugin loadOnFrontend="true">paste</plugin>
<plugin loadOnFrontend="true">umbracolink</plugin>
<plugin loadOnFrontend="true">anchor</plugin>
<plugin loadOnFrontend="true">charmap</plugin>
<plugin loadOnFrontend="true">table</plugin>
<plugin loadOnFrontend="true">lists</plugin>
<plugin loadOnFrontend="true">hr</plugin>
</plugins>
<validElements>
<![CDATA[+a[id|style|rel|data-id|rev|charset|hreflang|dir|lang|tabindex|accesskey|type|name|href|target|title|class|onfocus|onblur|onclick|
<plugin loadOnFrontend="true">hr</plugin>
</plugins>
<validElements>
<![CDATA[+a[id|style|rel|data-id|rev|charset|hreflang|dir|lang|tabindex|accesskey|type|name|href|target|title|class|onfocus|onblur|onclick|
ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup],-strong/-b[class|style],-em/-i[class|style],
-strike[class|style],-u[class|style],#p[id|style|dir|class|align],-ol[class|reversed|start|style|type],-ul[class|style],-li[class|style],br[class],
img[id|dir|lang|longdesc|usemap|style|class|src|onmouseover|onmouseout|border|alt=|title|hspace|vspace|width|height|align|umbracoorgwidth|umbracoorgheight|onresize|onresizestart|onresizeend|rel|data-id],
@@ -228,24 +253,24 @@ thead[id|class],tfoot[id|class],#td[id|lang|dir|class|colspan|rowspan|width|heig
-h3[id|dir|class|align|style],-h4[id|dir|class|align|style],-h5[id|dir|class|align|style],-h6[id|style|dir|class|align|style],hr[class|style],
dd[id|class|title|style|dir|lang],dl[id|class|title|style|dir|lang],dt[id|class|title|style|dir|lang],object[class|id|width|height|codebase|*],
param[name|value|_value|class],embed[type|width|height|src|class|*],map[name|class],area[shape|coords|href|alt|target|class],bdo[class],button[class],iframe[*]]]>
</validElements>
<invalidElements>font</invalidElements>
</validElements>
<invalidElements>font</invalidElements>
<!-- this area is for custom config settings that should be added during TinyMCE initialization -->
<customConfig>
<!-- <config key="myKey">mySetting</config>-->
<config key="entity_encoding">raw</config>
<config key="codemirror">
{
"indentOnInit": false,
"path": "../../../../lib/codemirror",
"config": {
},
"jsFiles": [
],
"cssFiles": [
]
}
</config>
</customConfig>
<!-- this area is for custom config settings that should be added during TinyMCE initialization -->
<customConfig>
<!-- <config key="myKey">mySetting</config>-->
<config key="entity_encoding">raw</config>
<config key="codemirror">
{
"indentOnInit": false,
"path": "../../../../lib/codemirror",
"config": {
},
"jsFiles": [
],
"cssFiles": [
]
}
</config>
</customConfig>
</tinymceConfig>
+51 -27
View File
@@ -5,204 +5,228 @@
<commands>
<command>
<umbracoAlias>code</umbracoAlias>
<name>Code</name>
<icon>images/editor/code.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="code">code</tinyMceCommand>
<priority>1</priority>
</command>
<command>
<umbracoAlias>codemirror</umbracoAlias>
<name>Code mirror</name>
<icon>images/editor/code.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="codemirror">codemirror</tinyMceCommand>
<priority>1</priority>
</command>
<command>
<umbracoAlias>removeformat</umbracoAlias>
<name>Remove format</name>
<icon>images/editor/removeformat.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="removeformat">removeformat</tinyMceCommand>
<priority>2</priority>
</command>
<command>
<umbracoAlias>Undo</umbracoAlias>
<umbracoAlias>undo</umbracoAlias>
<name>Undo</name>
<icon>images/editor/undo.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="undo">undo</tinyMceCommand>
<priority>11</priority>
</command>
<command>
<umbracoAlias>Redo</umbracoAlias>
<umbracoAlias>redo</umbracoAlias>
<name>Redo</name>
<icon>images/editor/redo.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="redo">redo</tinyMceCommand>
<priority>12</priority>
</command>
<command>
<umbracoAlias>Cut</umbracoAlias>
<umbracoAlias>cut</umbracoAlias>
<name>Cut</name>
<icon>images/editor/cut.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="cut">cut</tinyMceCommand>
<priority>13</priority>
</command>
<command>
<umbracoAlias>Copy</umbracoAlias>
<umbracoAlias>copy</umbracoAlias>
<name>Copy</name>
<icon>images/editor/copy.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="copy">copy</tinyMceCommand>
<priority>14</priority>
</command>
<command>
<umbracoAlias>paste</umbracoAlias>
<name>Paste</name>
<icon>images/editor/paste.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="paste">paste</tinyMceCommand>
<priority>15</priority>
</command>
<command>
<umbracoAlias>styleselect</umbracoAlias>
<name>Style select</name>
<icon>images/editor/showStyles.png</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="styleselect">styleselect</tinyMceCommand>
<priority>20</priority>
</command>
<command>
<umbracoAlias>bold</umbracoAlias>
<name>Bold</name>
<icon>images/editor/bold.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="bold">bold</tinyMceCommand>
<priority>21</priority>
</command>
<command>
<umbracoAlias>italic</umbracoAlias>
<name>Italic</name>
<icon>images/editor/italic.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="italic">italic</tinyMceCommand>
<priority>22</priority>
</command>
<command>
<umbracoAlias>Underline</umbracoAlias>
<umbracoAlias>underline</umbracoAlias>
<name>Underline</name>
<icon>images/editor/underline.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="underline">underline</tinyMceCommand>
<priority>23</priority>
</command>
<command>
<umbracoAlias>Strikethrough</umbracoAlias>
<umbracoAlias>strikethrough</umbracoAlias>
<name>Strikethrough</name>
<icon>images/editor/strikethrough.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="strikethrough">strikethrough</tinyMceCommand>
<priority>24</priority>
</command>
<command>
<umbracoAlias>JustifyLeft</umbracoAlias>
<umbracoAlias>justifyleft</umbracoAlias>
<name>Justify left</name>
<icon>images/editor/justifyleft.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="alignleft">justifyleft</tinyMceCommand>
<priority>31</priority>
</command>
<command>
<umbracoAlias>JustifyCenter</umbracoAlias>
<umbracoAlias>justifycenter</umbracoAlias>
<name>Justify center</name>
<icon>images/editor/justifycenter.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="aligncenter">justifycenter</tinyMceCommand>
<priority>32</priority>
</command>
<command>
<umbracoAlias>JustifyRight</umbracoAlias>
<umbracoAlias>justifyright</umbracoAlias>
<name>Justify right</name>
<icon>images/editor/justifyright.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="alignright">justifyright</tinyMceCommand>
<priority>33</priority>
</command>
<command>
<umbracoAlias>JustifyFull</umbracoAlias>
<umbracoAlias>justifyfull</umbracoAlias>
<name>Justify full</name>
<icon>images/editor/justifyfull.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="alignjustify">alignjustify</tinyMceCommand>
<priority>34</priority>
</command>
<command>
<umbracoAlias>bullist</umbracoAlias>
<name>Bullet list</name>
<icon>images/editor/bullist.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="bullist">bullist</tinyMceCommand>
<priority>41</priority>
</command>
<command>
<umbracoAlias>numlist</umbracoAlias>
<name>Numbered list</name>
<icon>images/editor/numlist.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="numlist">numlist</tinyMceCommand>
<priority>42</priority>
</command>
<command>
<umbracoAlias>Outdent</umbracoAlias>
<umbracoAlias>outdent</umbracoAlias>
<name>Decrease indent</name>
<icon>images/editor/outdent.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="outdent">outdent</tinyMceCommand>
<priority>43</priority>
</command>
<command>
<umbracoAlias>Indent</umbracoAlias>
<umbracoAlias>indent</umbracoAlias>
<name>Increase indent</name>
<icon>images/editor/indent.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="indent">indent</tinyMceCommand>
<priority>44</priority>
</command>
<command>
<umbracoAlias>mceLink</umbracoAlias>
<name>Insert/edit link</name>
<icon>images/editor/link.gif</icon>
<tinyMceCommand value="" userInterface="true" frontendCommand="link">link</tinyMceCommand>
<priority>51</priority>
</command>
<command>
<umbracoAlias>unlink</umbracoAlias>
<name>Remove link</name>
<icon>images/editor/unLink.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="unlink">unlink</tinyMceCommand>
<priority>52</priority>
</command>
<command>
<umbracoAlias>mceInsertAnchor</umbracoAlias>
<name>Anchor</name>
<icon>images/editor/anchor.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="anchor">anchor</tinyMceCommand>
<priority>53</priority>
</command>
<command>
<umbracoAlias>mceImage</umbracoAlias>
<name>Image</name>
<icon>images/editor/image.gif</icon>
<tinyMceCommand value="" userInterface="true" frontendCommand="umbmediapicker">image</tinyMceCommand>
<priority>61</priority>
</command>
<command>
<umbracoAlias>umbracomacro</umbracoAlias>
<name>Macro</name>
<icon>images/editor/insMacro.gif</icon>
<tinyMceCommand value="" userInterface="true" frontendCommand="umbmacro">umbracomacro</tinyMceCommand>
<priority>62</priority>
</command>
<command>
<umbracoAlias>mceInsertTable</umbracoAlias>
<name>Table</name>
<icon>images/editor/table.gif</icon>
<tinyMceCommand value="" userInterface="true" frontendCommand="table">table</tinyMceCommand>
<priority>63</priority>
</command>
<command>
<umbracoAlias>umbracoembed</umbracoAlias>
<name>Embed</name>
<icon>images/editor/media.gif</icon>
<tinyMceCommand value="" userInterface="true" frontendCommand="umbembeddialog">umbracoembed</tinyMceCommand>
<priority>66</priority>
</command>
<command>
<umbracoAlias>inserthorizontalrule</umbracoAlias>
<name>Horizontal rule</name>
<icon>images/editor/hr.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="hr">hr</tinyMceCommand>
<priority>71</priority>
</command>
<command>
<umbracoAlias>subscript</umbracoAlias>
<name>Subscript</name>
<icon>images/editor/sub.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="subscript">subscript</tinyMceCommand>
<priority>72</priority>
</command>
<command>
<umbracoAlias>superscript</umbracoAlias>
<name>Superscript</name>
<icon>images/editor/sup.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="superscript">superscript</tinyMceCommand>
<priority>73</priority>
</command>
<command>
<umbracoAlias>mceCharMap</umbracoAlias>
<name>Character map</name>
<icon>images/editor/charmap.gif</icon>
<tinyMceCommand value="" userInterface="false" frontendCommand="charmap">charmap</tinyMceCommand>
<priority>74</priority>
</command>
</commands>
<plugins>
<plugin loadOnFrontend="true">code</plugin>
@@ -247,4 +271,4 @@ param[name|value|_value|class],embed[type|width|height|src|class|*],map[name|cla
}
</config>
</customConfig>
</tinymceConfig>
</tinymceConfig>
+1 -1
View File
@@ -35,6 +35,6 @@
<package id="SqlServerCE" version="4.0.0.1" targetFramework="net45" />
<package id="System.Collections.Immutable" version="1.1.36" targetFramework="net45" />
<package id="System.Reflection.Metadata" version="1.0.21" targetFramework="net45" />
<package id="Umbraco.ModelsBuilder" version="3.0.0" targetFramework="net45" />
<package id="Umbraco.ModelsBuilder" version="3.0.1" targetFramework="net45" />
<package id="UrlRewritingNet.UrlRewriter" version="2.0.7" targetFramework="net45" />
</packages>
@@ -1055,7 +1055,9 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="yesDelete">Yes, delete</key>
<key alias="movedUnderneath">was moved underneath</key>
<key alias="copiedUnderneath">was copied underneath</key>
<key alias="folderToMove">Select the folder to move</key>
<key alias="folderToCopy">Select the folder to copy</key>
<key alias="structureBelow">to in the tree structure below</key>
<key alias="allDocumentTypes">All Document types</key>
@@ -1051,7 +1051,9 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="yesDelete">Yes, delete</key>
<key alias="movedUnderneath">was moved underneath</key>
<key alias="copiedUnderneath">was copied underneath</key>
<key alias="folderToMove">Select the folder to move</key>
<key alias="folderToCopy">Select the folder to copy</key>
<key alias="structureBelow">to in the tree structure below</key>
<key alias="allDocumentTypes">All Document types</key>
+14 -2
View File
@@ -8,6 +8,7 @@ using System.Net.Http.Formatting;
using System.Text;
using System.Web.Http;
using System.Web.Http.ModelBinding;
using System.Web.Http.ModelBinding.Binders;
using AutoMapper;
using Umbraco.Core;
using Umbraco.Core.Logging;
@@ -215,6 +216,19 @@ namespace Umbraco.Web.Editors
return HasPermission(permissionToCheck, nodeId);
}
/// <summary>
/// Returns permissions for all nodes passed in for the current user
/// </summary>
/// <param name="nodeIds"></param>
/// <returns></returns>
[HttpPost]
public Dictionary<int, string[]> GetPermissions(int[] nodeIds)
{
return Services.UserService
.GetPermissions(Security.CurrentUser, nodeIds)
.ToDictionary(x => x.EntityId, x => x.AssignedPermissions);
}
[HttpGet]
public bool HasPermission(string permissionToCheck, int nodeId)
{
@@ -352,9 +366,7 @@ namespace Umbraco.Web.Editors
return display;
}
/// <summary>
/// Publishes a document with a given ID
/// </summary>
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Net;
@@ -22,9 +23,9 @@ using Umbraco.Web.Models;
namespace Umbraco.Web.Editors
{
//TODO: We'll need to be careful about the security on this controller, when we start implementing
//TODO: We'll need to be careful about the security on this controller, when we start implementing
// methods to modify content types we'll need to enforce security on the individual methods, we
// cannot put security on the whole controller because things like
// cannot put security on the whole controller because things like
// GetAllowedChildren, GetPropertyTypeScaffold, GetAllPropertyTypeAliases are required for content editing.
/// <summary>
@@ -129,7 +130,7 @@ namespace Umbraco.Web.Editors
}
[UmbracoTreeAuthorize(
Constants.Trees.DocumentTypes, Constants.Trees.Content,
Constants.Trees.DocumentTypes, Constants.Trees.Content,
Constants.Trees.MediaTypes, Constants.Trees.Media,
Constants.Trees.MemberTypes, Constants.Trees.Members)]
public ContentPropertyDisplay GetPropertyTypeScaffold(int id)
@@ -166,13 +167,13 @@ namespace Umbraco.Web.Editors
return Request.CreateResponse(HttpStatusCode.OK);
}
public HttpResponseMessage PostCreateContainer(int parentId, string name)
{
var result = Services.ContentTypeService.CreateContentTypeContainer(parentId, name, Security.CurrentUser.Id);
return result
? Request.CreateResponse(HttpStatusCode.OK, result.Result) //return the id
? Request.CreateResponse(HttpStatusCode.OK, result.Result) //return the id
: Request.CreateNotificationValidationErrorResponse(result.Exception.Message);
}
@@ -203,7 +204,7 @@ namespace Umbraco.Web.Editors
//make sure the template alias is set on the default and allowed template so we can map it back
ctSave.DefaultTemplate = template.Alias;
}
});
@@ -231,7 +232,7 @@ namespace Umbraco.Web.Editors
}
else
ct = new ContentType(parentId);
ct.Icon = "icon-document";
var dto = Mapper.Map<IContentType, DocumentTypeDisplay>(ct);
@@ -302,7 +303,7 @@ namespace Umbraco.Web.Editors
}
/// <summary>
/// Move the media type
/// Move the content type
/// </summary>
/// <param name="move"></param>
/// <returns></returns>
@@ -314,6 +315,17 @@ namespace Umbraco.Web.Editors
doMove: (type, i) => Services.ContentTypeService.MoveContentType(type, i));
}
/// <summary>
/// Copy the content type
/// </summary>
/// <param name="copy"></param>
/// <returns></returns>
public HttpResponseMessage PostCopy(MoveOrCopy copy)
{
return PerformCopy(
copy,
getContentType: i => Services.ContentTypeService.GetContentType(i),
doCopy: (type, i) => Services.ContentTypeService.CopyContentType(type, i));
}
}
}
@@ -25,7 +25,7 @@ namespace Umbraco.Web.Editors
/// <summary>
/// Am abstract API controller providing functionality used for dealing with content and media types
/// </summary>
[PluginController("UmbracoApi")]
[PluginController("UmbracoApi")]
[PrefixlessBodyModelValidator]
public abstract class ContentTypeControllerBase : UmbracoAuthorizedJsonController
{
@@ -36,7 +36,7 @@ namespace Umbraco.Web.Editors
/// </summary>
protected ContentTypeControllerBase()
: this(UmbracoContext.Current)
{
{
}
/// <summary>
@@ -61,17 +61,17 @@ namespace Umbraco.Web.Editors
/// This is required because in the case of creating/modifying a content type because new property types being added to it are not yet persisted so cannot
/// be looked up via the db, they need to be passed in.
/// </param>
/// <param name="contentTypeId"></param>
/// <param name="contentTypeId"></param>
/// <returns></returns>
protected IEnumerable<Tuple<EntityBasic, bool>> PerformGetAvailableCompositeContentTypes(int contentTypeId,
UmbracoObjectTypes type,
protected IEnumerable<Tuple<EntityBasic, bool>> PerformGetAvailableCompositeContentTypes(int contentTypeId,
UmbracoObjectTypes type,
string[] filterContentTypes,
string[] filterPropertyTypes)
{
IContentTypeComposition source = null;
//below is all ported from the old doc type editor and comes with the same weaknesses /insanity / magic
IContentTypeComposition[] allContentTypes;
switch (type)
@@ -132,7 +132,7 @@ namespace Umbraco.Web.Editors
})
.ToList();
}
protected string TranslateItem(string text)
{
@@ -155,7 +155,7 @@ namespace Umbraco.Web.Editors
Action<TContentTypeSave> beforeCreateNew = null)
where TContentType : class, IContentTypeComposition
where TContentTypeDisplay : ContentTypeCompositionDisplay
where TContentTypeSave : ContentTypeSave<TPropertyType>
where TContentTypeSave : ContentTypeSave<TPropertyType>
where TPropertyType : PropertyTypeBasic
{
var ctId = Convert.ToInt32(contentTypeSave.Id);
@@ -187,10 +187,10 @@ namespace Umbraco.Web.Editors
{
group.Properties = group.Properties.Where(x => x.Alias.IsNullOrWhiteSpace() == false).ToList();
}
if (ctId > 0)
{
//its an update to an existing content type
//its an update to an existing content type
//This mapping will cause a lot of content type validation to occur which we need to deal with
try
@@ -216,7 +216,7 @@ namespace Umbraco.Web.Editors
{
beforeCreateNew(contentTypeSave);
}
//check if the type is trying to allow type 0 below itself - id zero refers to the currently unsaved type
//always filter these 0 types out
var allowItselfAsChild = false;
@@ -227,12 +227,12 @@ namespace Umbraco.Web.Editors
}
//save as new
TContentType newCt = null;
try
{
//This mapping will cause a lot of content type validation to occur which we need to deal with
newCt = Mapper.Map<TContentType>(contentTypeSave);
newCt = Mapper.Map<TContentType>(contentTypeSave);
}
catch (Exception ex)
{
@@ -260,9 +260,9 @@ namespace Umbraco.Web.Editors
return newCt;
}
}
/// <summary>
/// Change the sort order for media
/// Move
/// </summary>
/// <param name="move"></param>
/// <param name="getContentType"></param>
@@ -293,7 +293,52 @@ namespace Umbraco.Web.Editors
case MoveOperationStatusType.FailedParentNotFound:
return Request.CreateResponse(HttpStatusCode.NotFound);
case MoveOperationStatusType.FailedCancelledByEvent:
//returning an object of INotificationModel will ensure that any pending
//returning an object of INotificationModel will ensure that any pending
// notification messages are added to the response.
return Request.CreateValidationErrorResponse(new SimpleNotificationModel());
case MoveOperationStatusType.FailedNotAllowedByPath:
var notificationModel = new SimpleNotificationModel();
notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedByPath"), "");
return Request.CreateValidationErrorResponse(notificationModel);
default:
throw new ArgumentOutOfRangeException();
}
}
/// <summary>
/// Move
/// </summary>
/// <param name="move"></param>
/// <param name="getContentType"></param>
/// <param name="doCopy"></param>
/// <returns></returns>
protected HttpResponseMessage PerformCopy<TContentType>(
MoveOrCopy move,
Func<int, TContentType> getContentType,
Func<TContentType, int, Attempt<OperationStatus<TContentType, MoveOperationStatusType>>> doCopy)
where TContentType : IContentTypeComposition
{
var toMove = getContentType(move.Id);
if (toMove == null)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
var result = doCopy(toMove, move.ParentId);
if (result.Success)
{
var copy = result.Result.Entity;
var response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(copy.Path, Encoding.UTF8, "application/json");
return response;
}
switch (result.Result.StatusType)
{
case MoveOperationStatusType.FailedParentNotFound:
return Request.CreateResponse(HttpStatusCode.NotFound);
case MoveOperationStatusType.FailedCancelledByEvent:
//returning an object of INotificationModel will ensure that any pending
// notification messages are added to the response.
return Request.CreateValidationErrorResponse(new SimpleNotificationModel());
case MoveOperationStatusType.FailedNotAllowedByPath:
@@ -312,14 +357,14 @@ namespace Umbraco.Web.Editors
/// <param name="composition"></param>
/// <returns></returns>
private HttpResponseException CreateCompositionValidationExceptionIfInvalid<TContentTypeSave, TPropertyType, TContentTypeDisplay>(TContentTypeSave contentTypeSave, IContentTypeComposition composition)
where TContentTypeSave : ContentTypeSave<TPropertyType>
where TContentTypeSave : ContentTypeSave<TPropertyType>
where TPropertyType : PropertyTypeBasic
where TContentTypeDisplay : ContentTypeCompositionDisplay
{
var validateAttempt = Services.ContentTypeService.ValidateComposition(composition);
if (validateAttempt == false)
{
//if it's not successful then we need to return some model state for the property aliases that
//if it's not successful then we need to return some model state for the property aliases that
// are duplicated
var invalidPropertyAliases = validateAttempt.Result.Distinct();
AddCompositionValidationErrors<TContentTypeSave, TPropertyType>(contentTypeSave, invalidPropertyAliases);
@@ -340,7 +385,7 @@ namespace Umbraco.Web.Editors
/// <param name="invalidPropertyAliases"></param>
/// <returns></returns>
private void AddCompositionValidationErrors<TContentTypeSave, TPropertyType>(TContentTypeSave contentTypeSave, IEnumerable<string> invalidPropertyAliases)
where TContentTypeSave : ContentTypeSave<TPropertyType>
where TContentTypeSave : ContentTypeSave<TPropertyType>
where TPropertyType : PropertyTypeBasic
{
foreach (var propertyAlias in invalidPropertyAliases)
@@ -369,8 +414,8 @@ namespace Umbraco.Web.Editors
private HttpResponseException CreateInvalidCompositionResponseException<TContentTypeDisplay, TContentType, TContentTypeSave, TPropertyType>(
Exception ex, TContentTypeSave contentTypeSave, TContentType ct, int ctId)
where TContentType : class, IContentTypeComposition
where TContentTypeDisplay : ContentTypeCompositionDisplay
where TContentTypeSave : ContentTypeSave<TPropertyType>
where TContentTypeDisplay : ContentTypeCompositionDisplay
where TContentTypeSave : ContentTypeSave<TPropertyType>
where TPropertyType : PropertyTypeBasic
{
InvalidCompositionException invalidCompositionException = null;
@@ -431,7 +476,7 @@ namespace Umbraco.Web.Editors
(_cultureDictionary = CultureDictionaryFactoryResolver.Current.Factory.CreateDictionary());
}
}
}
}
+22 -10
View File
@@ -22,7 +22,7 @@ using Umbraco.Web.Models;
namespace Umbraco.Web.Editors
{
//TODO: We'll need to be careful about the security on this controller, when we start implementing
//TODO: We'll need to be careful about the security on this controller, when we start implementing
// methods to modify content types we'll need to enforce security on the individual methods, we
// cannot put security on the whole controller because things like GetAllowedChildren are required for content editing.
@@ -49,7 +49,7 @@ namespace Umbraco.Web.Editors
public MediaTypeController(UmbracoContext umbracoContext)
: base(umbracoContext)
{
}
public int GetCount()
@@ -112,7 +112,7 @@ namespace Umbraco.Web.Editors
contentType = x.Item1,
allowed = x.Item2
});
return Request.CreateResponse(result);
return Request.CreateResponse(result);
}
public MediaTypeDisplay GetEmpty(int parentId)
@@ -129,8 +129,8 @@ namespace Umbraco.Web.Editors
/// Returns all member types
/// </summary>
public IEnumerable<ContentTypeBasic> GetAll()
{
{
return Services.ContentTypeService.GetAllMediaTypes()
.Select(Mapper.Map<IMediaType, ContentTypeBasic>);
}
@@ -154,7 +154,7 @@ namespace Umbraco.Web.Editors
var result = Services.ContentTypeService.CreateMediaTypeContainer(parentId, name, Security.CurrentUser.Id);
return result
? Request.CreateResponse(HttpStatusCode.OK, result.Result) //return the id
? Request.CreateResponse(HttpStatusCode.OK, result.Result) //return the id
: Request.CreateNotificationValidationErrorResponse(result.Exception.Message);
}
@@ -228,10 +228,22 @@ namespace Umbraco.Web.Editors
public HttpResponseMessage PostMove(MoveOrCopy move)
{
return PerformMove(
move,
getContentType: i => Services.ContentTypeService.GetMediaType(i),
doMove: (type, i) => Services.ContentTypeService.MoveMediaType(type, i));
move,
getContentType: i => Services.ContentTypeService.GetMediaType(i),
doMove: (type, i) => Services.ContentTypeService.MoveMediaType(type, i));
}
/// <summary>
/// Copy the media type
/// </summary>
/// <param name="copy"></param>
/// <returns></returns>
public HttpResponseMessage PostCopy(MoveOrCopy copy)
{
return PerformCopy(
copy,
getContentType: i => Services.ContentTypeService.GetMediaType(i),
doCopy: (type, i) => Services.ContentTypeService.CopyMediaType(type, i));
}
}
}
+12 -7
View File
@@ -275,6 +275,14 @@ namespace Umbraco.Web.Editors
{
throw new NotSupportedException("Currently the member editor does not support providers that have RequiresQuestionAndAnswer specified");
}
//We're gonna look up the current roles now because the below code can cause
// events to be raised and developers could be manually adding roles to members in
// their handlers. If we don't look this up now there's a chance we'll just end up
// removing the roles they've assigned.
var currRoles = Roles.GetRolesForUser(contentItem.PersistedContent.Username);
//find the ones to remove and remove them
var rolesToRemove = currRoles.Except(contentItem.Groups).ToArray();
string generatedPassword = null;
//Depending on the action we need to first do a create or update using the membership provider
@@ -316,18 +324,15 @@ namespace Umbraco.Web.Editors
//create/save the IMember
Services.MemberService.Save(contentItem.PersistedContent);
}
//Now let's do the role provider stuff - now that we've saved the content item (that is important since
// if we are changing the username, it must be persisted before looking up the member roles).
var currGroups = Roles.GetRolesForUser(contentItem.PersistedContent.Username);
//find the ones to remove and remove them
var toRemove = currGroups.Except(contentItem.Groups).ToArray();
if (toRemove.Any())
if (rolesToRemove.Any())
{
Roles.RemoveUserFromRoles(contentItem.PersistedContent.Username, toRemove);
Roles.RemoveUserFromRoles(contentItem.PersistedContent.Username, rolesToRemove);
}
//find the ones to add and add them
var toAdd = contentItem.Groups.Except(currGroups).ToArray();
var toAdd = contentItem.Groups.Except(currRoles).ToArray();
if (toAdd.Any())
{
//add the ones submitted
@@ -3,8 +3,11 @@ using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
using ClientDependency.Core.Config;
using Microsoft.Owin.Security;
using Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Web.Editors;
namespace Umbraco.Web
@@ -30,6 +33,7 @@ namespace Umbraco.Web
/// </remarks>
public static IHtmlString BareMinimumServerVariablesScript(this HtmlHelper html, UrlHelper uri, string externalLoginsUrl)
{
var version = UmbracoVersion.GetSemanticVersion().ToSemanticString();
var str = @"<script type=""text/javascript"">
var Umbraco = {};
Umbraco.Sys = {};
@@ -40,7 +44,9 @@ namespace Umbraco.Web
""externalLoginsUrl"": """ + externalLoginsUrl + @"""
},
""application"": {
""applicationPath"": """ + html.ViewContext.HttpContext.Request.ApplicationPath + @"""
""applicationPath"": """ + html.ViewContext.HttpContext.Request.ApplicationPath + @""",
""version"": """ + version + @""",
""cdf"": """ + ClientDependencySettings.Instance.Version + @"""
},
""isDebuggingEnabled"" : " + html.ViewContext.HttpContext.IsDebuggingEnabled.ToString().ToLowerInvariant() + @"
};
@@ -10,6 +10,9 @@ namespace Umbraco.Web.Models.ContentEditing
[DataContract(Name = "richtexteditorcommand", Namespace = "")]
public class RichTextEditorCommand
{
[DataMember(Name = "name")]
public string Name { get; set; }
[DataMember(Name = "icon")]
public string Icon { get; set; }
@@ -292,35 +292,9 @@ namespace Umbraco.Web.Models.Mapping
source.HasIdentity ? source.Id : source.ParentId)
.FirstOrDefault();
if (permissions == null)
{
return Enumerable.Empty<char>();
}
var result = new List<char>();
//can they publish ?
if (permissions.AssignedPermissions.Contains(ActionPublish.Instance.Letter.ToString(CultureInfo.InvariantCulture)))
{
result.Add(ActionPublish.Instance.Letter);
}
//can they send to publish ?
if (permissions.AssignedPermissions.Contains(ActionToPublish.Instance.Letter.ToString(CultureInfo.InvariantCulture)))
{
result.Add(ActionToPublish.Instance.Letter);
}
//can they save ?
if (permissions.AssignedPermissions.Contains(ActionUpdate.Instance.Letter.ToString(CultureInfo.InvariantCulture)))
{
result.Add(ActionUpdate.Instance.Letter);
}
//can they create ?
if (permissions.AssignedPermissions.Contains(ActionNew.Instance.Letter.ToString(CultureInfo.InvariantCulture)))
{
result.Add(ActionNew.Instance.Letter);
}
return result;
return permissions == null
? Enumerable.Empty<char>()
: permissions.AssignedPermissions.Where(x => x.Length == 1).Select(x => x.ToUpperInvariant()[0]);
}
}
+27 -15
View File
@@ -8,7 +8,10 @@ using Umbraco.Web.Models;
namespace Umbraco.Web.Mvc
{
public class RenderModelBinder : IModelBinder, IModelBinderProvider
/// <summary>
/// Allows for Model Binding any IPublishedContent or IRenderModel
/// </summary>
public class RenderModelBinder : DefaultModelBinder, IModelBinder, IModelBinderProvider
{
/// <summary>
/// Binds the model to a value by using the specified controller context and binding context.
@@ -17,14 +20,29 @@ namespace Umbraco.Web.Mvc
/// The bound value.
/// </returns>
/// <param name="controllerContext">The controller context.</param><param name="bindingContext">The binding context.</param>
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
object model;
if (controllerContext.RouteData.DataTokens.TryGetValue(Core.Constants.Web.UmbracoDataToken, out model) == false)
return null;
//default culture
var culture = CultureInfo.CurrentCulture;
//This model binder deals with IRenderModel and IPublishedContent by extracting the model from the route's
// datatokens. This data token is set in 2 places: RenderRouteHandler, UmbracoVirtualNodeRouteHandler
// and both always set the model to an instance of `RenderModel`. So if this isn't an instance of IRenderModel then
// we need to let the DefaultModelBinder deal with the logic.
var renderModel = model as IRenderModel;
if (renderModel == null)
{
model = base.BindModel(controllerContext, bindingContext);
if (model == null) return null;
}
//if for any reason the model is not either IRenderModel or IPublishedContent, then we return since those are the only
// types this binder is dealing with.
if ((model is IRenderModel) == false && (model is IPublishedContent) == false) return null;
//default culture
var culture = CultureInfo.CurrentCulture;
var umbracoContext = controllerContext.GetUmbracoContext()
?? UmbracoContext.Current;
@@ -34,8 +52,8 @@ namespace Umbraco.Web.Mvc
culture = umbracoContext.PublishedContentRequest.Culture;
}
return BindModel(model, bindingContext.ModelType, culture);
}
return BindModel(model, bindingContext.ModelType, culture);
}
// source is the model that we have
// modelType is the type of the model that we need to bind to
@@ -140,15 +158,9 @@ namespace Umbraco.Web.Mvc
public IModelBinder GetBinder(Type modelType)
{
// can bind to RenderModel
if (modelType == typeof(RenderModel)) return this;
// can bind to RenderModel<TContent>
if (modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(RenderModel<>)) return this;
// can bind to TContent where TContent : IPublishedContent
if (typeof(IPublishedContent).IsAssignableFrom(modelType)) return this;
return null;
return TypeHelper.IsTypeAssignableFrom<IRenderModel>(modelType) || TypeHelper.IsTypeAssignableFrom<IPublishedContent>(modelType)
? this
: null;
}
}
}
@@ -42,8 +42,6 @@ namespace Umbraco.Web.PropertyEditors
return config;
}
private static void EnsureInit()
{
@@ -71,9 +69,10 @@ namespace Umbraco.Web.PropertyEditors
new RichTextEditorCommand()
{
IsStylePicker = isStyle,
Name = n.SelectSingleNode("./name") != null ? n.SelectSingleNode("./name").FirstChild.Value : alias,
Icon = n.SelectSingleNode("./icon").FirstChild.Value,
Command = n.SelectSingleNode("./tinyMceCommand").FirstChild.Value,
Alias = n.SelectSingleNode("./umbracoAlias").FirstChild.Value.ToLower(),
Alias = alias,
UserInterface = n.SelectSingleNode("./tinyMceCommand").Attributes.GetNamedItem("userInterface").Value,
FrontEndCommand = n.SelectSingleNode("./tinyMceCommand").Attributes.GetNamedItem("frontendCommand").Value,
Value = n.SelectSingleNode("./tinyMceCommand").Attributes.GetNamedItem("value").Value,
@@ -136,4 +135,4 @@ namespace Umbraco.Web.PropertyEditors
}
}
}
}
@@ -87,7 +87,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
{
throw new NotImplementedException("PublishedMediaCache does not support XPath.");
}
public virtual IEnumerable<IPublishedContent> GetByXPath(UmbracoContext umbracoContext, bool preview, string xpath, XPathVariable[] vars)
{
throw new NotImplementedException("PublishedMediaCache does not support XPath.");
@@ -105,7 +105,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
public bool XPathNavigatorIsNavigable { get { return false; } }
public virtual bool HasContent(UmbracoContext context, bool preview) { throw new NotImplementedException(); }
public virtual bool HasContent(UmbracoContext context, bool preview) { throw new NotImplementedException(); }
private ExamineManager GetExamineManagerSafe()
{
@@ -138,7 +138,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
return indexer;
}
catch (Exception ex)
{
{
LogHelper.Error<PublishedMediaCache>("Could not retrieve the InternalIndexer", ex);
//something didn't work, continue returning null.
}
@@ -182,6 +182,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
// it is called, but at least it should NOT hit the database
// nor Lucene each time, relying on the memory cache instead
if (id <= 0) return null; // fail fast
var cacheValues = GetCacheValues(id, GetUmbracoMediaCacheValues);
return cacheValues == null ? null : CreateFromCacheValues(cacheValues);
@@ -197,11 +199,11 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
{
//first check in Examine as this is WAY faster
var criteria = searchProvider.CreateSearchCriteria("media");
var filter = criteria.Id(id).Not().Field(UmbracoContentIndexer.IndexPathFieldName, "-1,-21,".MultipleCharacterWildcard());
//the above filter will create a query like this, NOTE: That since the use of the wildcard, it automatically escapes it in Lucene.
//+(+__NodeId:3113 -__Path:-1,-21,*) +__IndexType:media
var results = searchProvider.Search(filter.Compile());
if (results.Any())
{
@@ -215,7 +217,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
//Catch the exception here for the time being, and just fallback to GetMedia
//TODO: Need to fix examine in LB scenarios!
LogHelper.Error<PublishedMediaCache>("Could not load data from Examine index for media", ex);
}
}
}
LogHelper.Warn<PublishedMediaCache>(
@@ -231,8 +233,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
{
if (media != null && media.Current != null)
{
return media.Current.Name.InvariantEquals("error")
? null
return media.Current.Name.InvariantEquals("error")
? null
: ConvertFromXPathNavigator(media.Current);
}
@@ -245,14 +247,14 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
internal CacheValues ConvertFromSearchResult(SearchResult searchResult)
{
//NOTE: Some fields will not be included if the config section for the internal index has been
//NOTE: Some fields will not be included if the config section for the internal index has been
//mucked around with. It should index everything and so the index definition should simply be:
// <IndexSet SetName="InternalIndexSet" IndexPath="~/App_Data/TEMP/ExamineIndexes/Internal/" />
var values = new Dictionary<string, string>(searchResult.Fields);
//we need to ensure some fields exist, because of the above issue
if (!new []{"template", "templateId"}.Any(values.ContainsKey))
if (!new []{"template", "templateId"}.Any(values.ContainsKey))
values.Add("template", 0.ToString());
if (!new[] { "sortOrder" }.Any(values.ContainsKey))
values.Add("sortOrder", 0.ToString());
@@ -269,7 +271,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
if (!new[] { "createDate" }.Any(values.ContainsKey))
values.Add("createDate", default(DateTime).ToString("yyyy-MM-dd HH:mm:ss"));
if (!new[] { "level" }.Any(values.ContainsKey))
{
{
values.Add("level", values["__Path"].Split(',').Length.ToString());
}
@@ -303,7 +305,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
{
values["nodeTypeAlias"] = xpath.Name;
}
var result = xpath.SelectChildren(XPathNodeType.Element);
//add the attributes e.g. id, parentId etc
if (result.Current != null && result.Current.HasAttributes)
@@ -320,7 +322,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
if (!values.ContainsKey(result.Current.Name))
{
values[result.Current.Name] = result.Current.Value;
}
}
}
result.Current.MoveToParent();
}
@@ -351,9 +353,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
XPath = forceNav ? xpath : null // outside of tests we do NOT want to cache the navigator!
};
//var content = new DictionaryPublishedContent(values,
//var content = new DictionaryPublishedContent(values,
// d => d.ParentId != -1 //parent should be null if -1
// ? GetUmbracoMedia(d.ParentId)
// ? GetUmbracoMedia(d.ParentId)
// : null,
// //callback to return the children of the current node based on the xml structure already found
// d => GetChildrenMedia(d.Id, xpath),
@@ -363,8 +365,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
}
/// <summary>
/// We will need to first check if the document was loaded by Examine, if so we'll need to check if this property exists
/// in the results, if it does not, then we'll have to revert to looking up in the db.
/// We will need to first check if the document was loaded by Examine, if so we'll need to check if this property exists
/// in the results, if it does not, then we'll have to revert to looking up in the db.
/// </summary>
/// <param name="dd"> </param>
/// <param name="alias"></param>
@@ -399,7 +401,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
/// <param name="xpath"></param>
/// <returns></returns>
private IEnumerable<IPublishedContent> GetChildrenMedia(int parentId, XPathNavigator xpath = null)
{
{
//if there is no navigator, try examine first, then re-look it up
if (xpath == null)
@@ -412,7 +414,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
{
//first check in Examine as this is WAY faster
var criteria = searchProvider.CreateSearchCriteria("media");
var filter = criteria.ParentId(parentId).Not().Field(UmbracoContentIndexer.IndexPathFieldName, "-1,-21,".MultipleCharacterWildcard());
//the above filter will create a query like this, NOTE: That since the use of the wildcard, it automatically escapes it in Lucene.
//+(+parentId:3113 -__Path:-1,-21,*) +__IndexType:media
@@ -433,7 +435,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
{
results = searchProvider.Search(filter.Compile());
}
if (results.Any())
{
// var medias = results.Select(ConvertFromSearchResult);
@@ -451,7 +453,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
else
{
//if there's no result then return null. Previously we defaulted back to library.GetMedia below
//but this will always get called for when we are getting descendents since many items won't have
//but this will always get called for when we are getting descendents since many items won't have
//children and then we are hitting the database again!
//So instead we're going to rely on Examine to have the correct results like it should.
return Enumerable.Empty<IPublishedContent>();
@@ -462,7 +464,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
//Currently examine is throwing FileNotFound exceptions when we have a loadbalanced filestore and a node is published in umbraco
//See this thread: http://examine.cdodeplex.com/discussions/264341
//Catch the exception here for the time being, and just fallback to GetMedia
}
}
}
//falling back to get media
@@ -514,13 +516,13 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
// // will leave it here as it must have done something!
// if (x.Name != "contents")
// {
// //make sure it's actually a node, not a property
// //make sure it's actually a node, not a property
// if (!string.IsNullOrEmpty(x.GetAttribute("path", "")) &&
// !string.IsNullOrEmpty(x.GetAttribute("id", "")))
// {
// mediaList.Add(ConvertFromXPathNavigator(x));
// }
// }
// }
//}
return mediaList;
@@ -530,7 +532,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
/// An IPublishedContent that is represented all by a dictionary.
/// </summary>
/// <remarks>
/// This is a helper class and definitely not intended for public use, it expects that all of the values required
/// This is a helper class and definitely not intended for public use, it expects that all of the values required
/// to create an IPublishedContent exist in the dictionary by specific aliases.
/// </remarks>
internal class DictionaryPublishedContent : PublishedContentWithKeyBase
@@ -544,7 +546,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
private static readonly string[] IgnoredKeys = { "version", "isDoc" };
public DictionaryPublishedContent(
IDictionary<string, string> valueDictionary,
IDictionary<string, string> valueDictionary,
Func<int, IPublishedContent> getParent,
Func<int, XPathNavigator, IEnumerable<IPublishedContent>> getChildren,
Func<DictionaryPublishedContent, string, IPublishedProperty> getProperty,
@@ -585,7 +587,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
if (int.TryParse(val, out pId))
{
ParentId = pId;
}
}
}, "parentID");
_contentType = PublishedContentType.Get(PublishedItemType.Media, _documentTypeAlias);
@@ -600,7 +602,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
string value;
const bool isPreviewing = false; // false :: never preview a media
var property = valueDictionary.TryGetValue(alias, out value) == false
? new XmlPublishedProperty(propertyType, isPreviewing)
? new XmlPublishedProperty(propertyType, isPreviewing)
: new XmlPublishedProperty(propertyType, isPreviewing, value);
_properties.Add(property);
}
@@ -648,7 +650,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
internal bool LoadedFromExamine { get; private set; }
//private readonly Func<DictionaryPublishedContent, IPublishedContent> _getParent;
private readonly Lazy<IPublishedContent> _getParent;
private readonly Lazy<IPublishedContent> _getParent;
//private readonly Func<DictionaryPublishedContent, IEnumerable<IPublishedContent>> _getChildren;
private readonly Lazy<IEnumerable<IPublishedContent>> _getChildren;
private readonly Func<DictionaryPublishedContent, string, IPublishedProperty> _getProperty;
@@ -788,7 +790,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
IPublishedProperty property;
string key = null;
var cache = UmbracoContextCache.Current;
if (cache != null)
{
key = string.Format("RECURSIVE_PROPERTY::{0}::{1}", Id, alias.ToLowerInvariant());
@@ -911,15 +913,15 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
// we do clear a lot of things... but the cache refresher is somewhat
// convoluted and it's hard to tell what to clear exactly ;-(
// clear the parent - NOT (why?)
//var exist = (CacheValues) cache.GetCacheItem(key);
//if (exist != null)
// cache.ClearCacheItem(PublishedMediaCacheKey + GetValuesValue(exist.Values, "parentID"));
// clear the item
cache.ClearCacheItem(key);
// clear all children - in case we moved and their path has changed
var fid = "/" + sid + "/";
cache.ClearCacheObjectTypes<CacheValues>((k, v) =>
@@ -217,7 +217,11 @@ namespace Umbraco.Web.Scheduling
{
lock (_locker)
{
if (_isCompleted) return false;
if (_isCompleted)
{
_logger.Debug<BackgroundTaskRunner>(_logPrefix + "Task cannot be added {0}, the task runner is already shutdown", task.GetType);
return false;
}
// add task
_logger.Debug<BackgroundTaskRunner>(_logPrefix + "Task added {0}", task.GetType);
+62 -45
View File
@@ -1,7 +1,10 @@
using System.Web;
using System.Collections.Generic;
using System.Threading;
using System.Web;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Web.Routing;
namespace Umbraco.Web.Scheduling
{
@@ -14,60 +17,74 @@ namespace Umbraco.Web.Scheduling
/// </remarks>
internal sealed class Scheduler : ApplicationEventHandler
{
private static BackgroundTaskRunner<IBackgroundTask> _keepAliveRunner;
private static BackgroundTaskRunner<IBackgroundTask> _publishingRunner;
private static BackgroundTaskRunner<IBackgroundTask> _tasksRunner;
private static BackgroundTaskRunner<IBackgroundTask> _scrubberRunner;
private static volatile bool _started;
private static readonly object Locker = new object();
private BackgroundTaskRunner<IBackgroundTask> _keepAliveRunner;
private BackgroundTaskRunner<IBackgroundTask> _publishingRunner;
private BackgroundTaskRunner<IBackgroundTask> _tasksRunner;
private BackgroundTaskRunner<IBackgroundTask> _scrubberRunner;
private bool _started = false;
private object _locker = new object();
private IBackgroundTask[] _tasks;
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
if (umbracoApplication.Context == null)
return;
//subscribe to app init so we can subsribe to the application events
UmbracoApplicationBase.ApplicationInit += (sender, args) =>
// backgrounds runners are web aware, if the app domain dies, these tasks will wind down correctly
_keepAliveRunner = new BackgroundTaskRunner<IBackgroundTask>("KeepAlive", applicationContext.ProfilingLogger.Logger);
_publishingRunner = new BackgroundTaskRunner<IBackgroundTask>("ScheduledPublishing", applicationContext.ProfilingLogger.Logger);
_tasksRunner = new BackgroundTaskRunner<IBackgroundTask>("ScheduledTasks", applicationContext.ProfilingLogger.Logger);
_scrubberRunner = new BackgroundTaskRunner<IBackgroundTask>("LogScrubber", applicationContext.ProfilingLogger.Logger);
//We will start the whole process when a successful request is made
UmbracoModule.RouteAttempt += UmbracoModuleRouteAttempt;
}
private void UmbracoModuleRouteAttempt(object sender, RoutableAttemptEventArgs e)
{
switch (e.Outcome)
{
var app = (HttpApplication)sender;
case EnsureRoutableOutcome.IsRoutable:
case EnsureRoutableOutcome.NotDocumentRequest:
RegisterBackgroundTasks(e);
break;
}
}
//subscribe to the end of a successful request (a handler actually executed)
app.PostRequestHandlerExecute += (o, eventArgs) =>
private void RegisterBackgroundTasks(UmbracoRequestEventArgs e)
{
//remove handler, we're done
UmbracoModule.RouteAttempt -= UmbracoModuleRouteAttempt;
LazyInitializer.EnsureInitialized(ref _tasks, ref _started, ref _locker, () =>
{
LogHelper.Debug<Scheduler>(() => "Initializing the scheduler");
var settings = UmbracoConfig.For.UmbracoSettings();
var tasks = new List<IBackgroundTask>
{
if (_started == false)
{
lock (Locker)
{
if (_started == false)
{
_started = true;
LogHelper.Debug<Scheduler>(() => "Initializing the scheduler");
// backgrounds runners are web aware, if the app domain dies, these tasks will wind down correctly
_keepAliveRunner = new BackgroundTaskRunner<IBackgroundTask>("KeepAlive", applicationContext.ProfilingLogger.Logger);
_publishingRunner = new BackgroundTaskRunner<IBackgroundTask>("ScheduledPublishing", applicationContext.ProfilingLogger.Logger);
_tasksRunner = new BackgroundTaskRunner<IBackgroundTask>("ScheduledTasks", applicationContext.ProfilingLogger.Logger);
_scrubberRunner = new BackgroundTaskRunner<IBackgroundTask>("LogScrubber", applicationContext.ProfilingLogger.Logger);
var settings = UmbracoConfig.For.UmbracoSettings();
// ping/keepalive
// on all servers
_keepAliveRunner.Add(new KeepAlive(_keepAliveRunner, 60000, 300000, applicationContext));
// scheduled publishing/unpublishing
// install on all, will only run on non-slaves servers
_publishingRunner.Add(new ScheduledPublishing(_publishingRunner, 60000, 60000, applicationContext, settings));
_tasksRunner.Add(new ScheduledTasks(_tasksRunner, 60000, 60000, applicationContext, settings));
// log scrubbing
// install on all, will only run on non-slaves servers
_scrubberRunner.Add(new LogScrubber(_scrubberRunner, 60000, LogScrubber.GetLogScrubbingInterval(settings), applicationContext, settings));
}
}
}
new KeepAlive(_keepAliveRunner, 60000, 300000, e.UmbracoContext.Application),
new ScheduledPublishing(_publishingRunner, 60000, 60000, e.UmbracoContext.Application, settings),
new ScheduledTasks(_tasksRunner, 60000, 60000, e.UmbracoContext.Application, settings),
new LogScrubber(_scrubberRunner, 60000, LogScrubber.GetLogScrubbingInterval(settings), e.UmbracoContext.Application, settings)
};
};
// ping/keepalive
// on all servers
_keepAliveRunner.TryAdd(tasks[0]);
// scheduled publishing/unpublishing
// install on all, will only run on non-slaves servers
_publishingRunner.TryAdd(tasks[1]);
_tasksRunner.TryAdd(tasks[2]);
// log scrubbing
// install on all, will only run on non-slaves servers
_scrubberRunner.TryAdd(tasks[3]);
return tasks.ToArray();
});
}
}
}
@@ -1,8 +1,10 @@
using System.Web;
using System.Linq;
using System.Web;
using Umbraco.Core.Events;
using Umbraco.Core.Persistence.Migrations;
using Umbraco.Web.WebApi.Filters;
using umbraco.interfaces;
using Umbraco.Core.Configuration;
namespace Umbraco.Web.Strategies.Migrations
{
@@ -13,6 +15,8 @@ namespace Umbraco.Web.Strategies.Migrations
{
protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e)
{
if (e.ProductName != GlobalSettings.UmbracoMigrationName) return;
if (HttpContext.Current == null) return;
var http = new HttpContextWrapper(HttpContext.Current);
@@ -5,21 +5,24 @@ using Umbraco.Core.Logging;
using Umbraco.Core.Persistence.Migrations;
using Umbraco.Core.Persistence.SqlSyntax;
using umbraco.interfaces;
using Umbraco.Core.Configuration;
namespace Umbraco.Web.Strategies.Migrations
{
/// <summary>
/// This will execute after upgrading to remove any xml cache for media that are currently in the bin
/// </summary>
/// <remarks>
/// This will execute for specific versions -
///
/// <remarks>
/// This will execute for specific versions -
///
/// * If current is less than or equal to 7.0.0
/// </remarks>
public class ClearMediaXmlCacheForDeletedItemsAfterUpgrade : MigrationStartupHander
{
{
protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e)
{
if (e.ProductName != GlobalSettings.UmbracoMigrationName) return;
var target70 = new Version(7, 0, 0);
if (e.ConfiguredVersion <= target70)
@@ -28,7 +31,7 @@ namespace Umbraco.Web.Strategies.Migrations
// http://issues.umbraco.org/issue/U4-3876
var sql = @"DELETE FROM cmsContentXml WHERE nodeId IN
(SELECT nodeId FROM (SELECT DISTINCT cmsContentXml.nodeId FROM cmsContentXml
(SELECT nodeId FROM (SELECT DISTINCT cmsContentXml.nodeId FROM cmsContentXml
INNER JOIN umbracoNode ON cmsContentXml.nodeId = umbracoNode.id
WHERE nodeObjectType = '" + Constants.ObjectTypes.Media + "' AND " + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("path") + " LIKE '%-21%') x)";
@@ -10,6 +10,7 @@ using Umbraco.Core.Persistence.Migrations;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Persistence.UnitOfWork;
using umbraco.interfaces;
using Umbraco.Core.Configuration;
namespace Umbraco.Web.Strategies.Migrations
{
@@ -20,6 +21,8 @@ namespace Umbraco.Web.Strategies.Migrations
{
protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e)
{
if (e.ProductName != GlobalSettings.UmbracoMigrationName) return;
var target720 = new Version(7, 2, 0);
if (e.ConfiguredVersion <= target720)
@@ -95,7 +98,7 @@ namespace Umbraco.Web.Strategies.Migrations
e.MigrationContext.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF;", SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName("cmsDataTypePreValues"))));
}
transaction.Complete();
}
@@ -1,4 +1,5 @@
using Umbraco.Core;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Persistence.Migrations;
namespace Umbraco.Web.Strategies.Migrations
@@ -41,9 +42,20 @@ namespace Umbraco.Web.Strategies.Migrations
private void MigrationRunner_Migrated(MigrationRunner sender, Core.Events.MigrationEventArgs e)
{
AfterMigration(sender, e);
if (TargetProductNames.Length == 0 || TargetProductNames.Contains(e.ProductName))
{
AfterMigration(sender, e);
}
}
protected abstract void AfterMigration(MigrationRunner sender, Core.Events.MigrationEventArgs e);
/// <summary>
/// Override to specify explicit target product names
/// </summary>
/// <remarks>
/// Leaving empty will run for all migration products
/// </remarks>
public virtual string[] TargetProductNames { get { return new string[] {}; } }
}
}
@@ -6,6 +6,7 @@ using System.Text;
using System.Threading.Tasks;
using Umbraco.Core.Events;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence.Migrations;
@@ -22,6 +23,8 @@ namespace Umbraco.Web.Strategies.Migrations
{
protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e)
{
if (e.ProductName != GlobalSettings.UmbracoMigrationName) return;
var target73 = new Version(7, 3, 0);
if (e.ConfiguredVersion <= target73)
@@ -8,6 +8,7 @@ using Umbraco.Core.Persistence.Migrations;
using Umbraco.Core.Persistence.UnitOfWork;
using umbraco.interfaces;
using Umbraco.Core;
using Umbraco.Core.Configuration;
namespace Umbraco.Web.Strategies.Migrations
{
@@ -19,6 +20,8 @@ namespace Umbraco.Web.Strategies.Migrations
{
protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e)
{
if (e.ProductName != GlobalSettings.UmbracoMigrationName) return;
var target = new Version(6, 0, 0);
if (e.ConfiguredVersion < target)
{
@@ -69,7 +72,7 @@ namespace Umbraco.Web.Strategies.Migrations
transaction.Complete();
}
}
}
}
}
}
@@ -4,6 +4,7 @@ using Umbraco.Core.Events;
using Umbraco.Core.Persistence.Migrations;
using Umbraco.Core.Services;
using umbraco.interfaces;
using Umbraco.Core.Configuration;
namespace Umbraco.Web.Strategies.Migrations
{
@@ -12,15 +13,17 @@ namespace Umbraco.Web.Strategies.Migrations
/// </summary>
/// <remarks>
/// This cannot execute as part of a db migration since we need access to the services/repos.
///
/// This will execute for specific versions -
///
///
/// This will execute for specific versions -
///
/// * If current is less than or equal to 7.0.0
/// </remarks>
public class RebuildMediaXmlCacheAfterUpgrade : MigrationStartupHander
{
protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e)
{
if (e.ProductName != GlobalSettings.UmbracoMigrationName) return;
var target70 = new Version(7, 0, 0);
if (e.ConfiguredVersion <= target70)
@@ -28,19 +28,21 @@ namespace Umbraco.Web.Strategies
private DatabaseServerRegistrar _registrar;
private BackgroundTaskRunner<IBackgroundTask> _backgroundTaskRunner;
private bool _started = false;
private TouchServerTask _task;
private object _lock = new object();
// bind to events
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
_registrar = ServerRegistrarResolver.Current.Registrar as DatabaseServerRegistrar;
// only for the DatabaseServerRegistrar
if (_registrar == null) return;
_backgroundTaskRunner = new BackgroundTaskRunner<IBackgroundTask>(
new BackgroundTaskRunnerOptions { AutoStart = true },
applicationContext.ProfilingLogger.Logger);
// only for the DatabaseServerRegistrar
if (_registrar == null) return;
//We will start the whole process when a successful request is made
UmbracoModule.RouteAttempt += UmbracoModuleRouteAttempt;
}
@@ -61,40 +63,34 @@ namespace Umbraco.Web.Strategies
switch (e.Outcome)
{
case EnsureRoutableOutcome.IsRoutable:
// front-end request
RegisterServer(e);
//remove handler, we're done
UmbracoModule.RouteAttempt -= UmbracoModuleRouteAttempt;
break;
case EnsureRoutableOutcome.NotDocumentRequest:
// anything else (back-end request, service...)
//so it's not a document request, we'll check if it's a back office request
if (e.HttpContext.Request.Url.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath))
{
RegisterServer(e);
//remove handler, we're done
UmbracoModule.RouteAttempt -= UmbracoModuleRouteAttempt;
}
break;
RegisterBackgroundTasks(e);
break;
}
}
private void RegisterServer(UmbracoRequestEventArgs e)
private void RegisterBackgroundTasks(UmbracoRequestEventArgs e)
{
//only process once
if (_started) return;
//remove handler, we're done
UmbracoModule.RouteAttempt -= UmbracoModuleRouteAttempt;
_started = true;
//only perform this one time ever
LazyInitializer.EnsureInitialized(ref _task, ref _started, ref _lock, () =>
{
var serverAddress = e.UmbracoContext.Application.UmbracoApplicationUrl;
var svc = e.UmbracoContext.Application.Services.ServerRegistrationService;
var serverAddress = e.UmbracoContext.Application.UmbracoApplicationUrl;
var svc = e.UmbracoContext.Application.Services.ServerRegistrationService;
var task = new TouchServerTask(_backgroundTaskRunner,
15000, //delay before first execution
_registrar.Options.RecurringSeconds*1000, //amount of ms between executions
svc, _registrar, serverAddress);
//Perform the rest async, we don't want to block the startup sequence
// this will just reoccur on a background thread
_backgroundTaskRunner.TryAdd(task);
//Perform the rest async, we don't want to block the startup sequence
// this will just reoccur on a background thread
_backgroundTaskRunner.Add(new TouchServerTask(_backgroundTaskRunner,
15000, //delay before first execution
_registrar.Options.RecurringSeconds * 1000, //amount of ms between executions
svc, _registrar, serverAddress));
return task;
});
}
private class TouchServerTask : RecurringTaskBase
@@ -123,6 +123,7 @@ namespace Umbraco.Web.Trees
menu.Items.Add<ActionMove>(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)), true);
}
}
menu.Items.Add<ActionCopy>(Services.TextService.Localize(string.Format("actions/{0}", ActionCopy.Instance.Alias)));
menu.Items.Add<ActionExport>(Services.TextService.Localize(string.Format("actions/{0}", ActionExport.Instance.Alias)), true).ConvertLegacyMenuItem(new UmbracoEntity
{
Id = int.Parse(id),
@@ -92,6 +92,7 @@ namespace Umbraco.Web.Trees
{
menu.Items.Add<ActionDelete>(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias)));
menu.Items.Add<ActionMove>(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)), hasSeparator: true);
menu.Items.Add<ActionCopy>(Services.TextService.Localize(string.Format("actions/{0}", ActionCopy.Instance.Alias)));
}
return menu;
+2 -2
View File
@@ -77,8 +77,8 @@ namespace Umbraco.Web.Trees
protected override void OnRenderFileNode(ref XmlTreeNode xNode)
{
ChangeNodeAction(xNode);
xNode.Icon = "settingView.gif";
xNode.OpenIcon = "settingView.gif";
xNode.Icon = "icon-article";
xNode.OpenIcon = "icon-article";
xNode.Text = xNode.Text.StripFileExtension();
}
@@ -60,7 +60,7 @@ namespace umbraco.cms.presentation.settings.stylesheet
lttPath.Text = "<a id=\"" + lttPath.ClientID + "\" target=\"_blank\" href=\"" + stylesheet.VirtualPath + "\">" + stylesheet.VirtualPath + "</a>";
editorSource.Text = stylesheet.Content;
TreeSyncPath = DeepLink.GetTreePathFromFilePath(filename);
TreeSyncPath = DeepLink.GetTreePathFromFilePath(filename).TrimEnd(".css");
// name derives from path, without the .css extension, clean for xss
NameTxt.Text = stylesheet.Path.TrimEnd(".css").CleanForXss('\\', '/');