Compare commits

..

28 Commits

Author SHA1 Message Date
Claus 771f8d92f2 adding in missing extension method stuff for some udis. 2017-02-10 14:44:56 +01:00
Claus 5226762528 Merge branch 'temp-U4-9351' into dev-v7.6
Conflicts:
	src/Umbraco.Core/Events/ImportPackageEventArgs.cs
	src/Umbraco.Core/Umbraco.Core.csproj
	src/Umbraco.Web/Editors/PackageInstallController.cs
2017-02-10 10:08:31 +01:00
Claus 7889f82a9a added a check so the 'ensure' wont fail if the path is already as it should be.
added unit test for this method.
2017-02-10 09:25:16 +01:00
Sebastiaan Janssen 3d1a44b32c Merge branch 'temp-U4-8998' into dev-v7.6
# Conflicts:
#	src/Umbraco.Core/Umbraco.Core.csproj
2017-02-10 08:25:52 +01:00
Sebastiaan Janssen 724ec9ab73 Merge pull request #1748 from umbraco/temp-U4-8835
U4-8835 cmsMember.LoginName needs a DB index
2017-02-10 08:22:36 +01:00
Shannon aa0eaf590c adds indexes to relation type 2017-02-10 15:51:41 +11:00
Shannon 2b82520067 U4-8998 umbracoRelation table needs unique index on columns: parentId, childId, relType 2017-02-10 15:38:53 +11:00
Shannon 5aed6334c4 missing file 2017-02-10 14:24:33 +11:00
Shannon 91157caf0e U4-8835 cmsMember.LoginName needs a DB index 2017-02-10 14:23:46 +11:00
Shannon d27fee8a5b Merge remote-tracking branch 'refs/remotes/origin/dev-v7' into dev-v7.6 2017-02-10 09:38:03 +11:00
Sebastiaan Janssen 7aef8cbb67 Merge branch 'temp-U4-9481' into dev-v7.6
# Conflicts:
#	src/Umbraco.Core/Services/FileService.cs
2017-02-09 21:55:17 +01:00
Sebastiaan Janssen a7e28a97d9 Merge pull request #1715 from umbraco/temp-U4-9450
U4-9450 Paged data queries return all property data in the entire dat…
2017-02-09 21:02:15 +01:00
Sebastiaan Janssen ade3c4ee6c Merge pull request #1732 from rsoeteman/U4-9493
U4 9493
2017-02-09 14:26:44 +01:00
Sebastiaan Janssen bb31eb7c35 Emit only relative paths for files in a package 2017-02-08 16:19:47 +01:00
Sebastiaan Janssen 7648c76f6f List full path of each file installed 2017-02-08 14:45:07 +01:00
Sebastiaan Janssen 308ed3bba7 Removed files that are not found on disk don't get updated to list their full path 2017-02-08 14:44:48 +01:00
Sebastiaan Janssen 7bcb2b11f6 Makes the (un)install summary accessible to events 2017-02-08 14:43:59 +01:00
Emil Wangaa c36000c4dd Adds create container endpoint for creation folders for scripts, partialviews, partialviewmacros 2017-02-08 08:52:36 +01:00
Mads Rasmussen 999de5ae1d add client side logic for partial view macros folder creation 2017-02-08 08:52:36 +01:00
Mads Rasmussen 00982d4278 add client side logic for partial views folder creation 2017-02-08 08:46:10 +01:00
Mads Rasmussen cda47ed536 add client side logic for scripts folder creation 2017-02-08 08:45:23 +01:00
Claus 925c343a30 U4-9351 Add events for package install/uninstall 2017-02-07 10:00:34 +01:00
Richard Soeteman a3c7b1fcfe Fixed issue U4-9494 2017-02-06 11:51:44 +01:00
Richard Soeteman 02785eea42 Fixed issue U4-9493 2017-02-06 11:26:58 +01:00
Shannon 32d757b13c Updates the ContentRepository to not fetch the published version info in the same query when returning a collection of documents, instead we fetch the published version information in a separate query 2017-02-02 17:05:33 +11:00
Shannon 86b2dac231 Needed to change the 2nd query to use a new PagingSqlQuery object due to the way that < SQL 2012 formats it's paging query which is just not compatible with how we were parsing the queries for properties... i don't actually think it was working for a long tme. 2017-01-31 17:20:44 +11:00
Shannon 2f67a485aa Ensures that the sqlIds query is used for paging when processing members/media 2017-01-31 14:52:48 +11:00
Shannon f192f241cf U4-9450 Paged data queries return all property data in the entire database, not just for the paged subset 2017-01-31 13:48:47 +11:00
46 changed files with 1050 additions and 265 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
# Usage: on line 2 put the release version, on line 3 put the version comment (example: beta)
7.6.0
alpha060
alpha061
+1 -1
View File
@@ -12,4 +12,4 @@ using System.Resources;
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("7.6.0")]
[assembly: AssemblyInformationalVersion("7.6.0-alpha060")]
[assembly: AssemblyInformationalVersion("7.6.0-alpha061")]
@@ -24,7 +24,7 @@ namespace Umbraco.Core.Configuration
/// Gets the version comment (like beta or RC).
/// </summary>
/// <value>The version comment.</value>
public static string CurrentComment { get { return "alpha060"; } }
public static string CurrentComment { get { return "alpha061"; } }
// Get the version of the umbraco.dll by looking at a class in that dll
// Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx
@@ -23,6 +23,11 @@ namespace Umbraco.Core.Events
{
get { return _packageMetaData; }
}
public IEnumerable<TEntity> InstallationSummary
{
get { return EventObject; }
}
public bool Equals(ImportPackageEventArgs<TEntity> other)
{
@@ -0,0 +1,31 @@
using System.Collections.Generic;
using Umbraco.Core.Packaging.Models;
namespace Umbraco.Core.Events
{
internal class UninstallPackageEventArgs<TEntity> : CancellableObjectEventArgs<IEnumerable<TEntity>>
{
private readonly MetaData _packageMetaData;
public UninstallPackageEventArgs(TEntity eventObject, bool canCancel)
: base(new[] { eventObject }, canCancel)
{
}
public UninstallPackageEventArgs(TEntity eventObject, MetaData packageMetaData)
: base(new[] { eventObject })
{
_packageMetaData = packageMetaData;
}
public MetaData PackageMetaData
{
get { return _packageMetaData; }
}
public IEnumerable<TEntity> UninstallationSummary
{
get { return EventObject; }
}
}
}
-19
View File
@@ -109,24 +109,5 @@ namespace Umbraco.Core.IO
var wrapped2 = Wrapped as IFileSystem2;
return wrapped2 == null ? Wrapped.GetSize(path) : wrapped2.GetSize(path);
}
// explicitely implementing - not breaking
bool IFileSystem2.CanAddPhysical
{
get
{
var wrapped2 = Wrapped as IFileSystem2;
return wrapped2 != null && wrapped2.CanAddPhysical;
}
}
// explicitely implementing - not breaking
void IFileSystem2.AddFile(string path, string physicalPath, bool overrideIfExists, bool copy)
{
var wrapped2 = Wrapped as IFileSystem2;
if (wrapped2 == null)
throw new NotSupportedException();
wrapped2.AddFile(path, physicalPath, overrideIfExists, copy);
}
}
}
-4
View File
@@ -44,10 +44,6 @@ namespace Umbraco.Core.IO
{
long GetSize(string path);
bool CanAddPhysical { get; }
void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false);
// TODO: implement these
//
//void CreateDirectory(string path);
+50 -3
View File
@@ -4,11 +4,11 @@ using System.Globalization;
using System.Reflection;
using System.IO;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Text.RegularExpressions;
using System.Web.Hosting;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
namespace Umbraco.Core.IO
{
@@ -351,7 +351,54 @@ namespace Umbraco.Core.IO
writer.Write(contents);
}
}
}
}
/// <summary>
/// Checks if a given path is a full path including drive letter
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
// From: http://stackoverflow.com/a/35046453/5018
internal static bool IsFullPath(this string path)
{
return string.IsNullOrWhiteSpace(path) == false
&& path.IndexOfAny(Path.GetInvalidPathChars().ToArray()) == -1
&& Path.IsPathRooted(path)
&& Path.GetPathRoot(path).Equals(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) == false;
}
/// <summary>
/// Get properly formatted relative path from an existing absolute or relative path
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
internal static string GetRelativePath(this string path)
{
if (path.IsFullPath())
{
var rootDirectory = GetRootDirectorySafe();
var relativePath = path.ToLowerInvariant().Replace(rootDirectory.ToLowerInvariant(), string.Empty);
path = relativePath;
}
return path.EnsurePathIsApplicationRootPrefixed();
}
/// <summary>
/// Ensures that a path has `~/` as prefix
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
internal static string EnsurePathIsApplicationRootPrefixed(this string path)
{
if (path.StartsWith("~/"))
return path;
if (path.StartsWith("/") == false && path.StartsWith("\\") == false)
path = string.Format("/{0}", path);
if (path.StartsWith("~") == false)
path = string.Format("~{0}", path);
return path;
}
}
}
-23
View File
@@ -364,29 +364,6 @@ namespace Umbraco.Core.IO
return file.Exists ? file.Length : -1;
}
public bool CanAddPhysical { get { return true; } }
public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false)
{
var fullPath = GetFullPath(path);
if (File.Exists(fullPath))
{
if (overrideIfExists == false)
throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path));
File.Delete(fullPath);
}
var directory = Path.GetDirectoryName(fullPath);
if (directory == null) throw new InvalidOperationException("Could not get directory.");
Directory.CreateDirectory(directory); // ensure it exists
if (copy)
File.Copy(physicalPath, fullPath);
else
File.Move(physicalPath, fullPath);
}
#region Helper Methods
protected virtual void EnsureDirectory(string path)
+2 -41
View File
@@ -33,16 +33,8 @@ namespace Umbraco.Core.IO
{
try
{
var fs2 = _fs as IFileSystem2;
if (fs2 != null && fs2.CanAddPhysical)
{
fs2.AddFile(kvp.Key, _sfs.GetFullPath(kvp.Key)); // overwrite, move
}
else
{
using (var stream = _sfs.OpenFile(kvp.Key))
_fs.AddFile(kvp.Key, stream, true);
}
using (var stream = _sfs.OpenFile(kvp.Key))
_fs.AddFile(kvp.Key, stream, true);
}
catch (Exception e)
{
@@ -290,36 +282,5 @@ namespace Umbraco.Core.IO
if (sf.IsDelete || sf.IsDir) throw new InvalidOperationException("Invalid path.");
return _sfs.GetSize(path);
}
public bool CanAddPhysical { get { return true; } }
public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false)
{
ShadowNode sf;
var normPath = NormPath(path);
if (Nodes.TryGetValue(normPath, out sf) && sf.IsExist && (sf.IsDir || overrideIfExists == false))
throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path));
var parts = normPath.Split('/');
for (var i = 0; i < parts.Length - 1; i++)
{
var dirPath = string.Join("/", parts.Take(i + 1));
ShadowNode sd;
if (Nodes.TryGetValue(dirPath, out sd))
{
if (sd.IsFile) throw new InvalidOperationException("Invalid path.");
if (sd.IsDelete) Nodes[dirPath] = new ShadowNode(false, true);
}
else
{
if (_fs.DirectoryExists(dirPath)) continue;
if (_fs.FileExists(dirPath)) throw new InvalidOperationException("Invalid path.");
Nodes[dirPath] = new ShadowNode(false, true);
}
}
_sfs.AddFile(path, physicalPath, overrideIfExists, copy);
Nodes[normPath] = new ShadowNode(false, false);
}
}
}
-17
View File
@@ -164,22 +164,5 @@ namespace Umbraco.Core.IO
var filesystem2 = filesystem as IFileSystem2;
return filesystem2 == null ? filesystem.GetSize(path) : filesystem2.GetSize(path);
}
public bool CanAddPhysical
{
get
{
var fileSystem2 = FileSystem as IFileSystem2;
return fileSystem2 != null && fileSystem2.CanAddPhysical;
}
}
public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false)
{
var fileSystem2 = FileSystem as IFileSystem2;
if (fileSystem2 == null)
throw new NotSupportedException();
fileSystem2.AddFile(path, physicalPath, overrideIfExists, copy);
}
}
}
@@ -22,6 +22,7 @@ namespace Umbraco.Core.Models.Rdbms
[Column("LoginName")]
[Length(1000)]
[Constraint(Default = "''")]
[Index(IndexTypes.NonClustered, Name = "IX_cmsMember_LoginName")]
public string LoginName { get; set; }
[Column("Password")]
@@ -16,6 +16,7 @@ namespace Umbraco.Core.Models.Rdbms
[Column("parentId")]
[ForeignKey(typeof(NodeDto), Name = "FK_umbracoRelation_umbracoNode")]
[Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelation_parentChildType", ForColumns = "parentId,childId,relType")]
public int ParentId { get; set; }
[Column("childId")]
@@ -29,11 +29,13 @@ namespace Umbraco.Core.Models.Rdbms
public Guid ChildObjectType { get; set; }
[Column("name")]
[Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_name")]
public string Name { get; set; }
[Column("alias")]
[NullSetting(NullSetting = NullSettings.Null)]
[Length(100)]
[Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_alias")]
public string Alias { get; set; }
}
}
@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Umbraco.Core.Models;
namespace Umbraco.Core.Packaging.Models
{
[Serializable]
[DataContract(IsReference = true)]
internal class UninstallationSummary
{
public MetaData MetaData { get; set; }
public IEnumerable<IDataTypeDefinition> DataTypesUninstalled { get; set; }
public IEnumerable<ILanguage> LanguagesUninstalled { get; set; }
public IEnumerable<IDictionaryItem> DictionaryItemsUninstalled { get; set; }
public IEnumerable<IMacro> MacrosUninstalled { get; set; }
public IEnumerable<string> FilesUninstalled { get; set; }
public IEnumerable<ITemplate> TemplatesUninstalled { get; set; }
public IEnumerable<IContentType> ContentTypesUninstalled { get; set; }
public IEnumerable<IFile> StylesheetsUninstalled { get; set; }
public IEnumerable<IContent> ContentUninstalled { get; set; }
public bool PackageUninstalled { get; set; }
}
internal static class UninstallationSummaryExtentions
{
public static UninstallationSummary InitEmpty(this UninstallationSummary summary)
{
summary.ContentUninstalled = new List<IContent>();
summary.ContentTypesUninstalled = new List<IContentType>();
summary.DataTypesUninstalled = new List<IDataTypeDefinition>();
summary.DictionaryItemsUninstalled = new List<IDictionaryItem>();
summary.FilesUninstalled = new List<string>();
summary.LanguagesUninstalled = new List<ILanguage>();
summary.MacrosUninstalled = new List<IMacro>();
summary.MetaData = new MetaData();
summary.TemplatesUninstalled = new List<ITemplate>();
summary.PackageUninstalled = false;
return summary;
}
}
}
@@ -27,7 +27,20 @@ namespace Umbraco.Core.Persistence.Factories
#region Implementation of IEntityFactory<IContent,DocumentDto>
public static IContent BuildEntity(DocumentDto dto, IContentType contentType)
/// <summary>
/// Builds a IContent item from the dto(s) and content type
/// </summary>
/// <param name="dto">
/// This DTO can contain all of the information to build an IContent item, however in cases where multiple entities are being built,
/// a separate <see cref="DocumentPublishedReadOnlyDto"/> publishedDto entity will be supplied in place of the <see cref="DocumentDto"/>'s own
/// ResultColumn DocumentPublishedReadOnlyDto
/// </param>
/// <param name="contentType"></param>
/// <param name="publishedDto">
/// When querying for multiple content items the main DTO will not contain the ResultColumn DocumentPublishedReadOnlyDto and a separate publishedDto instance will be supplied
/// </param>
/// <returns></returns>
public static IContent BuildEntity(DocumentDto dto, IContentType contentType, DocumentPublishedReadOnlyDto publishedDto = null)
{
var content = new Content(dto.Text, dto.ContentVersionDto.ContentDto.NodeDto.ParentId, contentType);
@@ -52,8 +65,13 @@ namespace Umbraco.Core.Persistence.Factories
content.ExpireDate = dto.ExpiresDate.HasValue ? dto.ExpiresDate.Value : (DateTime?)null;
content.ReleaseDate = dto.ReleaseDate.HasValue ? dto.ReleaseDate.Value : (DateTime?)null;
content.Version = dto.ContentVersionDto.VersionId;
content.PublishedState = dto.Published ? PublishedState.Published : PublishedState.Unpublished;
content.PublishedVersionGuid = dto.DocumentPublishedReadOnlyDto == null ? default(Guid) : dto.DocumentPublishedReadOnlyDto.VersionId;
//Check if the publishedDto has been supplied, if not the use the dto's own DocumentPublishedReadOnlyDto value
content.PublishedVersionGuid = publishedDto == null
? (dto.DocumentPublishedReadOnlyDto == null ? default(Guid) : dto.DocumentPublishedReadOnlyDto.VersionId)
: publishedDto.VersionId;
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
@@ -0,0 +1,42 @@
using System.Linq;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero
{
[Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)]
public class AddIndexToCmsMemberLoginName : MigrationBase
{
public AddIndexToCmsMemberLoginName(ISqlSyntaxProvider sqlSyntax, ILogger logger)
: base(sqlSyntax, logger)
{ }
public override void Up()
{
var dbIndexes = SqlSyntax.GetDefinedIndexes(Context.Database)
.Select(x => new DbIndexDefinition()
{
TableName = x.Item1,
IndexName = x.Item2,
ColumnName = x.Item3,
IsUnique = x.Item4
}).ToArray();
//make sure it doesn't already exist
if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsMember_LoginName")) == false)
{
Create.Index("IX_cmsMember_LoginName").OnTable("cmsMember")
.OnColumn("LoginName")
.Ascending()
.WithOptions()
.NonClustered();
}
}
public override void Down()
{
Delete.Index("IX_cmsMember_LoginName").OnTable("cmsMember");
}
}
}
@@ -14,14 +14,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero
public override void Up()
{
var dbIndexes = SqlSyntax.GetDefinedIndexes(Context.Database)
.Select(x => new DbIndexDefinition()
{
TableName = x.Item1,
IndexName = x.Item2,
ColumnName = x.Item3,
IsUnique = x.Item4
}).ToArray();
var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database);
//make sure it doesn't already exist
if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoNodePath")) == false)
@@ -0,0 +1,91 @@
using System;
using System.Linq;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero
{
[Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)]
public class AddIndexesToUmbracoRelationTables : MigrationBase
{
public AddIndexesToUmbracoRelationTables(ISqlSyntaxProvider sqlSyntax, ILogger logger)
: base(sqlSyntax, logger)
{ }
public override void Up()
{
var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database).ToArray();
//make sure it doesn't already exist
if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoRelation_parentChildType")) == false)
{
//This will remove any corrupt/duplicate data in the relation table before the index is applied
//Ensure this executes in a defered block which will be done inside of the migration transaction
this.Execute.Code(database =>
{
//We need to check if this index has corrupted data and then clear that data
var duplicates = database.Fetch<dynamic>("SELECT parentId,childId,relType FROM umbracoRelation GROUP BY parentId,childId,relType HAVING COUNT(*) > 1");
if (duplicates.Count > 0)
{
//need to fix this there cannot be duplicates so we'll take the latest entries, it's really not going to matter though
foreach (var duplicate in duplicates)
{
var ids = database.Fetch<int>("SELECT id FROM umbracoRelation WHERE parentId=@parentId AND childId=@childId AND relType=@relType ORDER BY datetime DESC",
new { parentId = duplicate.parentId, childId = duplicate.childId, relType = duplicate.relType });
if (ids.Count == 1)
{
//this is just a safety check, this should absolutely never happen
throw new InvalidOperationException("Duplicates were detected but could not be discovered");
}
//delete the others
ids = ids.Skip(0).ToList();
//iterate in groups of 2000 to avoid the max sql parameter limit
foreach (var idGroup in ids.InGroupsOf(2000))
{
database.Execute("DELETE FROM umbracoRelation WHERE id IN (@ids)", new { ids = idGroup });
}
}
}
return "";
});
//unique index to prevent duplicates - and for better perf
Create.Index("IX_umbracoRelation_parentChildType").OnTable("umbracoRelation")
.OnColumn("parentId").Ascending()
.OnColumn("childId").Ascending()
.OnColumn("relType").Ascending()
.WithOptions()
.Unique();
}
//need indexes on alias and name for relation type since these are queried against
//make sure it doesn't already exist
if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoRelationType_alias")) == false)
{
Create.Index("IX_umbracoRelationType_alias").OnTable("umbracoRelationType")
.OnColumn("alias")
.Ascending()
.WithOptions()
.NonClustered();
}
if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoRelationType_name")) == false)
{
Create.Index("IX_umbracoRelationType_name").OnTable("umbracoRelationType")
.OnColumn("name")
.Ascending()
.WithOptions()
.NonClustered();
}
}
public override void Down()
{
Delete.Index("IX_umbracoNodePath").OnTable("umbracoNode");
}
}
}
@@ -2,8 +2,30 @@ namespace Umbraco.Core.Persistence.Repositories
{
internal enum BaseQueryType
{
Full,
/// <summary>
/// A query to return all information for a single item
/// </summary>
/// <remarks>
/// In some cases this will be the same as <see cref="FullMultiple"/>
/// </remarks>
FullSingle,
/// <summary>
/// A query to return all information for multiple items
/// </summary>
/// <remarks>
/// In some cases this will be the same as <see cref="FullSingle"/>
/// </remarks>
FullMultiple,
/// <summary>
/// A query to return the ids for items
/// </summary>
Ids,
/// <summary>
/// A query to return the count for items
/// </summary>
Count
}
}
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Xml;
@@ -53,7 +54,7 @@ namespace Umbraco.Core.Persistence.Repositories
protected override IContent PerformGet(int id)
{
var sql = GetBaseQuery(false)
var sql = GetBaseQuery(BaseQueryType.FullSingle)
.Where(GetBaseWhereClause(), new { Id = id })
.Where<DocumentDto>(x => x.Newest, SqlSyntax)
.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
@@ -81,15 +82,15 @@ namespace Umbraco.Core.Persistence.Repositories
return s;
};
var sqlBaseFull = GetBaseQuery(BaseQueryType.Full);
var sqlBaseFull = GetBaseQuery(BaseQueryType.FullMultiple);
var sqlBaseIds = GetBaseQuery(BaseQueryType.Ids);
return ProcessQuery(translate(sqlBaseFull), translate(sqlBaseIds));
return ProcessQuery(translate(sqlBaseFull), new PagingSqlQuery(translate(sqlBaseIds)));
}
protected override IEnumerable<IContent> PerformGetByQuery(IQuery<IContent> query)
{
var sqlBaseFull = GetBaseQuery(BaseQueryType.Full);
var sqlBaseFull = GetBaseQuery(BaseQueryType.FullMultiple);
var sqlBaseIds = GetBaseQuery(BaseQueryType.Ids);
Func<SqlTranslator<IContent>, Sql> translate = (translator) =>
@@ -103,13 +104,25 @@ namespace Umbraco.Core.Persistence.Repositories
var translatorFull = new SqlTranslator<IContent>(sqlBaseFull, query);
var translatorIds = new SqlTranslator<IContent>(sqlBaseIds, query);
return ProcessQuery(translate(translatorFull), translate(translatorIds));
return ProcessQuery(translate(translatorFull), new PagingSqlQuery(translate(translatorIds)));
}
#endregion
#region Overrides of PetaPocoRepositoryBase<IContent>
/// <summary>
/// Returns the base query to return Content
/// </summary>
/// <param name="queryType"></param>
/// <returns></returns>
/// <remarks>
/// Content queries will differ depending on what needs to be returned:
/// * FullSingle: When querying for a single document, this will include the Outer join to fetch the content item's published version info
/// * FullMultiple: When querying for multiple documents, this will exclude the Outer join to fetch the content item's published version info - this info would need to be fetched separately
/// * Ids: This would essentially be the same as FullMultiple however the columns specified will only return the Ids for the documents
/// * Count: A query to return the count for documents
/// </remarks>
protected override Sql GetBaseQuery(BaseQueryType queryType)
{
var sql = new Sql();
@@ -122,14 +135,14 @@ namespace Umbraco.Core.Persistence.Repositories
.InnerJoin<NodeDto>(SqlSyntax)
.On<ContentDto, NodeDto>(SqlSyntax, left => left.NodeId, right => right.NodeId);
if (queryType == BaseQueryType.Full)
if (queryType == BaseQueryType.FullSingle)
{
//The only reason we apply this left outer join is to be able to pull back the DocumentPublishedReadOnlyDto
//information with the entire data set, so basically this will get both the latest document and also it's published
//version if it has one. When performing a count or when just retrieving Ids like in paging, this is unecessary
//version if it has one. When performing a count or when retrieving Ids like in paging, this is unecessary
//and causes huge performance overhead for the SQL server, especially when sorting the result.
//To fix this perf overhead we'd need another index on :
// CREATE NON CLUSTERED INDEX ON cmsDocument.node + cmsDocument.published
//We also don't include this outer join when querying for multiple entities since it is much faster to fetch this information
//in a separate query. For a single entity this is ok.
var sqlx = string.Format("LEFT OUTER JOIN {0} {1} ON ({1}.{2}={0}.{2} AND {1}.{3}=1)",
SqlSyntax.GetQuotedTableName("cmsDocument"),
@@ -151,7 +164,7 @@ namespace Umbraco.Core.Persistence.Repositories
protected override Sql GetBaseQuery(bool isCount)
{
return GetBaseQuery(isCount ? BaseQueryType.Count : BaseQueryType.Full);
return GetBaseQuery(isCount ? BaseQueryType.Count : BaseQueryType.FullSingle);
}
protected override string GetBaseWhereClause()
@@ -222,10 +235,10 @@ namespace Umbraco.Core.Persistence.Repositories
while (true)
{
// get the next group of nodes
var sqlFull = translate(baseId, GetBaseQuery(BaseQueryType.Full));
var sqlFull = translate(baseId, GetBaseQuery(BaseQueryType.FullMultiple));
var sqlIds = translate(baseId, GetBaseQuery(BaseQueryType.Ids));
var xmlItems = ProcessQuery(SqlSyntax.SelectTop(sqlFull, groupSize), SqlSyntax.SelectTop(sqlIds, groupSize))
var xmlItems = ProcessQuery(SqlSyntax.SelectTop(sqlFull, groupSize), new PagingSqlQuery(SqlSyntax.SelectTop(sqlIds, groupSize)))
.Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() })
.ToList();
@@ -245,7 +258,7 @@ namespace Umbraco.Core.Persistence.Repositories
Logger.Error<MediaRepository>("Could not rebuild XML for nodeId=" + xmlItem.NodeId, e);
}
}
baseId = xmlItems.Last().NodeId;
baseId = xmlItems[xmlItems.Count - 1].NodeId;
}
}
@@ -257,15 +270,15 @@ namespace Umbraco.Core.Persistence.Repositories
.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
};
var sqlFull = translate(GetBaseQuery(BaseQueryType.Full));
var sqlFull = translate(GetBaseQuery(BaseQueryType.FullMultiple));
var sqlIds = translate(GetBaseQuery(BaseQueryType.Ids));
return ProcessQuery(sqlFull, sqlIds, true);
return ProcessQuery(sqlFull, new PagingSqlQuery(sqlIds), true);
}
public override IContent GetByVersion(Guid versionId)
{
var sql = GetBaseQuery(false);
var sql = GetBaseQuery(BaseQueryType.FullSingle);
sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId });
sql.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
@@ -675,12 +688,12 @@ namespace Umbraco.Core.Persistence.Repositories
// ORDER BY substring(path, 1, len(path) - charindex(',', reverse(path))), sortOrder
// but that's probably an overkill - sorting by level,sortOrder should be enough
var sqlFull = GetBaseQuery(BaseQueryType.Full);
var sqlFull = GetBaseQuery(BaseQueryType.FullMultiple);
var translatorFull = new SqlTranslator<IContent>(sqlFull, query);
var sqlIds = GetBaseQuery(BaseQueryType.Ids);
var translatorIds = new SqlTranslator<IContent>(sqlIds, query);
return ProcessQuery(translate(translatorFull), translate(translatorIds), true);
return ProcessQuery(translate(translatorFull), new PagingSqlQuery(translate(translatorIds)), true);
}
/// <summary>
@@ -860,7 +873,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder",
return GetPagedResultsByQuery<DocumentDto>(query, pageIndex, pageSize, out totalRecords,
new Tuple<string, string>("cmsDocument", "nodeId"),
(sqlFull, sqlIds) => ProcessQuery(sqlFull, sqlIds), orderBy, orderDirection, orderBySystemField,
(sqlFull, pagingSqlQuery) => ProcessQuery(sqlFull, pagingSqlQuery), orderBy, orderDirection, orderBySystemField,
filterCallback);
}
@@ -890,23 +903,58 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder",
return base.GetDatabaseFieldNameForOrderBy(orderBy);
}
/// <summary>
/// This is the underlying method that processes most queries for this repository
/// </summary>
/// <param name="sqlFull">
/// The full SQL with the outer join to return all data required to create an IContent
/// The FullMultiple SQL without the outer join to return all data required to create an IContent excluding it's published state data which this will query separately
/// </param>
/// <param name="sqlIds">
/// <param name="pagingSqlQuery">
/// The Id SQL without the outer join to just return all document ids - used to process the properties for the content item
/// </param>
/// <param name="withCache"></param>
/// <returns></returns>
private IEnumerable<IContent> ProcessQuery(Sql sqlFull, Sql sqlIds, bool withCache = false)
private IEnumerable<IContent> ProcessQuery(Sql sqlFull, PagingSqlQuery pagingSqlQuery, bool withCache = false)
{
// fetch returns a list so it's ok to iterate it in this method
var dtos = Database.Fetch<DocumentDto, ContentVersionDto, ContentDto, NodeDto, DocumentPublishedReadOnlyDto>(sqlFull);
var dtos = Database.Fetch<DocumentDto, ContentVersionDto, ContentDto, NodeDto>(sqlFull);
if (dtos.Count == 0) return Enumerable.Empty<IContent>();
//Go and get all of the published version data separately for this data, this is because when we are querying
//for multiple content items we don't include the outer join to fetch this data in the same query because
//it is insanely slow. Instead we just fetch the published version data separately in one query.
//we need to parse the original SQL statement and reduce the columns to just cmsDocument.nodeId so that we can use
// the statement to go get the published data for all of the items by using an inner join
var parsedOriginalSql = "SELECT cmsDocument.nodeId " + sqlFull.SQL.Substring(sqlFull.SQL.IndexOf("FROM", StringComparison.Ordinal));
//now remove everything from an Orderby clause and beyond
if (parsedOriginalSql.InvariantContains("ORDER BY "))
{
parsedOriginalSql = parsedOriginalSql.Substring(0, parsedOriginalSql.LastIndexOf("ORDER BY ", StringComparison.Ordinal));
}
var publishedSql = new Sql(@"SELECT *
FROM cmsDocument AS doc2
INNER JOIN
(" + parsedOriginalSql + @") as docData
ON doc2.nodeId = docData.nodeId
WHERE doc2.published = 1
ORDER BY doc2.nodeId
", sqlFull.Arguments);
//go and get the published version data, we do a Query here and not a Fetch so we are
//not allocating a whole list to memory just to allocate another list in memory since
//we are assigning this data to a keyed collection for fast lookup below
var publishedData = Database.Query<DocumentPublishedReadOnlyDto>(publishedSql);
var publishedDataCollection = new DocumentPublishedReadOnlyDtoCollection();
foreach (var publishedDto in publishedData)
{
//double check that there's no corrupt db data, there should only be a single published item
if (publishedDataCollection.Contains(publishedDto.NodeId) == false)
publishedDataCollection.Add(publishedDto);
}
var content = new IContent[dtos.Count];
var defs = new List<DocumentDefinition>();
@@ -920,6 +968,8 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder",
for (var i = 0; i < dtos.Count; i++)
{
var dto = dtos[i];
DocumentPublishedReadOnlyDto publishedDto;
publishedDataCollection.TryGetValue(dto.NodeId, out publishedDto);
// if the cache contains the published version, use it
if (withCache)
@@ -947,7 +997,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder",
contentTypes[dto.ContentVersionDto.ContentDto.ContentTypeId] = contentType;
}
content[i] = ContentFactory.BuildEntity(dto, contentType);
content[i] = ContentFactory.BuildEntity(dto, contentType, publishedDto);
// need template
if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0)
@@ -968,7 +1018,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder",
.ToDictionary(x => x.Id, x => x);
// load all properties for all documents from database in 1 query
var propertyData = GetPropertyCollection(sqlIds, defs);
var propertyData = GetPropertyCollection(pagingSqlQuery, defs);
// assign
var dtoIndex = 0;
@@ -1056,7 +1106,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder",
return currentName;
}
/// <summary>
/// Dispose disposable properties
/// </summary>
@@ -1071,5 +1121,26 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder",
_contentPreviewRepository.Dispose();
_contentXmlRepository.Dispose();
}
/// <summary>
/// A keyed collection for fast lookup when retrieving a separate list of published version data
/// </summary>
private class DocumentPublishedReadOnlyDtoCollection : KeyedCollection<int, DocumentPublishedReadOnlyDto>
{
protected override int GetKeyForItem(DocumentPublishedReadOnlyDto item)
{
return item.NodeId;
}
public bool TryGetValue(int key, out DocumentPublishedReadOnlyDto val)
{
if (Dictionary == null)
{
val = null;
return false;
}
return Dictionary.TryGetValue(key, out val);
}
}
}
}
@@ -68,7 +68,7 @@ namespace Umbraco.Core.Persistence.Repositories
sql.Where("umbracoNode.id in (@ids)", new { ids = ids });
}
return ProcessQuery(sql);
return ProcessQuery(sql, new PagingSqlQuery(sql));
}
protected override IEnumerable<IMedia> PerformGetByQuery(IQuery<IMedia> query)
@@ -78,7 +78,7 @@ namespace Umbraco.Core.Persistence.Repositories
var sql = translator.Translate()
.OrderBy<NodeDto>(x => x.SortOrder, SqlSyntax);
return ProcessQuery(sql);
return ProcessQuery(sql, new PagingSqlQuery(sql));
}
#endregion
@@ -100,7 +100,7 @@ namespace Umbraco.Core.Persistence.Repositories
protected override Sql GetBaseQuery(bool isCount)
{
return GetBaseQuery(isCount ? BaseQueryType.Count : BaseQueryType.Full);
return GetBaseQuery(isCount ? BaseQueryType.Count : BaseQueryType.FullSingle);
}
protected override string GetBaseWhereClause()
@@ -143,13 +143,24 @@ namespace Umbraco.Core.Persistence.Repositories
var sql = GetBaseQuery(false)
.Where(GetBaseWhereClause(), new { Id = id })
.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
return ProcessQuery(sql, true);
return ProcessQuery(sql, new PagingSqlQuery(sql), true);
}
private IEnumerable<IMedia> ProcessQuery(Sql sql, bool withCache = false)
/// <summary>
/// This is the underlying method that processes most queries for this repository
/// </summary>
/// <param name="sqlFull">
/// The full SQL to select all media data
/// </param>
/// <param name="pagingSqlQuery">
/// The Id SQL to just return all media ids - used to process the properties for the media item
/// </param>
/// <param name="withCache"></param>
/// <returns></returns>
private IEnumerable<IMedia> ProcessQuery(Sql sqlFull, PagingSqlQuery pagingSqlQuery, bool withCache = false)
{
// fetch returns a list so it's ok to iterate it in this method
var dtos = Database.Fetch<ContentVersionDto, ContentDto, NodeDto>(sql);
var dtos = Database.Fetch<ContentVersionDto, ContentDto, NodeDto>(sqlFull);
var content = new IMedia[dtos.Count];
var defs = new List<DocumentDefinition>();
@@ -200,7 +211,7 @@ namespace Umbraco.Core.Persistence.Repositories
}
// load all properties for all documents from database in 1 query
var propertyData = GetPropertyCollection(sql, defs);
var propertyData = GetPropertyCollection(pagingSqlQuery, defs);
// assign
var dtoIndex = 0;
@@ -257,7 +268,8 @@ namespace Umbraco.Core.Persistence.Repositories
query = query
.Where<NodeDto>(x => x.NodeId > baseId, SqlSyntax)
.OrderBy<NodeDto>(x => x.NodeId, SqlSyntax);
var xmlItems = ProcessQuery(SqlSyntax.SelectTop(query, groupSize))
var sql = SqlSyntax.SelectTop(query, groupSize);
var xmlItems = ProcessQuery(sql, new PagingSqlQuery(sql))
.Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() })
.ToList();
@@ -505,7 +517,7 @@ namespace Umbraco.Core.Persistence.Repositories
return GetPagedResultsByQuery<ContentVersionDto>(query, pageIndex, pageSize, out totalRecords,
new Tuple<string, string>("cmsContentVersion", "contentId"),
(sqlFull, sqlIds) => ProcessQuery(sqlFull), orderBy, orderDirection, orderBySystemField,
(sqlFull, pagingSqlQuery) => ProcessQuery(sqlFull, pagingSqlQuery), orderBy, orderDirection, orderBySystemField,
filterCallback);
}
@@ -513,7 +525,7 @@ namespace Umbraco.Core.Persistence.Repositories
/// <summary>
/// Private method to create a media object from a ContentDto
/// </summary>
/// <param name="d"></param>
/// <param name="dto"></param>
/// <param name="versionId"></param>
/// <param name="docSql"></param>
/// <returns></returns>
@@ -525,7 +537,7 @@ namespace Umbraco.Core.Persistence.Repositories
var docDef = new DocumentDefinition(dto.NodeId, versionId, media.UpdateDate, media.CreateDate, contentType);
var properties = GetPropertyCollection(docSql, new[] { docDef });
var properties = GetPropertyCollection(new PagingSqlQuery(docSql), new[] { docDef });
media.Properties = properties[dto.NodeId];
@@ -68,7 +68,7 @@ namespace Umbraco.Core.Persistence.Repositories
sql.Where("umbracoNode.id in (@ids)", new { ids = ids });
}
return ProcessQuery(sql);
return ProcessQuery(sql, new PagingSqlQuery(sql));
}
@@ -90,7 +90,7 @@ namespace Umbraco.Core.Persistence.Repositories
baseQuery.Append(new Sql("WHERE umbracoNode.id IN (" + sql.SQL + ")", sql.Arguments))
.OrderBy<NodeDto>(x => x.SortOrder);
return ProcessQuery(baseQuery);
return ProcessQuery(baseQuery, new PagingSqlQuery(baseQuery));
}
else
{
@@ -98,7 +98,7 @@ namespace Umbraco.Core.Persistence.Repositories
var sql = translator.Translate()
.OrderBy<NodeDto>(x => x.SortOrder);
return ProcessQuery(sql);
return ProcessQuery(sql, new PagingSqlQuery(sql));
}
}
@@ -129,7 +129,7 @@ namespace Umbraco.Core.Persistence.Repositories
protected override Sql GetBaseQuery(bool isCount)
{
return GetBaseQuery(isCount ? BaseQueryType.Count : BaseQueryType.Full);
return GetBaseQuery(isCount ? BaseQueryType.Count : BaseQueryType.FullSingle);
}
protected override string GetBaseWhereClause()
@@ -385,7 +385,7 @@ namespace Umbraco.Core.Persistence.Repositories
var sql = GetBaseQuery(false)
.Where(GetBaseWhereClause(), new { Id = id })
.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
return ProcessQuery(sql, true);
return ProcessQuery(sql, new PagingSqlQuery(sql), true);
}
public void RebuildXmlStructures(Func<IMember, XElement> serializer, int groupSize = 200, IEnumerable<int> contentTypeIds = null)
@@ -408,7 +408,8 @@ namespace Umbraco.Core.Persistence.Repositories
query = query
.Where<NodeDto>(x => x.NodeId > baseId)
.OrderBy<NodeDto>(x => x.NodeId, SqlSyntax);
var xmlItems = ProcessQuery(SqlSyntax.SelectTop(query, groupSize))
var sql = SqlSyntax.SelectTop(query, groupSize);
var xmlItems = ProcessQuery(sql, new PagingSqlQuery(sql))
.Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() })
.ToList();
@@ -449,7 +450,7 @@ namespace Umbraco.Core.Persistence.Repositories
var factory = new MemberFactory(memberType, NodeObjectTypeId, dto.NodeId);
var media = factory.BuildEntity(dto);
var properties = GetPropertyCollection(sql, new[] { new DocumentDefinition(dto.NodeId, dto.ContentVersionDto.VersionId, media.UpdateDate, media.CreateDate, memberType) });
var properties = GetPropertyCollection(new PagingSqlQuery(sql), new[] { new DocumentDefinition(dto.NodeId, dto.ContentVersionDto.VersionId, media.UpdateDate, media.CreateDate, memberType) });
media.Properties = properties[dto.NodeId];
@@ -540,7 +541,7 @@ namespace Umbraco.Core.Persistence.Repositories
.OrderByDescending<ContentVersionDto>(x => x.VersionDate)
.OrderBy<NodeDto>(x => x.SortOrder);
return ProcessQuery(sql);
return ProcessQuery(sql, new PagingSqlQuery(sql));
}
@@ -601,7 +602,7 @@ namespace Umbraco.Core.Persistence.Repositories
return GetPagedResultsByQuery<MemberDto>(query, pageIndex, pageSize, out totalRecords,
new Tuple<string, string>("cmsMember", "nodeId"),
(sqlFull, sqlIds) => ProcessQuery(sqlFull), orderBy, orderDirection, orderBySystemField,
(sqlFull, sqlIds) => ProcessQuery(sqlFull, sqlIds), orderBy, orderDirection, orderBySystemField,
filterCallback);
}
@@ -641,10 +642,21 @@ namespace Umbraco.Core.Persistence.Repositories
return base.GetEntityPropertyNameForOrderBy(orderBy);
}
private IEnumerable<IMember> ProcessQuery(Sql sql, bool withCache = false)
/// <summary>
/// This is the underlying method that processes most queries for this repository
/// </summary>
/// <param name="sqlFull">
/// The full SQL to select all member data
/// </param>
/// <param name="pagingSqlQuery">
/// The Id SQL to just return all member ids - used to process the properties for the member item
/// </param>
/// <param name="withCache"></param>
/// <returns></returns>
private IEnumerable<IMember> ProcessQuery(Sql sqlFull, PagingSqlQuery pagingSqlQuery, bool withCache = false)
{
// fetch returns a list so it's ok to iterate it in this method
var dtos = Database.Fetch<MemberDto, ContentVersionDto, ContentDto, NodeDto>(sql);
var dtos = Database.Fetch<MemberDto, ContentVersionDto, ContentDto, NodeDto>(sqlFull);
var content = new IMember[dtos.Count];
var defs = new List<DocumentDefinition>();
@@ -681,7 +693,7 @@ namespace Umbraco.Core.Persistence.Repositories
}
// load all properties for all documents from database in 1 query
var propertyData = GetPropertyCollection(sql, defs);
var propertyData = GetPropertyCollection(pagingSqlQuery, defs);
// assign
var dtoIndex = 0;
@@ -432,7 +432,7 @@ namespace Umbraco.Core.Persistence.Repositories
/// <exception cref="System.ArgumentNullException">orderBy</exception>
protected IEnumerable<TEntity> GetPagedResultsByQuery<TDto>(IQuery<TEntity> query, long pageIndex, int pageSize, out long totalRecords,
Tuple<string, string> nodeIdSelect,
Func<Sql, Sql, IEnumerable<TEntity>> processQuery,
Func<Sql, PagingSqlQuery<TDto>, IEnumerable<TEntity>> processQuery,
string orderBy,
Direction orderDirection,
bool orderBySystemField,
@@ -443,7 +443,7 @@ namespace Umbraco.Core.Persistence.Repositories
// Get base query for returning IDs
var sqlBaseIds = GetBaseQuery(BaseQueryType.Ids);
// Get base query for returning all data
var sqlBaseFull = GetBaseQuery(BaseQueryType.Full);
var sqlBaseFull = GetBaseQuery(BaseQueryType.FullMultiple);
if (query == null) query = new Query<TEntity>();
var translatorIds = new SqlTranslator<TEntity>(sqlBaseIds, query);
@@ -466,7 +466,7 @@ namespace Umbraco.Core.Persistence.Repositories
// the pageResult, then the GetAll will actually return ALL records in the db.
if (pagedResult.Items.Any())
{
//Crete the inner paged query that was used above to get the paged result, we'll use that as the inner sub query
//Create the inner paged query that was used above to get the paged result, we'll use that as the inner sub query
var args = sqlNodeIdsWithSort.Arguments;
string sqlStringCount, sqlStringPage;
Database.BuildPageQueries<TDto>(pageIndex * pageSize, pageSize, sqlNodeIdsWithSort.SQL, ref args, out sqlStringCount, out sqlStringPage);
@@ -486,8 +486,8 @@ namespace Umbraco.Core.Persistence.Repositories
var fullQuery = GetSortedSqlForPagedResults(
GetFilteredSqlForPagedResults(fullQueryWithPagedInnerJoin, defaultFilter),
orderDirection, orderBy, orderBySystemField, nodeIdSelect);
return processQuery(fullQuery, sqlNodeIdsWithSort);
return processQuery(fullQuery, new PagingSqlQuery<TDto>(Database, sqlNodeIdsWithSort, pageIndex, pageSize));
}
else
{
@@ -497,18 +497,47 @@ namespace Umbraco.Core.Persistence.Repositories
return result;
}
/// <summary>
/// Gets the property collection for a non-paged query
/// </summary>
/// <param name="sql"></param>
/// <param name="documentDefs"></param>
/// <returns></returns>
protected IDictionary<int, PropertyCollection> GetPropertyCollection(
Sql docSql,
Sql sql,
IReadOnlyCollection<DocumentDefinition> documentDefs)
{
return GetPropertyCollection(new PagingSqlQuery(sql), documentDefs);
}
/// <summary>
/// Gets the property collection for a query
/// </summary>
/// <param name="pagingSqlQuery"></param>
/// <param name="documentDefs"></param>
/// <returns></returns>
protected IDictionary<int, PropertyCollection> GetPropertyCollection(
PagingSqlQuery pagingSqlQuery,
IReadOnlyCollection<DocumentDefinition> documentDefs)
{
if (documentDefs.Count == 0) return new Dictionary<int, PropertyCollection>();
//initialize to the query passed in
var docSql = pagingSqlQuery.PrePagedSql;
//we need to parse the original SQL statement and reduce the columns to just cmsContent.nodeId, cmsContentVersion.VersionId so that we can use
// the statement to go get the property data for all of the items by using an inner join
var parsedOriginalSql = "SELECT {0} " + docSql.SQL.Substring(docSql.SQL.IndexOf("FROM", StringComparison.Ordinal));
//now remove everything from an Orderby clause and beyond
if (parsedOriginalSql.InvariantContains("ORDER BY "))
if (pagingSqlQuery.HasPaging)
{
//if this is a paged query, build the paged query with the custom column substitution, then re-assign
docSql = pagingSqlQuery.BuildPagedQuery("{0}");
parsedOriginalSql = docSql.SQL;
}
else if (parsedOriginalSql.InvariantContains("ORDER BY "))
{
//now remove everything from an Orderby clause and beyond if this is unpaged data
parsedOriginalSql = parsedOriginalSql.Substring(0, parsedOriginalSql.LastIndexOf("ORDER BY ", StringComparison.Ordinal));
}
@@ -525,7 +554,7 @@ WHERE EXISTS(
INNER JOIN cmsPropertyType
ON b.datatypeNodeId = cmsPropertyType.dataTypeId
INNER JOIN
(" + string.Format(parsedOriginalSql, "DISTINCT cmsContent.contentType") + @") as docData
(" + string.Format(parsedOriginalSql, "cmsContent.contentType") + @") as docData
ON cmsPropertyType.contentTypeId = docData.contentType
WHERE a.id = b.id)", docSql.Arguments);
@@ -646,28 +675,7 @@ ORDER BY contentNodeId, propertytypeid
return result;
}
public class DocumentDefinition
{
/// <summary>
/// Initializes a new instance of the <see cref="T:System.Object"/> class.
/// </summary>
public DocumentDefinition(int id, Guid version, DateTime versionDate, DateTime createDate, IContentTypeComposition composition)
{
Id = id;
Version = version;
VersionDate = versionDate;
CreateDate = createDate;
Composition = composition;
}
public int Id { get; set; }
public Guid Version { get; set; }
public DateTime VersionDate { get; set; }
public DateTime CreateDate { get; set; }
public IContentTypeComposition Composition { get; set; }
}
protected virtual string GetDatabaseFieldNameForOrderBy(string orderBy)
{
// Translate the passed order by field (which were originally defined for in-memory object sorting
@@ -763,5 +771,92 @@ ORDER BY contentNodeId, propertytypeid
/// <param name="queryType"></param>
/// <returns></returns>
protected abstract Sql GetBaseQuery(BaseQueryType queryType);
internal class DocumentDefinition
{
/// <summary>
/// Initializes a new instance of the <see cref="T:System.Object"/> class.
/// </summary>
public DocumentDefinition(int id, Guid version, DateTime versionDate, DateTime createDate, IContentTypeComposition composition)
{
Id = id;
Version = version;
VersionDate = versionDate;
CreateDate = createDate;
Composition = composition;
}
public int Id { get; set; }
public Guid Version { get; set; }
public DateTime VersionDate { get; set; }
public DateTime CreateDate { get; set; }
public IContentTypeComposition Composition { get; set; }
}
/// <summary>
/// An object representing a query that may contain paging information
/// </summary>
internal class PagingSqlQuery
{
public Sql PrePagedSql { get; private set; }
public PagingSqlQuery(Sql prePagedSql)
{
PrePagedSql = prePagedSql;
}
public virtual bool HasPaging
{
get { return false; }
}
public virtual Sql BuildPagedQuery(string selectColumns)
{
throw new InvalidOperationException("This query has no paging information");
}
}
/// <summary>
/// An object representing a query that contains paging information
/// </summary>
/// <typeparam name="T"></typeparam>
internal class PagingSqlQuery<T> : PagingSqlQuery
{
private readonly Database _db;
private readonly long _pageIndex;
private readonly int _pageSize;
public PagingSqlQuery(Database db, Sql prePagedSql, long pageIndex, int pageSize) : base(prePagedSql)
{
_db = db;
_pageIndex = pageIndex;
_pageSize = pageSize;
}
public override bool HasPaging
{
get { return _pageSize > 0; }
}
/// <summary>
/// Creates a paged query based on the original query and subtitutes the selectColumns specified
/// </summary>
/// <param name="selectColumns"></param>
/// <returns></returns>
public override Sql BuildPagedQuery(string selectColumns)
{
if (HasPaging == false) throw new InvalidOperationException("This query has no paging information");
var resultSql = string.Format("SELECT {0} {1}", selectColumns, PrePagedSql.SQL.Substring(PrePagedSql.SQL.IndexOf("FROM", StringComparison.Ordinal)));
//this query is meant to be paged so we need to generate the paging syntax
//Create the inner paged query that was used above to get the paged result, we'll use that as the inner sub query
var args = PrePagedSql.Arguments;
string sqlStringCount, sqlStringPage;
_db.BuildPageQueries<T>(_pageIndex * _pageSize, _pageSize, resultSql, ref args, out sqlStringCount, out sqlStringPage);
return new Sql(sqlStringPage, args);
}
}
}
}
@@ -1,7 +1,23 @@
namespace Umbraco.Core.Persistence.SqlSyntax
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
namespace Umbraco.Core.Persistence.SqlSyntax
{
internal static class SqlSyntaxProviderExtensions
{
public static IEnumerable<DbIndexDefinition> GetDefinedIndexesDefinitions(this ISqlSyntaxProvider sql, Database db)
{
return sql.GetDefinedIndexes(db)
.Select(x => new DbIndexDefinition()
{
TableName = x.Item1,
IndexName = x.Item2,
ColumnName = x.Item3,
IsUnique = x.Item4
}).ToArray();
}
/// <summary>
/// Returns the quotes tableName.columnName combo
/// </summary>
+20
View File
@@ -735,6 +735,26 @@ namespace Umbraco.Core.Services
return empty.Union(files.Except(empty));
}
public void CreatePartialViewFolder(string folderPath)
{
var uow = _fileUowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreatePartialViewRepository(uow))
{
((PartialViewRepository)repository).AddFolder(folderPath);
uow.Commit();
}
}
public void CreatePartialViewMacroFolder(string folderPath)
{
var uow = _fileUowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreatePartialViewMacroRepository(uow))
{
((PartialViewMacroRepository)repository).AddFolder(folderPath);
uow.Commit();
}
}
public void DeletePartialViewFolder(string folderPath)
{
using (var uow = _fileUowProvider.GetUnitOfWork())
@@ -11,6 +11,8 @@ namespace Umbraco.Core.Services
public interface IFileService : IService
{
IEnumerable<string> GetPartialViewSnippetNames(params string[] filterNames);
void CreatePartialViewFolder(string folderPath);
void CreatePartialViewMacroFolder(string folderPath);
void DeletePartialViewFolder(string folderPath);
void DeletePartialViewMacroFolder(string folderPath);
IPartialView GetPartialView(string path);
+24 -1
View File
@@ -1716,6 +1716,24 @@ namespace Umbraco.Core.Services
#region Package Building
#endregion
/// <summary>
/// This method can be used to trigger the 'ImportedPackage' event when a package is installed by something else but this service.
/// </summary>
/// <param name="args"></param>
internal static void OnImportedPackage(ImportPackageEventArgs<InstallationSummary> args)
{
ImportedPackage.RaiseEvent(args, null);
}
/// <summary>
/// This method can be used to trigger the 'UninstalledPackage' event when a package is uninstalled by something else but this service.
/// </summary>
/// <param name="args"></param>
internal static void OnUninstalledPackage(UninstallPackageEventArgs<UninstallationSummary> args)
{
UninstalledPackage.RaiseEvent(args, null);
}
#region Event Handlers
/// <summary>
/// Occurs before Importing Content
@@ -1876,10 +1894,15 @@ namespace Umbraco.Core.Services
internal static event TypedEventHandler<IPackagingService, ImportPackageEventArgs<string>> ImportingPackage;
/// <summary>
/// Occurs after a apckage is imported
/// Occurs after a package is imported
/// </summary>
internal static event TypedEventHandler<IPackagingService, ImportPackageEventArgs<InstallationSummary>> ImportedPackage;
/// <summary>
/// Occurs after a package is uninstalled
/// </summary>
internal static event TypedEventHandler<IPackagingService, UninstallPackageEventArgs<UninstallationSummary>> UninstalledPackage;
#endregion
}
}
+2 -2
View File
@@ -215,7 +215,7 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateRelationRepository(uow);
var query = new Query<IRelation>().Where(x => x.ChildId == id || x.ParentId == id);
var query = new Query<IRelation>().Where(x => x.ParentId == id || x.ChildId == id);
return repository.GetByQuery(query);
}
}
@@ -230,7 +230,7 @@ namespace Umbraco.Core.Services
if (relationType == null) return Enumerable.Empty<IRelation>();
var relationRepo = RepositoryFactory.CreateRelationRepository(uow);
var query = new Query<IRelation>().Where(x => (x.ChildId == id || x.ParentId == id) && x.RelationTypeId == relationType.Id);
var query = new Query<IRelation>().Where(x => (x.ParentId == id || x.ChildId == id) && x.RelationTypeId == relationType.Id);
return relationRepo.GetByQuery(query);
}
}
+17 -5
View File
@@ -285,18 +285,30 @@ namespace Umbraco.Core
var member = entity as IMember;
if (member != null) return member.GetUdi();
var contentBase = entity as IContentBase;
if (contentBase != null) return contentBase.GetUdi();
var stylesheet = entity as Stylesheet;
if (stylesheet != null) return stylesheet.GetUdi();
var script = entity as Script;
if (script != null) return script.GetUdi();
var dictionaryItem = entity as IDictionaryItem;
if (dictionaryItem != null) return dictionaryItem.GetUdi();
var macro = entity as IMacro;
if (macro != null) return macro.GetUdi();
var partialView = entity as IPartialView;
if (partialView != null) return partialView.GetUdi();
var xsltFile = entity as IXsltFile;
if (xsltFile != null) return xsltFile.GetUdi();
var contentBase = entity as IContentBase;
if (contentBase != null) return contentBase.GetUdi();
var relationType = entity as IRelationType;
if (relationType != null) return relationType.GetUdi();
var dictionaryItem = entity as IDictionaryItem;
if (dictionaryItem != null) return dictionaryItem.GetUdi();
throw new NotSupportedException(string.Format("Entity type {0} is not supported.", entity.GetType().FullName));
}
}
+4
View File
@@ -314,6 +314,7 @@
<Compile Include="Deploy\IFileStore.cs" />
<Compile Include="Events\EventDefinitionFilter.cs" />
<Compile Include="OrderedHashSet.cs" />
<Compile Include="Events\UninstallPackageEventArgs.cs" />
<Compile Include="Models\GridValue.cs" />
<Compile Include="Deploy\IArtifact.cs" />
<Compile Include="Deploy\IArtifactSignature.cs" />
@@ -450,6 +451,7 @@
<Compile Include="Models\DoNotCloneAttribute.cs" />
<Compile Include="Models\IDomain.cs" />
<Compile Include="NamedUdiRange.cs" />
<Compile Include="Packaging\Models\UninstallationSummary.cs" />
<Compile Include="Persistence\BulkDataReader.cs" />
<Compile Include="Persistence\Constants-Locks.cs" />
<Compile Include="Persistence\DatabaseNodeLockExtensions.cs" />
@@ -479,6 +481,8 @@
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenFourZero\AddUniqueIdPropertyTypeGroupColumn.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenFourZero\RemoveParentIdPropertyTypeGroupColumn.cs" />
<Compile Include="Persistence\Mappers\TaskTypeMapper.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenSixZero\AddIndexToCmsMemberLoginName.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenSixZero\AddIndexesToUmbracoRelationTables.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenSixZero\AddIndexToUmbracoNodePath.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenSixZero\AddRelationTypeUniqueIdColumn.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenSixZero\AddMacroUniqueIdColumn.cs" />
+9
View File
@@ -56,5 +56,14 @@ namespace Umbraco.Tests.IO
Assert.AreEqual(IOHelper.MapPath(SystemDirectories.WebServices, true), IOHelper.MapPath(SystemDirectories.WebServices, false));
Assert.AreEqual(IOHelper.MapPath(SystemDirectories.Xslt, true), IOHelper.MapPath(SystemDirectories.Xslt, false));
}
[Test]
public void EnsurePathIsApplicationRootPrefixed()
{
//Assert
Assert.AreEqual("~/Views/Template.cshtml", IOHelper.EnsurePathIsApplicationRootPrefixed("Views/Template.cshtml"));
Assert.AreEqual("~/Views/Template.cshtml", IOHelper.EnsurePathIsApplicationRootPrefixed("/Views/Template.cshtml"));
Assert.AreEqual("~/Views/Template.cshtml", IOHelper.EnsurePathIsApplicationRootPrefixed("~/Views/Template.cshtml"));
}
}
}
@@ -364,8 +364,7 @@ namespace Umbraco.Tests.IO
ss.Complete();
// yes we are cleaning now
//Assert.IsTrue(File.Exists(path + "/ShadowSystem/path/to/some/dir/f1.txt")); // *not* cleaning
Assert.IsTrue(File.Exists(path + "/ShadowSystem/path/to/some/dir/f1.txt")); // *not* cleaning
Assert.IsTrue(File.Exists(path + "/ShadowTests/path/to/some/dir/f1.txt"));
Assert.IsFalse(File.Exists(path + "/ShadowTests/sub/sub/f2.txt"));
}
@@ -559,7 +558,7 @@ namespace Umbraco.Tests.IO
Assert.AreEqual(1, ae.InnerExceptions.Count);
e = ae.InnerExceptions[0];
Assert.IsNotNull(e.InnerException);
Assert.IsInstanceOf<Exception>(e.InnerException);
Assert.IsInstanceOf<UnauthorizedAccessException>(e.InnerException);
}
// still, the rest of the changes has been applied ok
@@ -121,6 +121,19 @@ WHERE (([umbracoNode].[nodeObjectType] = @0))) x)".Replace(Environment.NewLine,
Assert.AreEqual("CREATE UNIQUE NONCLUSTERED INDEX [IX_A] ON [TheTable] ([A])", createExpression.ToString());
}
[Test]
public void CreateIndexBuilder_SqlServer_Unique_CreatesUniqueNonClusteredIndex_Multi_Columnn()
{
var sqlSyntax = new SqlServerSyntaxProvider();
var createExpression = new CreateIndexExpression(DatabaseProviders.SqlServer, new[] { DatabaseProviders.SqlServer }, sqlSyntax)
{
Index = { Name = "IX_AB" }
};
var builder = new CreateIndexBuilder(createExpression);
builder.OnTable("TheTable").OnColumn("A").Ascending().OnColumn("B").Ascending().WithOptions().Unique();
Assert.AreEqual("CREATE UNIQUE NONCLUSTERED INDEX [IX_AB] ON [TheTable] ([A],[B])", createExpression.ToString());
}
[Test]
public void CreateIndexBuilder_SqlServer_Clustered_CreatesClusteredIndex()
{
@@ -205,6 +205,38 @@ function codefileResource($q, $http, umbDataFormatter, umbRequestHelper) {
"codeFileApiBaseUrl",
"GetScaffold?type=" + type + "&id=" + id + "&snippetName=" + snippetName)),
"Failed to get scaffold for" + type);
},
/**
* @ngdoc method
* @name umbraco.resources.codefileResource#createContainer
* @methodOf umbraco.resources.codefileResource
*
* @description
* Creates a container/folder
*
* ##usage
* <pre>
* codefileResource.createContainer("partialViews", "folder%2ffolder", "folder")
* .then(function(data) {
* alert('its here!');
* });
* </pre>
*
* @param {string} File type: (scripts, partialViews, partialViewMacros).
* @param {string} Parent Id: url encoded path
* @param {string} Container name
* @returns {Promise} resourcePromise object.
*
*/
createContainer: function(type, parentId, name) {
return umbRequestHelper.resourcePromise(
$http.post(umbRequestHelper.getApiUrl(
"codeFileApiBaseUrl",
"PostCreateContainer",
{ type: type, parentId: parentId, name: encodeURIComponent(name) })),
'Failed to create a folder under parent id ' + parentId);
}
};
@@ -1,14 +1,17 @@
(function () {
"use strict";
function PartialViewMacrosCreateController($scope, codefileResource, $location, navigationService) {
function PartialViewMacrosCreateController($scope, codefileResource, $location, navigationService, formHelper, localizationService, appState) {
var vm = this;
var node = $scope.dialogOptions.currentNode;
var localizeCreateFolder = localizationService.localize("defaultdialog_createFolder");
vm.snippets = [];
vm.showSnippets = false;
vm.creatingFolder = false;
vm.createFolderError = "";
vm.folderName = "";
vm.createPartialViewMacro = createPartialViewMacro;
vm.showCreateFolder = showCreateFolder;
@@ -39,8 +42,38 @@
vm.creatingFolder = true;
}
function createFolder() {
function createFolder(form) {
if (formHelper.submitForm({scope: $scope, formCtrl: form, statusMessage: localizeCreateFolder})) {
codefileResource.createContainer("partialViewMacros", node.id, vm.folderName).then(function (saved) {
navigationService.hideMenu();
navigationService.syncTree({
tree: "partialViewMacros",
path: saved.path,
forceReload: true,
activate: true
});
formHelper.resetForm({
scope: $scope
});
var section = appState.getSectionState("currentSection");
}, function(err) {
vm.createFolderError = err;
//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]);
}
}
});
}
}
function showCreateFromSnippet() {
@@ -47,12 +47,12 @@
<!-- Create folder -->
<div class="umb-pane" ng-if="vm.creatingFolder">
<form novalidate name="createFolderForm"
ng-submit="vm.createFolder()"
val-form-manager>
ng-submit="vm.createFolder(createFolderForm)"
val-form-manager>
<div ng-show="error">
<h5 class="text-error">{{error.errorMsg}}</h5>
<p class="text-error">{{error.data.message}}</p>
<div ng-show="vm.createFolderError">
<h5 class="text-error">{{vm.createFolderError.errorMsg}}</h5>
<p class="text-error">{{vm.createFolderError.data.message}}</p>
</div>
<umb-control-group label="Enter a folder name" hide-label="false">
@@ -1,14 +1,17 @@
(function () {
"use strict";
function PartialViewsCreateController($scope, codefileResource, $location, navigationService) {
function PartialViewsCreateController($scope, codefileResource, $location, navigationService, formHelper, localizationService, appState) {
var vm = this;
var node = $scope.dialogOptions.currentNode;
var localizeCreateFolder = localizationService.localize("defaultdialog_createFolder");
vm.snippets = [];
vm.showSnippets = false;
vm.creatingFolder = false;
vm.createFolderError = "";
vm.folderName = "";
vm.createPartialView = createPartialView;
vm.showCreateFolder = showCreateFolder;
@@ -39,8 +42,38 @@
vm.creatingFolder = true;
}
function createFolder() {
function createFolder(form) {
if (formHelper.submitForm({scope: $scope, formCtrl: form, statusMessage: localizeCreateFolder})) {
codefileResource.createContainer("partialViews", node.id, vm.folderName).then(function(saved) {
navigationService.hideMenu();
navigationService.syncTree({
tree: "partialViews",
path: saved.path,
forceReload: true,
activate: true
});
formHelper.resetForm({
scope: $scope
});
var section = appState.getSectionState("currentSection");
}, function(err) {
vm.createFolderError = err;
//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]);
}
}
});
}
}
function showCreateFromSnippet() {
@@ -47,12 +47,12 @@
<!-- Create folder -->
<div class="umb-pane" ng-if="vm.creatingFolder">
<form novalidate name="createFolderForm"
ng-submit="vm.createFolder()"
val-form-manager>
ng-submit="vm.createFolder(createFolderForm)"
val-form-manager>
<div ng-show="error">
<h5 class="text-error">{{error.errorMsg}}</h5>
<p class="text-error">{{error.data.message}}</p>
<div ng-show="vm.createFolderError">
<h5 class="text-error">{{vm.createFolderError.errorMsg}}</h5>
<p class="text-error">{{vm.createFolderError.data.message}}</p>
</div>
<umb-control-group label="Enter a folder name" hide-label="false">
@@ -1,13 +1,15 @@
(function () {
"use strict";
function ScriptsCreateController($scope, $location, navigationService) {
function ScriptsCreateController($scope, $location, navigationService, formHelper, codefileResource, localizationService, appState) {
var vm = this;
var node = $scope.dialogOptions.currentNode;
var localizeCreateFolder = localizationService.localize("defaultdialog_createFolder");
vm.creatingFolder = false;
vm.folderName = "";
vm.createFolderError = "";
vm.fileExtension = "";
vm.createFile = createFile;
@@ -23,8 +25,40 @@
vm.creatingFolder = true;
}
function createFolder() {
function createFolder(form) {
if (formHelper.submitForm({scope: $scope, formCtrl: form, statusMessage: localizeCreateFolder})) {
codefileResource.createContainer("scripts", node.id, vm.folderName).then(function (saved) {
navigationService.hideMenu();
navigationService.syncTree({
tree: "scripts",
path: saved.path,
forceReload: true,
activate: true
});
formHelper.resetForm({
scope: $scope
});
var section = appState.getSectionState("currentSection");
}, function(err) {
vm.createFolderError = err;
//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]);
}
}
});
}
}
}
@@ -22,12 +22,12 @@
<div class="umb-pane" ng-if="vm.creatingFolder">
<form novalidate name="createFolderForm"
ng-submit="vm.createFolder()"
ng-submit="vm.createFolder(createFolderForm)"
val-form-manager>
<div ng-show="error">
<h5 class="text-error">{{error.errorMsg}}</h5>
<p class="text-error">{{error.data.message}}</p>
<div ng-show="vm.createFolderError">
<h5 class="text-error">{{vm.createFolderError.errorMsg}}</h5>
<p class="text-error">{{vm.createFolderError.data.message}}</p>
</div>
<umb-control-group label="Enter a folder name" hide-label="false">
+55 -1
View File
@@ -56,6 +56,61 @@ namespace Umbraco.Web.Editors
}
}
/// <summary>
/// Used to create a container/folder in 'partialViews', 'partialViewMacros' or 'scripts'
/// </summary>
/// <param name="type">'partialViews', 'partialViewMacros' or 'scripts'</param>
/// <param name="parentId">The virtual path of the parent.</param>
/// <param name="name">The name of the container/folder</param>
/// <returns></returns>
[HttpPost]
public CodeFileDisplay PostCreateContainer(string type, string parentId, string name)
{
if (string.IsNullOrWhiteSpace(type) || string.IsNullOrWhiteSpace(name))
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
// if the parentId is root (-1) then we just need an empty string as we are
// creating the path below and we don't wan't -1 in the path
if (parentId == Core.Constants.System.Root.ToInvariantString())
{
parentId = string.Empty;
}
name = System.Web.HttpUtility.UrlDecode(name);
if (parentId.IsNullOrWhiteSpace() == false)
{
parentId = System.Web.HttpUtility.UrlDecode(parentId);
name = parentId.EnsureEndsWith("/") + name;
}
var virtualPath = string.Empty;
switch (type)
{
case Core.Constants.Trees.PartialViews:
virtualPath = NormalizeVirtualPath(name, SystemDirectories.PartialViews);
Services.FileService.CreatePartialViewFolder(virtualPath);
break;
case Core.Constants.Trees.PartialViewMacros:
virtualPath = NormalizeVirtualPath(name, SystemDirectories.MacroPartials);
Services.FileService.CreatePartialViewMacroFolder(virtualPath);
break;
case Core.Constants.Trees.Scripts:
virtualPath = NormalizeVirtualPath(name, SystemDirectories.Scripts);
Services.FileService.CreateScriptFolder(virtualPath);
break;
}
return new CodeFileDisplay
{
VirtualPath = virtualPath,
Path = Url.GetTreePathFromFilePath(virtualPath)
};
}
/// <summary>
/// Used to get a specific file from disk via the FileService
/// </summary>
@@ -70,7 +125,6 @@ namespace Umbraco.Web.Editors
}
virtualPath = System.Web.HttpUtility.UrlDecode(virtualPath);
switch (type)
{
@@ -15,9 +15,11 @@ using umbraco.cms.presentation.Trees;
using umbraco.presentation.developer.packages;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Events;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Packaging.Models;
using Umbraco.Core.Services;
using Umbraco.Web.Models;
using Umbraco.Web.Models.ContentEditing;
@@ -81,7 +83,14 @@ namespace Umbraco.Web.Editors
if (pack == null) throw new ArgumentNullException("pack");
var refreshCache = false;
var removedTemplates = new List<ITemplate>();
var removedMacros = new List<IMacro>();
var removedContentTypes = new List<IContentType>();
var removedDictionaryItems = new List<IDictionaryItem>();
var removedDataTypes = new List<IDataTypeDefinition>();
var removedFiles = new List<string>();
//Uninstall templates
foreach (var item in pack.Data.Templates.ToArray())
{
@@ -90,6 +99,7 @@ namespace Umbraco.Web.Editors
var found = Services.FileService.GetTemplate(nId);
if (found != null)
{
removedTemplates.Add(found);
ApplicationContext.Services.FileService.DeleteTemplate(found.Alias, Security.GetUserId());
}
pack.Data.Templates.Remove(nId.ToString());
@@ -103,8 +113,9 @@ namespace Umbraco.Web.Editors
var macro = Services.MacroService.GetById(nId);
if (macro != null)
{
removedMacros.Add(macro);
Services.MacroService.Delete(macro);
}
}
pack.Data.Macros.Remove(nId.ToString());
}
@@ -128,8 +139,9 @@ namespace Umbraco.Web.Editors
{
//TODO: I don't think this ordering is necessary
var orderedTypes = from contentType in contentTypes
orderby contentType.ParentId descending, contentType.Id descending
select contentType;
orderby contentType.ParentId descending, contentType.Id descending
select contentType;
removedContentTypes.AddRange(orderedTypes);
contentTypeService.Delete(orderedTypes);
}
@@ -141,8 +153,9 @@ namespace Umbraco.Web.Editors
var di = Services.LocalizationService.GetDictionaryItemById(nId);
if (di != null)
{
removedDictionaryItems.Add(di);
Services.LocalizationService.Delete(di);
}
}
pack.Data.DictionaryItems.Remove(nId.ToString());
}
@@ -154,8 +167,9 @@ namespace Umbraco.Web.Editors
var dtd = Services.DataTypeService.GetDataTypeDefinitionById(nId);
if (dtd != null)
{
removedDataTypes.Add(dtd);
Services.DataTypeService.Delete(dtd);
}
}
pack.Data.DataTypes.Remove(nId.ToString());
}
@@ -196,30 +210,45 @@ namespace Umbraco.Web.Editors
//Remove files
foreach (var item in pack.Data.Files.ToArray())
{
removedFiles.Add(item.GetRelativePath());
//here we need to try to find the file in question as most packages does not support the tilde char
var file = IOHelper.FindFile(item);
if (file != null)
{
if (file.StartsWith("/") == false)
file = string.Format("/{0}", file);
var filePath = IOHelper.MapPath(file);
if (File.Exists(filePath))
{
File.Delete(filePath);
}
}
pack.Data.Files.Remove(file);
}
pack.Save();
pack.Delete(Security.GetUserId());
// create a summary of what was actually removed, for PackagingService.UninstalledPackage
var summary = new UninstallationSummary
{
MetaData = pack.GetMetaData(),
TemplatesUninstalled = removedTemplates,
MacrosUninstalled = removedMacros,
ContentTypesUninstalled = removedContentTypes,
DictionaryItemsUninstalled = removedDictionaryItems,
DataTypesUninstalled = removedDataTypes,
FilesUninstalled = removedFiles,
PackageUninstalled = true
};
// trigger the UninstalledPackage event
PackagingService.OnUninstalledPackage(new UninstallPackageEventArgs<UninstallationSummary>(summary, false));
//TODO: Legacy - probably not needed
if (refreshCache)
{
library.RefreshContent();
}
}
TreeDefinitionCollection.Instance.ReRegisterTrees();
global::umbraco.BusinessLogic.Actions.Action.ReRegisterActionsAndHandlers();
}
@@ -239,8 +268,8 @@ namespace Umbraco.Web.Editors
{
Version pckVersion;
return Version.TryParse(pck.Data.Version, out pckVersion)
? new {package = pck, version = pckVersion}
: new {package = pck, version = new Version(0, 0, 0)};
? new { package = pck, version = pckVersion }
: new { package = pck, version = new Version(0, 0, 0) };
})
.Select(grouping =>
{
@@ -311,7 +340,7 @@ namespace Umbraco.Web.Editors
model.UmbracoVersion = ins.RequirementsType == RequirementsType.Strict
? string.Format("{0}.{1}.{2}", ins.RequirementsMajor, ins.RequirementsMinor, ins.RequirementsPatch)
: string.Empty;
//now we need to check for version comparison
model.IsCompatible = true;
if (ins.RequirementsType == RequirementsType.Strict)
@@ -391,7 +420,7 @@ namespace Umbraco.Web.Editors
{
//TODO: Currently it has to be here, it's not ideal but that's the way it is right now
var packageTempDir = IOHelper.MapPath(SystemDirectories.Data);
//ensure it's there
Directory.CreateDirectory(packageTempDir);
@@ -407,14 +436,14 @@ namespace Umbraco.Web.Editors
PopulateFromPackageData(model);
var validate = ValidateInstalledInternal(model.Name, model.Version);
if (validate == false)
{
//this package is already installed
throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse(
Services.TextService.Localize("packager/packageAlreadyInstalled")));
Services.TextService.Localize("packager/packageAlreadyInstalled")));
}
}
else
{
@@ -423,7 +452,7 @@ namespace Umbraco.Web.Editors
Services.TextService.Localize("media/disallowedFileType"),
SpeechBubbleIcon.Warning));
}
}
return model;
@@ -445,7 +474,7 @@ namespace Umbraco.Web.Editors
//our repo guid
using (var our = Repository.getByGuid("65194810-1f85-11dd-bd0b-0800200c9a66"))
{
path = our.fetch(packageGuid, Security.CurrentUser.Id);
path = our.fetch(packageGuid, Security.CurrentUser.Id);
}
}
@@ -489,12 +518,12 @@ namespace Umbraco.Web.Editors
if (UmbracoVersion.Current < packageMinVersion)
{
throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse(
Services.TextService.Localize("packager/targetVersionMismatch", new[] {packageMinVersion.ToString()})));
Services.TextService.Localize("packager/targetVersionMismatch", new[] { packageMinVersion.ToString() })));
}
}
model.TemporaryDirectoryPath = Path.Combine(SystemDirectories.Data, tempPath);
model.Id = ins.CreateManifest( IOHelper.MapPath(model.TemporaryDirectoryPath), model.PackageGuid.ToString(), model.RepositoryGuid.ToString());
model.Id = ins.CreateManifest(IOHelper.MapPath(model.TemporaryDirectoryPath), model.PackageGuid.ToString(), model.RepositoryGuid.ToString());
return model;
}
@@ -14,16 +14,18 @@ using Umbraco.Core.Packaging;
using umbraco.cms.businesslogic.web;
using umbraco.BusinessLogic;
using System.Diagnostics;
using umbraco.cms.businesslogic.macro;
using umbraco.cms.businesslogic.template;
using umbraco.interfaces;
using Umbraco.Core.Events;
using Umbraco.Core.Packaging.Models;
using Umbraco.Core.Services;
namespace umbraco.cms.businesslogic.packager
{
/// <summary>
/// The packager is a component which enables sharing of both data and functionality components between different umbraco installations.
///
/// The output is a .umb (a zip compressed file) which contains the exported documents/medias/macroes/documenttypes (etc.)
/// The output is a .umb (a zip compressed file) which contains the exported documents/medias/macros/documenttypes (etc.)
/// in a Xml document, along with the physical files used (images/usercontrols/xsl documents etc.)
///
/// Partly implemented, import of packages is done, the export is *under construction*.
@@ -507,6 +509,7 @@ namespace umbraco.cms.businesslogic.packager
}
OnPackageBusinessLogicInstalled(insPack);
OnPackageInstalled(insPack);
}
}
@@ -517,6 +520,7 @@ namespace umbraco.cms.businesslogic.packager
/// <param name="tempDir"></param>
public void InstallCleanUp(int packageId, string tempDir)
{
if (Directory.Exists(tempDir))
{
Directory.Delete(tempDir, true);
@@ -814,5 +818,21 @@ namespace umbraco.cms.businesslogic.packager
EventHandler<InstalledPackage> handler = PackageBusinessLogicInstalled;
if (handler != null) handler(null, e);
}
private void OnPackageInstalled(InstalledPackage insPack)
{
// getting an InstallationSummary for sending to the PackagingService.ImportedPackage event
var fileService = ApplicationContext.Current.Services.FileService;
var macroService = ApplicationContext.Current.Services.MacroService;
var contentTypeService = ApplicationContext.Current.Services.ContentTypeService;
var dataTypeService = ApplicationContext.Current.Services.DataTypeService;
var localizationService = ApplicationContext.Current.Services.LocalizationService;
var installationSummary = insPack.GetInstallationSummary(contentTypeService, dataTypeService, fileService, localizationService, macroService);
installationSummary.PackageInstalled = true;
var args = new ImportPackageEventArgs<InstallationSummary>(installationSummary, false);
PackagingService.OnImportedPackage(args);
}
}
}
@@ -1,8 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Auditing;
using Umbraco.Core.Logging;
using Umbraco.Core.IO;
using Umbraco.Core.Packaging.Models;
using Umbraco.Core.Services;
namespace umbraco.cms.businesslogic.packager {
public class InstalledPackage
@@ -124,5 +127,70 @@ namespace umbraco.cms.businesslogic.packager {
if (AfterDelete != null)
AfterDelete(this, e);
}
/// <summary>
/// Used internally for creating an InstallationSummary (used in new PackagingService) representation of this InstalledPackage object.
/// </summary>
/// <param name="contentTypeService"></param>
/// <param name="dataTypeService"></param>
/// <param name="fileService"></param>
/// <param name="localizationService"></param>
/// <param name="macroService"></param>
/// <returns></returns>
internal InstallationSummary GetInstallationSummary(IContentTypeService contentTypeService, IDataTypeService dataTypeService, IFileService fileService, ILocalizationService localizationService, IMacroService macroService)
{
var macros = TryGetIntegerIds(Data.Macros).Select(macroService.GetById).ToList();
var templates = TryGetIntegerIds(Data.Templates).Select(fileService.GetTemplate).ToList();
var contentTypes = TryGetIntegerIds(Data.Documenttypes).Select(contentTypeService.GetContentType).ToList();
var dataTypes = TryGetIntegerIds(Data.DataTypes).Select(dataTypeService.GetDataTypeDefinitionById).ToList();
var dictionaryItems = TryGetIntegerIds(Data.DictionaryItems).Select(localizationService.GetDictionaryItemById).ToList();
var languages = TryGetIntegerIds(Data.Languages).Select(localizationService.GetLanguageById).ToList();
for (var i = 0; i < Data.Files.Count; i++)
{
var filePath = Data.Files[i];
Data.Files[i] = filePath.GetRelativePath();
}
return new InstallationSummary
{
ContentTypesInstalled = contentTypes,
DataTypesInstalled = dataTypes,
DictionaryItemsInstalled = dictionaryItems,
FilesInstalled = Data.Files,
LanguagesInstalled = languages,
MacrosInstalled = macros,
MetaData = GetMetaData(),
TemplatesInstalled = templates,
};
}
internal MetaData GetMetaData()
{
return new MetaData()
{
AuthorName = Data.Author,
AuthorUrl = Data.AuthorUrl,
Control = Data.LoadControl,
License = Data.License,
LicenseUrl = Data.LicenseUrl,
Name = Data.Name,
Readme = Data.Readme,
Url = Data.Url,
Version = Data.Version
};
}
private static IEnumerable<int> TryGetIntegerIds(IEnumerable<string> ids)
{
var intIds = new List<int>();
foreach (var id in ids)
{
int parsed;
if (int.TryParse(id, out parsed))
intIds.Add(parsed);
}
return intIds;
}
}
}
@@ -72,13 +72,14 @@ namespace umbraco.cms.businesslogic.propertytype
_tabId = _propertyTypeGroup;
}
//Fixed issue U4-9493 Case issues
_sortOrder = found.sortOrder;
_alias = found.alias;
_name = found.name;
_alias = found.Alias;
_name = found.Name;
_validationRegExp = found.validationRegExp;
_DataTypeId = found.DataTypeId;
_DataTypeId = found.dataTypeId;
_contenttypeid = found.contentTypeId;
_description = found.description;
_description = found.Description;
}
#endregion