Merge pull request #1984 from umbraco/temp-content-blueprints
Content blueprints
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
# Usage: on line 2 put the release version, on line 3 put the version comment (example: beta)
|
||||
7.6.3
|
||||
7.6.4
|
||||
+2
-2
@@ -11,5 +11,5 @@ using System.Resources;
|
||||
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
[assembly: AssemblyFileVersion("7.6.3")]
|
||||
[assembly: AssemblyInformationalVersion("7.6.3")]
|
||||
[assembly: AssemblyFileVersion("7.6.4")]
|
||||
[assembly: AssemblyInformationalVersion("7.6.4")]
|
||||
@@ -56,7 +56,12 @@
|
||||
/// <summary>
|
||||
/// alias for the content tree.
|
||||
/// </summary>
|
||||
public const string Content = "content";
|
||||
public const string Content = "content";
|
||||
|
||||
/// <summary>
|
||||
/// alias for the content blueprint tree.
|
||||
/// </summary>
|
||||
public const string ContentBlueprints = "contentBlueprints";
|
||||
|
||||
/// <summary>
|
||||
/// alias for the member tree.
|
||||
|
||||
@@ -68,12 +68,22 @@ namespace Umbraco.Core
|
||||
/// Guid for a Document object.
|
||||
/// </summary>
|
||||
public const string Document = "C66BA18E-EAF3-4CFF-8A22-41B16D66A972";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Guid for a Document object.
|
||||
/// </summary>
|
||||
public static readonly Guid DocumentGuid = new Guid(Document);
|
||||
|
||||
/// <summary>
|
||||
/// Guid for a Document Blueprint object.
|
||||
/// </summary>
|
||||
public const string DocumentBlueprint = "6EBEF410-03AA-48CF-A792-E1C1CB087ACA";
|
||||
|
||||
/// <summary>
|
||||
/// Guid for a Document object.
|
||||
/// </summary>
|
||||
public static readonly Guid DocumentBlueprintGuid = new Guid(DocumentBlueprint);
|
||||
|
||||
/// <summary>
|
||||
/// Guid for a Document Type object.
|
||||
/// </summary>
|
||||
|
||||
@@ -38,6 +38,14 @@ namespace Umbraco.Core.Models
|
||||
[UmbracoUdiType(Constants.UdiEntityType.Document)]
|
||||
Document,
|
||||
|
||||
/// <summary>
|
||||
/// Document
|
||||
/// </summary>
|
||||
[UmbracoObjectType(Constants.ObjectTypes.DocumentBlueprint, typeof(IContent))]
|
||||
[FriendlyName("DocumentBlueprint")]
|
||||
[UmbracoUdiType(Constants.UdiEntityType.DocumentBluePrint)]
|
||||
DocumentBlueprint,
|
||||
|
||||
/// <summary>
|
||||
/// Media
|
||||
/// </summary>
|
||||
|
||||
@@ -170,9 +170,9 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
|
||||
|
||||
private void CreateUmbracoUserTypeData()
|
||||
{
|
||||
_database.Insert("umbracoUserType", "id", false, new UserTypeDto { Id = 1, Alias = "admin", Name = "Administrators", DefaultPermissions = "CADMOSKTPIURZ:5F7" });
|
||||
_database.Insert("umbracoUserType", "id", false, new UserTypeDto { Id = 1, Alias = "admin", Name = "Administrators", DefaultPermissions = "CADMOSKTPIURZ:5F7ï" });
|
||||
_database.Insert("umbracoUserType", "id", false, new UserTypeDto { Id = 2, Alias = "writer", Name = "Writer", DefaultPermissions = "CAH:F" });
|
||||
_database.Insert("umbracoUserType", "id", false, new UserTypeDto { Id = 3, Alias = "editor", Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5F" });
|
||||
_database.Insert("umbracoUserType", "id", false, new UserTypeDto { Id = 3, Alias = "editor", Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5Fï" });
|
||||
_database.Insert("umbracoUserType", "id", false, new UserTypeDto { Id = 4, Alias = "translator", Name = "Translator", DefaultPermissions = "AF" });
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,28 @@ using Umbraco.Core.Persistence.UnitOfWork;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
/// <summary>
|
||||
/// Override the base content repository so we can change the node object type
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It would be nicer if we could separate most of this down into a smaller version of the ContentRepository class, however to do that
|
||||
/// requires quite a lot of work since we'd need to re-organize the interhitance quite a lot or create a helper class to perform a lot of the underlying logic.
|
||||
///
|
||||
/// TODO: Create a helper method to conain most of the underlying logic for the ContentRepository
|
||||
/// </remarks>
|
||||
internal class ContentBlueprintRepository : ContentRepository
|
||||
{
|
||||
public ContentBlueprintRepository(IScopeUnitOfWork work, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider syntaxProvider, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, IContentSection contentSection) : base(work, cacheHelper, logger, syntaxProvider, contentTypeRepository, templateRepository, tagRepository, contentSection)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Guid NodeObjectTypeId
|
||||
{
|
||||
get { return Constants.ObjectTypes.DocumentBlueprintGuid; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a repository for doing CRUD operations for <see cref="IContent"/>
|
||||
/// </summary>
|
||||
@@ -201,7 +223,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
protected override Guid NodeObjectTypeId
|
||||
{
|
||||
get { return new Guid(Constants.ObjectTypes.Document); }
|
||||
get { return Constants.ObjectTypes.DocumentGuid; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -267,7 +289,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
//now delete the items that shouldn't be there
|
||||
var sqlAllIds = translate(0, GetBaseQuery(BaseQueryType.Ids));
|
||||
var allContentIds = Database.Fetch<int>(sqlAllIds);
|
||||
var docObjectType = Guid.Parse(Constants.ObjectTypes.Document);
|
||||
var docObjectType = NodeObjectTypeId;
|
||||
var xmlIdsQuery = new Sql()
|
||||
.Select("DISTINCT cmsContentXml.nodeId")
|
||||
.From<ContentXmlDto>(SqlSyntax)
|
||||
@@ -742,7 +764,19 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
return ProcessQuery(translate(translatorFull), new PagingSqlQuery(translate(translatorIds)), true);
|
||||
}
|
||||
|
||||
|
||||
public IEnumerable<IContent> GetBlueprints(IQuery<IContent> query)
|
||||
{
|
||||
Func<SqlTranslator<IContent>, Sql> translate = t => t.Translate();
|
||||
|
||||
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), new PagingSqlQuery(translate(translatorIds)), true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This builds the Xml document used for the XML cache
|
||||
/// </summary>
|
||||
@@ -786,7 +820,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder",
|
||||
XmlElement last = null;
|
||||
|
||||
//NOTE: Query creates a reader - does not load all into memory
|
||||
foreach (var row in Database.Query<dynamic>(sql, new { type = new Guid(Constants.ObjectTypes.Document) }))
|
||||
foreach (var row in Database.Query<dynamic>(sql, new { type = NodeObjectTypeId }))
|
||||
{
|
||||
string parentId = ((int)row.parentID).ToInvariantString();
|
||||
string xml = row.xml;
|
||||
|
||||
@@ -110,6 +110,19 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
return Database.Fetch<string>(sql);
|
||||
}
|
||||
|
||||
public IEnumerable<int> GetAllContentTypeIds(string[] aliases)
|
||||
{
|
||||
if (aliases.Length == 0) return Enumerable.Empty<int>();
|
||||
|
||||
var sql = new Sql().Select("cmsContentType.nodeId")
|
||||
.From<ContentTypeDto>(SqlSyntax)
|
||||
.InnerJoin<NodeDto>(SqlSyntax)
|
||||
.On<ContentTypeDto, NodeDto>(SqlSyntax, dto => dto.NodeId, dto => dto.NodeId)
|
||||
.Where<ContentTypeDto>(dto => aliases.Contains(dto.Alias), SqlSyntax);
|
||||
|
||||
return Database.Fetch<int>(sql);
|
||||
}
|
||||
|
||||
protected override Sql GetBaseQuery(bool isCount)
|
||||
{
|
||||
var sql = new Sql();
|
||||
|
||||
@@ -48,8 +48,8 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
public IEnumerable<IUmbracoEntity> GetPagedResultsByQuery(IQuery<IUmbracoEntity> query, Guid objectTypeId, long pageIndex, int pageSize, out long totalRecords,
|
||||
string orderBy, Direction orderDirection, IQuery<IUmbracoEntity> filter = null)
|
||||
{
|
||||
bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document);
|
||||
bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media);
|
||||
bool isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid;
|
||||
bool isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid;
|
||||
var factory = new UmbracoEntityFactory();
|
||||
|
||||
var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, sql =>
|
||||
@@ -178,8 +178,8 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
public IUmbracoEntity GetByKey(Guid key, Guid objectTypeId)
|
||||
{
|
||||
bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document);
|
||||
bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media);
|
||||
bool isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid;
|
||||
bool isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid;
|
||||
|
||||
var sql = GetFullSqlForEntityType(key, isContent, isMedia, objectTypeId);
|
||||
|
||||
@@ -225,8 +225,8 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
public virtual IUmbracoEntity Get(int id, Guid objectTypeId)
|
||||
{
|
||||
bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document);
|
||||
bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media);
|
||||
bool isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid;
|
||||
bool isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid;
|
||||
|
||||
var sql = GetFullSqlForEntityType(id, isContent, isMedia, objectTypeId);
|
||||
|
||||
@@ -280,8 +280,8 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
private IEnumerable<IUmbracoEntity> PerformGetAll(Guid objectTypeId, Action<Sql> filter = null)
|
||||
{
|
||||
bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document);
|
||||
bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media);
|
||||
bool isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid;
|
||||
bool isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid;
|
||||
var sql = GetFullSqlForEntityType(isContent, isMedia, objectTypeId, filter);
|
||||
|
||||
var factory = new UmbracoEntityFactory();
|
||||
@@ -324,8 +324,8 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
public virtual IEnumerable<IUmbracoEntity> GetByQuery(IQuery<IUmbracoEntity> query, Guid objectTypeId)
|
||||
{
|
||||
|
||||
bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document);
|
||||
bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media);
|
||||
bool isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid;
|
||||
bool isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid;
|
||||
|
||||
var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, null, objectTypeId);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
{
|
||||
|
||||
public interface IContentRepository : IRepositoryVersionable<int, IContent>, IRecycleBinRepository<IContent>, IDeleteMediaFilesRepository
|
||||
{
|
||||
|
||||
@@ -47,5 +47,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
/// <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);
|
||||
|
||||
IEnumerable<int> GetAllContentTypeIds(string[] aliases);
|
||||
}
|
||||
}
|
||||
@@ -52,25 +52,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
/// </summary>
|
||||
/// <param name="id">Id of the <see cref="TEntity"/> to retrieve versions from</param>
|
||||
/// <returns>An enumerable list of the same <see cref="TEntity"/> object with different versions</returns>
|
||||
public virtual IEnumerable<TEntity> GetAllVersions(int id)
|
||||
{
|
||||
var sql = new Sql();
|
||||
sql.Select("*")
|
||||
.From<ContentVersionDto>(SqlSyntax)
|
||||
.InnerJoin<ContentDto>(SqlSyntax)
|
||||
.On<ContentVersionDto, ContentDto>(SqlSyntax, left => left.NodeId, right => right.NodeId)
|
||||
.InnerJoin<NodeDto>(SqlSyntax)
|
||||
.On<ContentDto, NodeDto>(SqlSyntax, left => left.NodeId, right => right.NodeId)
|
||||
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId)
|
||||
.Where<NodeDto>(x => x.NodeId == id)
|
||||
.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
|
||||
|
||||
var dtos = Database.Fetch<ContentVersionDto, ContentDto, NodeDto>(sql);
|
||||
foreach (var dto in dtos)
|
||||
{
|
||||
yield return GetByVersion(dto.VersionId);
|
||||
}
|
||||
}
|
||||
public abstract IEnumerable<TEntity> GetAllVersions(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all version Ids for the given content item ordered so latest is first
|
||||
|
||||
@@ -145,6 +145,23 @@ namespace Umbraco.Core.Persistence
|
||||
};
|
||||
}
|
||||
|
||||
public virtual IContentRepository CreateContentBlueprintRepository(IScopeUnitOfWork uow)
|
||||
{
|
||||
return new ContentBlueprintRepository(
|
||||
uow,
|
||||
_cacheHelper,
|
||||
_logger,
|
||||
_sqlSyntax,
|
||||
CreateContentTypeRepository(uow),
|
||||
CreateTemplateRepository(uow),
|
||||
CreateTagRepository(uow),
|
||||
_settings.Content)
|
||||
{
|
||||
//duplicates are allowed
|
||||
EnsureUniqueNaming = false
|
||||
};
|
||||
}
|
||||
|
||||
public virtual IContentTypeRepository CreateContentTypeRepository(IScopeUnitOfWork uow)
|
||||
{
|
||||
return new ContentTypeRepository(
|
||||
|
||||
@@ -174,8 +174,7 @@ namespace Umbraco.Core.Services
|
||||
content.WriterId = userId;
|
||||
|
||||
uow.Events.Dispatch(Created, this, new NewEventArgs<IContent>(content, false, contentTypeAlias, parentId));
|
||||
|
||||
Audit(uow, AuditType.New, string.Format("Content '{0}' was created", name), content.CreatorId, content.Id);
|
||||
|
||||
uow.Commit();
|
||||
}
|
||||
|
||||
@@ -217,8 +216,7 @@ namespace Umbraco.Core.Services
|
||||
content.WriterId = userId;
|
||||
|
||||
uow.Events.Dispatch(Created, this, new NewEventArgs<IContent>(content, false, contentTypeAlias, parent));
|
||||
|
||||
Audit(uow, AuditType.New, string.Format("Content '{0}' was created", name), content.CreatorId, content.Id);
|
||||
|
||||
uow.Commit();
|
||||
}
|
||||
|
||||
@@ -1155,6 +1153,82 @@ namespace Umbraco.Core.Services
|
||||
return ((IContentServiceOperations)this).SaveAndPublish(content, userId, raiseEvents);
|
||||
}
|
||||
|
||||
public IContent GetBlueprintById(int id)
|
||||
{
|
||||
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
|
||||
{
|
||||
var repository = RepositoryFactory.CreateContentBlueprintRepository(uow);
|
||||
return repository.Get(id);
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveBlueprint(IContent content, int userId = 0)
|
||||
{
|
||||
//always ensure the blueprint is at the root
|
||||
content.ParentId = -1;
|
||||
|
||||
using (new WriteLock(Locker))
|
||||
{
|
||||
using (var uow = UowProvider.GetUnitOfWork())
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(content.Name))
|
||||
{
|
||||
throw new ArgumentException("Cannot save content blueprint with empty name.");
|
||||
}
|
||||
|
||||
var repository = RepositoryFactory.CreateContentBlueprintRepository(uow);
|
||||
|
||||
if (content.HasIdentity == false)
|
||||
{
|
||||
content.CreatorId = userId;
|
||||
}
|
||||
content.WriterId = userId;
|
||||
|
||||
repository.AddOrUpdate(content);
|
||||
|
||||
uow.Commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteBlueprint(IContent content, int userId = 0)
|
||||
{
|
||||
using (new WriteLock(Locker))
|
||||
{
|
||||
using (var uow = UowProvider.GetUnitOfWork())
|
||||
{
|
||||
var repository = RepositoryFactory.CreateContentBlueprintRepository(uow);
|
||||
repository.Delete(content);
|
||||
uow.Commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = 0)
|
||||
{
|
||||
if (blueprint == null) throw new ArgumentNullException("blueprint");
|
||||
|
||||
var contentType = blueprint.ContentType;
|
||||
var content = new Content(name, -1, contentType);
|
||||
content.Path = string.Concat(content.ParentId.ToString(), ",", content.Id);
|
||||
|
||||
using (var uow = UowProvider.GetUnitOfWork())
|
||||
{
|
||||
content.CreatorId = userId;
|
||||
content.WriterId = userId;
|
||||
|
||||
//Now we need to map all of the properties over!
|
||||
foreach (var property in blueprint.Properties)
|
||||
{
|
||||
content.SetValue(property.Alias, property.Value);
|
||||
}
|
||||
|
||||
uow.Commit();
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves a single <see cref="IContent"/> object
|
||||
/// </summary>
|
||||
@@ -1787,6 +1861,21 @@ namespace Umbraco.Core.Services
|
||||
return true;
|
||||
}
|
||||
|
||||
public IEnumerable<IContent> GetBlueprintsForContentTypes(params int[] documentTypeIds)
|
||||
{
|
||||
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
|
||||
{
|
||||
var repository = RepositoryFactory.CreateContentBlueprintRepository(uow);
|
||||
|
||||
var query = new Query<IContent>();
|
||||
if (documentTypeIds.Length > 0)
|
||||
{
|
||||
query.Where(x => documentTypeIds.Contains(x.ContentTypeId));
|
||||
}
|
||||
return repository.GetByQuery(query);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets paged content descendants as XML by path
|
||||
/// </summary>
|
||||
|
||||
@@ -367,6 +367,15 @@ namespace Umbraco.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<int> GetAllContentTypeIds(string[] aliases)
|
||||
{
|
||||
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
|
||||
{
|
||||
var repository = RepositoryFactory.CreateContentTypeRepository(uow);
|
||||
return repository.GetAllContentTypeIds(aliases);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a content type as a child under the specified parent if specified (otherwise to the root)
|
||||
/// </summary>
|
||||
|
||||
@@ -16,10 +16,10 @@ namespace Umbraco.Core.Services
|
||||
/// explicitly. These methods will replace the normal ones in IContentService in v8 and this will be removed.
|
||||
/// </summary>
|
||||
public interface IContentServiceOperations
|
||||
{
|
||||
//TODO: Remove this class in v8
|
||||
|
||||
//TODO: There's probably more that needs to be added like the EmptyRecycleBin, etc...
|
||||
{
|
||||
//TODO: Remove this class in v8
|
||||
|
||||
//TODO: There's probably more that needs to be added like the EmptyRecycleBin, etc...
|
||||
|
||||
/// <summary>
|
||||
/// Saves a single <see cref="IContent"/> object
|
||||
@@ -96,6 +96,12 @@ namespace Umbraco.Core.Services
|
||||
/// </summary>
|
||||
public interface IContentService : IContentServiceBase
|
||||
{
|
||||
IEnumerable<IContent> GetBlueprintsForContentTypes(params int[] documentTypeIds);
|
||||
IContent GetBlueprintById(int id);
|
||||
void SaveBlueprint(IContent content, int userId = 0);
|
||||
void DeleteBlueprint(IContent content, int userId = 0);
|
||||
IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all XML entries found in the cmsContentXml table based on the given path
|
||||
/// </summary>
|
||||
|
||||
@@ -61,6 +61,13 @@ namespace Umbraco.Core.Services
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<string> GetAllContentTypeAliases(params Guid[] objectTypes);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all content type Ids for the aliases given
|
||||
/// </summary>
|
||||
/// <param name="aliases"></param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<int> GetAllContentTypeIds(string[] aliases);
|
||||
|
||||
/// <summary>
|
||||
/// Copies a content type as a child under the specified parent if specified (otherwise to the root)
|
||||
|
||||
@@ -24,6 +24,10 @@ namespace Umbraco.Core
|
||||
|
||||
[UdiType(UdiType.GuidUdi)]
|
||||
public const string Document = "document";
|
||||
|
||||
[UdiType(UdiType.GuidUdi)]
|
||||
public const string DocumentBluePrint = "document-blueprint";
|
||||
|
||||
[UdiType(UdiType.GuidUdi)]
|
||||
public const string Media = "media";
|
||||
[UdiType(UdiType.GuidUdi)]
|
||||
|
||||
@@ -301,7 +301,7 @@ AnotherContentFinder
|
||||
public void Resolves_Actions()
|
||||
{
|
||||
var actions = _manager.ResolveActions();
|
||||
Assert.AreEqual(37, actions.Count());
|
||||
Assert.AreEqual(38, actions.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -46,11 +46,112 @@ namespace Umbraco.Tests.Services
|
||||
VersionableRepositoryBase<int, IContent>.ThrowOnWarning = false;
|
||||
|
||||
base.TearDown();
|
||||
}
|
||||
|
||||
//TODO Add test to verify there is only ONE newest document/content in cmsDocument table after updating.
|
||||
}
|
||||
|
||||
//TODO Add test to verify there is only ONE newest document/content in cmsDocument table after updating.
|
||||
//TODO Add test to delete specific version (with and without deleting prior versions) and versions by date.
|
||||
|
||||
[Test]
|
||||
public void Create_Blueprint()
|
||||
{
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
|
||||
var contentType = MockedContentTypes.CreateTextpageContentType();
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
var blueprint = MockedContent.CreateTextpageContent(contentType, "hello", -1);
|
||||
blueprint.SetValue("title", "blueprint 1");
|
||||
blueprint.SetValue("bodyText", "blueprint 2");
|
||||
blueprint.SetValue("keywords", "blueprint 3");
|
||||
blueprint.SetValue("description", "blueprint 4");
|
||||
|
||||
contentService.SaveBlueprint(blueprint);
|
||||
|
||||
var found = contentService.GetBlueprintsForContentTypes().ToArray();
|
||||
Assert.AreEqual(1, found.Length);
|
||||
|
||||
//ensures it's not found by normal content
|
||||
var contentFound = contentService.GetById(found[0].Id);
|
||||
Assert.IsNull(contentFound);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Delete_Blueprint()
|
||||
{
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
|
||||
var contentType = MockedContentTypes.CreateTextpageContentType();
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
var blueprint = MockedContent.CreateTextpageContent(contentType, "hello", -1);
|
||||
blueprint.SetValue("title", "blueprint 1");
|
||||
blueprint.SetValue("bodyText", "blueprint 2");
|
||||
blueprint.SetValue("keywords", "blueprint 3");
|
||||
blueprint.SetValue("description", "blueprint 4");
|
||||
|
||||
contentService.SaveBlueprint(blueprint);
|
||||
|
||||
contentService.DeleteBlueprint(blueprint);
|
||||
|
||||
var found = contentService.GetBlueprintsForContentTypes().ToArray();
|
||||
Assert.AreEqual(0, found.Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Create_Content_From_Blueprint()
|
||||
{
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
|
||||
var contentType = MockedContentTypes.CreateTextpageContentType();
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
var blueprint = MockedContent.CreateTextpageContent(contentType, "hello", -1);
|
||||
blueprint.SetValue("title", "blueprint 1");
|
||||
blueprint.SetValue("bodyText", "blueprint 2");
|
||||
blueprint.SetValue("keywords", "blueprint 3");
|
||||
blueprint.SetValue("description", "blueprint 4");
|
||||
|
||||
contentService.SaveBlueprint(blueprint);
|
||||
|
||||
var fromBlueprint = contentService.CreateContentFromBlueprint(blueprint, "hello world");
|
||||
contentService.Save(fromBlueprint);
|
||||
|
||||
Assert.IsTrue(fromBlueprint.HasIdentity);
|
||||
Assert.AreEqual("blueprint 1", fromBlueprint.Properties["title"].Value);
|
||||
Assert.AreEqual("blueprint 2", fromBlueprint.Properties["bodyText"].Value);
|
||||
Assert.AreEqual("blueprint 3", fromBlueprint.Properties["keywords"].Value);
|
||||
Assert.AreEqual("blueprint 4", fromBlueprint.Properties["description"].Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_All_Blueprints()
|
||||
{
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var contentTypeService = ServiceContext.ContentTypeService;
|
||||
|
||||
var ct1 = MockedContentTypes.CreateTextpageContentType("ct1");
|
||||
contentTypeService.Save(ct1);
|
||||
var ct2 = MockedContentTypes.CreateTextpageContentType("ct2");
|
||||
contentTypeService.Save(ct2);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
var blueprint = MockedContent.CreateTextpageContent(i % 2 == 0 ? ct1 : ct2, "hello" + i, -1);
|
||||
contentService.SaveBlueprint(blueprint);
|
||||
}
|
||||
|
||||
var found = contentService.GetBlueprintsForContentTypes().ToArray();
|
||||
Assert.AreEqual(10, found.Length);
|
||||
|
||||
found = contentService.GetBlueprintsForContentTypes(ct1.Id).ToArray();
|
||||
Assert.AreEqual(5, found.Length);
|
||||
|
||||
found = contentService.GetBlueprintsForContentTypes(ct2.Id).ToArray();
|
||||
Assert.AreEqual(5, found.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that we don't unpublish all nodes when a node is deleted that has an invalid path of -1
|
||||
|
||||
@@ -56,9 +56,9 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
//assert
|
||||
Assert.AreEqual(3, permissions.Count());
|
||||
Assert.AreEqual(17, permissions.ElementAt(0).AssignedPermissions.Count());
|
||||
Assert.AreEqual(17, permissions.ElementAt(1).AssignedPermissions.Count());
|
||||
Assert.AreEqual(17, permissions.ElementAt(2).AssignedPermissions.Count());
|
||||
Assert.AreEqual(18, permissions.ElementAt(0).AssignedPermissions.Count());
|
||||
Assert.AreEqual(18, permissions.ElementAt(1).AssignedPermissions.Count());
|
||||
Assert.AreEqual(18, permissions.ElementAt(2).AssignedPermissions.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function ContentEditController($rootScope, $scope, $routeParams, $q, $timeout, $window, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, keyboardService, umbModelMapper, editorState, $http) {
|
||||
|
||||
//setup scope vars
|
||||
$scope.defaultButton = null;
|
||||
$scope.subButtons = [];
|
||||
|
||||
$scope.page = {};
|
||||
$scope.page.loading = false;
|
||||
$scope.page.menu = {};
|
||||
$scope.page.menu.currentNode = null;
|
||||
$scope.page.menu.currentSection = appState.getSectionState("currentSection");
|
||||
$scope.page.listViewPath = null;
|
||||
$scope.page.isNew = $scope.isNew ? true : false;
|
||||
$scope.page.buttonGroupState = "init";
|
||||
|
||||
function init(content) {
|
||||
|
||||
var buttons = contentEditingHelper.configureContentEditorButtons({
|
||||
create: $scope.page.isNew,
|
||||
content: content,
|
||||
methods: {
|
||||
saveAndPublish: $scope.saveAndPublish,
|
||||
sendToPublish: $scope.sendToPublish,
|
||||
save: $scope.save,
|
||||
unPublish: $scope.unPublish
|
||||
}
|
||||
});
|
||||
$scope.defaultButton = buttons.defaultButton;
|
||||
$scope.subButtons = buttons.subButtons;
|
||||
|
||||
editorState.set($scope.content);
|
||||
|
||||
//We fetch all ancestors of the node to generate the footer breadcrumb navigation
|
||||
if (!$scope.page.isNew) {
|
||||
if (content.parentId && content.parentId !== -1) {
|
||||
entityResource.getAncestors(content.id, "document")
|
||||
.then(function (anc) {
|
||||
$scope.ancestors = anc;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Syncs the content item to it's tree node - this occurs on first load and after saving */
|
||||
function syncTreeNode(content, path, initialLoad) {
|
||||
|
||||
if (!$scope.content.isChildOfListView) {
|
||||
navigationService.syncTree({ tree: $scope.treeAlias, path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) {
|
||||
$scope.page.menu.currentNode = syncArgs.node;
|
||||
});
|
||||
}
|
||||
else if (initialLoad === true) {
|
||||
|
||||
//it's a child item, just sync the ui node to the parent
|
||||
navigationService.syncTree({ tree: $scope.treeAlias, path: path.substring(0, path.lastIndexOf(",")).split(","), forceReload: initialLoad !== true });
|
||||
|
||||
//if this is a child of a list view and it's the initial load of the editor, we need to get the tree node
|
||||
// from the server so that we can load in the actions menu.
|
||||
umbRequestHelper.resourcePromise(
|
||||
$http.get(content.treeNodeUrl),
|
||||
'Failed to retrieve data for child node ' + content.id).then(function (node) {
|
||||
$scope.page.menu.currentNode = node;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish
|
||||
function performSave(args) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
$scope.page.buttonGroupState = "busy";
|
||||
|
||||
contentEditingHelper.contentEditorPerformSave({
|
||||
statusMessage: args.statusMessage,
|
||||
saveMethod: args.saveMethod,
|
||||
scope: $scope,
|
||||
content: $scope.content,
|
||||
action: args.action
|
||||
}).then(function (data) {
|
||||
//success
|
||||
init($scope.content);
|
||||
syncTreeNode($scope.content, data.path);
|
||||
|
||||
$scope.page.buttonGroupState = "success";
|
||||
|
||||
deferred.resolve(data);
|
||||
}, function (err) {
|
||||
//error
|
||||
if (err) {
|
||||
editorState.set($scope.content);
|
||||
}
|
||||
|
||||
$scope.page.buttonGroupState = "error";
|
||||
|
||||
deferred.reject(err);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function resetLastListPageNumber(content) {
|
||||
// We're using rootScope to store the page number for list views, so if returning to the list
|
||||
// we can restore the page. If we've moved on to edit a piece of content that's not the list or it's children
|
||||
// we should remove this so as not to confuse if navigating to a different list
|
||||
if (!content.isChildOfListView && !content.isContainer) {
|
||||
$rootScope.lastListViewPageViewed = null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($scope.page.isNew) {
|
||||
|
||||
$scope.page.loading = true;
|
||||
|
||||
//we are creating so get an empty content item
|
||||
$scope.getScaffoldMethod()()
|
||||
.then(function (data) {
|
||||
|
||||
$scope.content = data;
|
||||
|
||||
init($scope.content);
|
||||
|
||||
resetLastListPageNumber($scope.content);
|
||||
|
||||
$scope.page.loading = false;
|
||||
|
||||
});
|
||||
}
|
||||
else {
|
||||
|
||||
$scope.page.loading = true;
|
||||
|
||||
//we are editing so get the content item from the server
|
||||
$scope.getMethod()($scope.contentId)
|
||||
.then(function (data) {
|
||||
|
||||
$scope.content = data;
|
||||
|
||||
if (data.isChildOfListView && data.trashed === false) {
|
||||
$scope.page.listViewPath = ($routeParams.page) ?
|
||||
"/content/content/edit/" + data.parentId + "?page=" + $routeParams.page :
|
||||
"/content/content/edit/" + data.parentId;
|
||||
}
|
||||
|
||||
init($scope.content);
|
||||
|
||||
//in one particular special case, after we've created a new item we redirect back to the edit
|
||||
// route but there might be server validation errors in the collection which we need to display
|
||||
// after the redirect, so we will bind all subscriptions which will show the server validation errors
|
||||
// if there are any and then clear them so the collection no longer persists them.
|
||||
serverValidationManager.executeAndClearAllSubscriptions();
|
||||
|
||||
syncTreeNode($scope.content, data.path, true);
|
||||
|
||||
resetLastListPageNumber($scope.content);
|
||||
|
||||
$scope.page.loading = false;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
$scope.unPublish = function () {
|
||||
|
||||
if (formHelper.submitForm({ scope: $scope, statusMessage: "Unpublishing...", skipValidation: true })) {
|
||||
|
||||
$scope.page.buttonGroupState = "busy";
|
||||
|
||||
contentResource.unPublish($scope.content.id)
|
||||
.then(function (data) {
|
||||
|
||||
formHelper.resetForm({ scope: $scope, notifications: data.notifications });
|
||||
|
||||
contentEditingHelper.handleSuccessfulSave({
|
||||
scope: $scope,
|
||||
savedContent: data,
|
||||
rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
|
||||
});
|
||||
|
||||
init($scope.content);
|
||||
|
||||
syncTreeNode($scope.content, data.path);
|
||||
|
||||
$scope.page.buttonGroupState = "success";
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
$scope.sendToPublish = function () {
|
||||
return performSave({ saveMethod: contentResource.sendToPublish, statusMessage: "Sending...", action: "sendToPublish" });
|
||||
};
|
||||
|
||||
$scope.saveAndPublish = function () {
|
||||
return performSave({ saveMethod: contentResource.publish, statusMessage: "Publishing...", action: "publish" });
|
||||
};
|
||||
|
||||
$scope.save = function () {
|
||||
return performSave({ saveMethod: $scope.saveMethod(), statusMessage: "Saving...", action: "save" });
|
||||
};
|
||||
|
||||
$scope.preview = function (content) {
|
||||
|
||||
|
||||
if (!$scope.busy) {
|
||||
|
||||
// Chromes popup blocker will kick in if a window is opened
|
||||
// outwith the initial scoped request. This trick will fix that.
|
||||
//
|
||||
var previewWindow = $window.open('preview/?id=' + content.id, 'umbpreview');
|
||||
$scope.save().then(function (data) {
|
||||
// Build the correct path so both /#/ and #/ work.
|
||||
var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?id=' + data.id;
|
||||
previewWindow.location.href = redirect;
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
function createDirective() {
|
||||
|
||||
var directive = {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
templateUrl: 'views/components/content/edit.html',
|
||||
controller: 'Umbraco.Editors.Content.EditorDirectiveController',
|
||||
scope: {
|
||||
contentId: "=",
|
||||
isNew: "=?",
|
||||
treeAlias: "@",
|
||||
page: "=?",
|
||||
saveMethod: "&",
|
||||
getMethod: "&",
|
||||
getScaffoldMethod: "&?"
|
||||
}
|
||||
};
|
||||
|
||||
return directive;
|
||||
|
||||
}
|
||||
|
||||
angular.module('umbraco.directives').controller('Umbraco.Editors.Content.EditorDirectiveController', ContentEditController);
|
||||
angular.module('umbraco.directives').directive('contentEditor', createDirective);
|
||||
|
||||
})();
|
||||
@@ -26,11 +26,9 @@
|
||||
function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
|
||||
/** internal method process the saving of data and post processing the result */
|
||||
function saveContentItem(content, action, files) {
|
||||
function saveContentItem(content, action, files, restApiUrl) {
|
||||
return umbRequestHelper.postSaveContent({
|
||||
restApiUrl: umbRequestHelper.getApiUrl(
|
||||
"contentApiBaseUrl",
|
||||
"PostSave"),
|
||||
restApiUrl: restApiUrl,
|
||||
content: content,
|
||||
action: action,
|
||||
files: files,
|
||||
@@ -269,6 +267,16 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
'Failed to delete item ' + id);
|
||||
},
|
||||
|
||||
deleteBlueprint: function (id) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.post(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"contentApiBaseUrl",
|
||||
"DeleteBlueprint",
|
||||
[{ id: id }])),
|
||||
'Failed to delete blueprint ' + id);
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.resources.contentResource#getById
|
||||
@@ -300,6 +308,16 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
'Failed to retrieve data for content id ' + id);
|
||||
},
|
||||
|
||||
getBlueprintById: function (id) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"contentApiBaseUrl",
|
||||
"GetBlueprintById",
|
||||
[{ id: id }])),
|
||||
'Failed to retrieve data for content id ' + id);
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.resources.contentResource#getByIds
|
||||
@@ -381,6 +399,17 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
'Failed to retrieve data for empty content item type ' + alias);
|
||||
},
|
||||
|
||||
getBlueprintScaffold: function (blueprintId) {
|
||||
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"contentApiBaseUrl",
|
||||
"GetEmpty",
|
||||
[{ blueprintId: blueprintId }])),
|
||||
'Failed to retrieve blueprint for id ' + blueprintId);
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.resources.contentResource#getNiceUrl
|
||||
@@ -564,9 +593,18 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
*
|
||||
*/
|
||||
save: function (content, isNew, files) {
|
||||
return saveContentItem(content, "save" + (isNew ? "New" : ""), files);
|
||||
var endpoint = umbRequestHelper.getApiUrl(
|
||||
"contentApiBaseUrl",
|
||||
"PostSave");
|
||||
return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint);
|
||||
},
|
||||
|
||||
saveBlueprint: function (content, isNew, files) {
|
||||
var endpoint = umbRequestHelper.getApiUrl(
|
||||
"contentApiBaseUrl",
|
||||
"PostSaveBlueprint");
|
||||
return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint);
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
@@ -597,7 +635,10 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
*
|
||||
*/
|
||||
publish: function (content, isNew, files) {
|
||||
return saveContentItem(content, "publish" + (isNew ? "New" : ""), files);
|
||||
var endpoint = umbRequestHelper.getApiUrl(
|
||||
"contentApiBaseUrl",
|
||||
"PostSave");
|
||||
return saveContentItem(content, "publish" + (isNew ? "New" : ""), files, endpoint);
|
||||
},
|
||||
|
||||
|
||||
@@ -628,7 +669,10 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
*
|
||||
*/
|
||||
sendToPublish: function (content, isNew, files) {
|
||||
return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files);
|
||||
var endpoint = umbRequestHelper.getApiUrl(
|
||||
"contentApiBaseUrl",
|
||||
"PostSave");
|
||||
return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files, endpoint);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -665,6 +709,17 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
[{ id: id }])),
|
||||
'Failed to publish content with id ' + id);
|
||||
|
||||
},
|
||||
|
||||
createBlueprintFromContent: function (contentId, name) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.post(
|
||||
umbRequestHelper.getApiUrl("contentApiBaseUrl", "CreateBlueprintFromContent", {
|
||||
contentId: contentId, name: name
|
||||
})
|
||||
),
|
||||
"Failed to create blueprint from content with id " + contentId
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -770,9 +770,9 @@ function umbDataFormatter() {
|
||||
var propTemplate = _.find(genericTab.properties, function (item) {
|
||||
return item.alias === "_umb_template";
|
||||
});
|
||||
saveModel.expireDate = propExpireDate.value;
|
||||
saveModel.releaseDate = propReleaseDate.value;
|
||||
saveModel.templateAlias = propTemplate.value;
|
||||
saveModel.expireDate = propExpireDate ? propExpireDate.value : null;
|
||||
saveModel.releaseDate = propReleaseDate ? propReleaseDate.value : null;
|
||||
saveModel.templateAlias = propTemplate ? propTemplate.value : null;
|
||||
|
||||
return saveModel;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ angular.module("umbraco.install").factory('installerService', function($rootScop
|
||||
"At least 4 people have the Umbraco logo tattooed on them",
|
||||
"'Umbraco' is the danish name for an allen key",
|
||||
"Umbraco has been around since 2005, that's a looong time in IT",
|
||||
"More than 550 people from all over the world meet each year in Denmark in June for our annual conference <a target='_blank' href='http://codegarden15.com'>CodeGarden</a>",
|
||||
"More than 550 people from all over the world meet each year in Denmark in June for our annual conference <a target='_blank' href='http://codegarden17.com'>CodeGarden</a>",
|
||||
"While you are installing Umbraco someone else on the other side of the planet is probably doing it too",
|
||||
"You can extend Umbraco without modifying the source code using either JavaScript or C#",
|
||||
"Umbraco was installed in more than 165 countries in 2015"
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
<div>
|
||||
|
||||
<umb-load-indicator ng-if="page.loading"></umb-load-indicator>
|
||||
|
||||
<form name="contentForm"
|
||||
ng-submit="save()"
|
||||
novalidate
|
||||
val-form-manager>
|
||||
|
||||
<umb-editor-view ng-if="!page.loading" umb-tabs>
|
||||
|
||||
<umb-editor-header
|
||||
menu="page.menu"
|
||||
name="content.name"
|
||||
tabs="content.tabs"
|
||||
hide-icon="true"
|
||||
hide-description="true"
|
||||
hide-alias="true">
|
||||
</umb-editor-header>
|
||||
|
||||
|
||||
<umb-editor-container>
|
||||
|
||||
<umb-tabs-content class="form-horizontal" view="true">
|
||||
<umb-tab id="tab{{tab.id}}" ng-repeat="tab in content.tabs" rel="{{tab.id}}">
|
||||
|
||||
<umb-property ng-repeat="property in tab.properties" property="property">
|
||||
<umb-editor model="property"></umb-editor>
|
||||
</umb-property>
|
||||
|
||||
</umb-tab>
|
||||
</umb-tabs-content>
|
||||
|
||||
</umb-editor-container>
|
||||
|
||||
|
||||
<umb-editor-footer>
|
||||
|
||||
<umb-editor-footer-content-left>
|
||||
|
||||
<umb-breadcrumbs
|
||||
ng-if="ancestors && ancestors.length > 0"
|
||||
ancestors="ancestors"
|
||||
entity-type="content">
|
||||
</umb-breadcrumbs>
|
||||
|
||||
</umb-editor-footer-content-left>
|
||||
|
||||
|
||||
<umb-editor-footer-content-right>
|
||||
|
||||
<umb-button
|
||||
ng-if="page.listViewPath"
|
||||
type="link"
|
||||
href="#{{page.listViewPath}}"
|
||||
label="Return to list"
|
||||
label-key="buttons_returnToList">
|
||||
</umb-button>
|
||||
|
||||
<umb-button
|
||||
ng-if="!page.isNew && content.allowPreview"
|
||||
type="button"
|
||||
button-style="info"
|
||||
action="preview(content)"
|
||||
label="Preview page"
|
||||
label-key="buttons_showPage">
|
||||
</umb-button>
|
||||
|
||||
<umb-button-group
|
||||
ng-if="defaultButton"
|
||||
default-button="defaultButton"
|
||||
sub-buttons="subButtons"
|
||||
state="page.buttonGroupState"
|
||||
direction="up"
|
||||
float="right">
|
||||
</umb-button-group>
|
||||
|
||||
|
||||
</umb-editor-footer-content-right>
|
||||
|
||||
</umb-editor-footer>
|
||||
|
||||
</umb-editor-view>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
@@ -6,11 +6,67 @@
|
||||
* @description
|
||||
* The controller for the content creation dialog
|
||||
*/
|
||||
function contentCreateController($scope, $routeParams, contentTypeResource, iconHelper) {
|
||||
|
||||
contentTypeResource.getAllowedTypes($scope.currentNode.id).then(function(data) {
|
||||
$scope.allowedTypes = iconHelper.formatContentTypeIcons(data);
|
||||
function contentCreateController($scope,
|
||||
$routeParams,
|
||||
contentTypeResource,
|
||||
iconHelper,
|
||||
$location,
|
||||
navigationService,
|
||||
blueprintConfig) {
|
||||
|
||||
function initialize() {
|
||||
contentTypeResource.getAllowedTypes($scope.currentNode.id).then(function (data) {
|
||||
$scope.allowedTypes = iconHelper.formatContentTypeIcons(data);
|
||||
});
|
||||
|
||||
$scope.selectContentType = true;
|
||||
$scope.selectBlueprint = false;
|
||||
$scope.allowBlank = blueprintConfig.allowBlank;
|
||||
}
|
||||
|
||||
function close() {
|
||||
navigationService.hideMenu();
|
||||
}
|
||||
|
||||
function createBlank(docType) {
|
||||
$location
|
||||
.path("/content/content/edit/" + $scope.currentNode.id)
|
||||
.search("doctype=" + docType.alias + "&create=true");
|
||||
close();
|
||||
}
|
||||
|
||||
function createOrSelectBlueprintIfAny(docType) {
|
||||
var blueprintIds = _.keys(docType.blueprints || {});
|
||||
$scope.docType = docType;
|
||||
if (blueprintIds.length) {
|
||||
if (blueprintConfig.skipSelect) {
|
||||
createFromBlueprint(blueprintIds[0]);
|
||||
} else {
|
||||
$scope.selectContentType = false;
|
||||
$scope.selectBlueprint = true;
|
||||
}
|
||||
} else {
|
||||
createBlank(docType);
|
||||
}
|
||||
}
|
||||
|
||||
function createFromBlueprint(blueprintId) {
|
||||
$location
|
||||
.path("/content/content/edit/" + $scope.currentNode.id)
|
||||
.search("doctype=" + $scope.docType.alias + "&create=true&blueprintId=" + blueprintId);
|
||||
close();
|
||||
}
|
||||
|
||||
$scope.createBlank = createBlank;
|
||||
$scope.createOrSelectBlueprintIfAny = createOrSelectBlueprintIfAny;
|
||||
$scope.createFromBlueprint = createFromBlueprint;
|
||||
|
||||
initialize();
|
||||
}
|
||||
|
||||
angular.module('umbraco').controller("Umbraco.Editors.Content.CreateController", contentCreateController);
|
||||
angular.module("umbraco").controller("Umbraco.Editors.Content.CreateController", contentCreateController);
|
||||
|
||||
angular.module("umbraco").value("blueprintConfig", {
|
||||
skipSelect: false,
|
||||
allowBlank: true
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
(function () {
|
||||
|
||||
function CreateBlueprintController(
|
||||
$scope,
|
||||
contentResource,
|
||||
notificationsService,
|
||||
navigationService,
|
||||
localizationService,
|
||||
formHelper,
|
||||
contentEditingHelper) {
|
||||
|
||||
$scope.message = {
|
||||
name: $scope.currentNode.name
|
||||
};
|
||||
|
||||
var successText = {};
|
||||
localizationService.localize("content_createBlueprintFrom", [$scope.message.name]).then(function (localizedVal) {
|
||||
$scope.label = localizedVal;
|
||||
});
|
||||
|
||||
|
||||
|
||||
$scope.cancel = function () {
|
||||
navigationService.hideMenu();
|
||||
};
|
||||
|
||||
$scope.create = function () {
|
||||
if (formHelper.submitForm({
|
||||
scope: $scope,
|
||||
formCtrl: this.blueprintForm,
|
||||
statusMessage: "Creating blueprint..."
|
||||
})) {
|
||||
|
||||
contentResource.createBlueprintFromContent($scope.currentNode.id, $scope.message.name)
|
||||
.then(function(data) {
|
||||
|
||||
formHelper.resetForm({ scope: $scope, notifications: data.notifications });
|
||||
|
||||
navigationService.hideMenu();
|
||||
},
|
||||
function(err) {
|
||||
|
||||
contentEditingHelper.handleSaveError({
|
||||
redirectOnFailure: false,
|
||||
err: err
|
||||
});
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
angular.module("umbraco").controller("Umbraco.Editors.Content.CreateBlueprintController", CreateBlueprintController);
|
||||
|
||||
}());
|
||||
@@ -6,227 +6,21 @@
|
||||
* @description
|
||||
* The controller for the content editor
|
||||
*/
|
||||
function ContentEditController($scope, $rootScope, $routeParams, $q, $timeout, $window, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, keyboardService, umbModelMapper, editorState, $http) {
|
||||
function ContentEditController($scope, $routeParams, contentResource) {
|
||||
|
||||
//setup scope vars
|
||||
$scope.defaultButton = null;
|
||||
$scope.subButtons = [];
|
||||
|
||||
$scope.page = {};
|
||||
$scope.page.loading = false;
|
||||
$scope.page.menu = {};
|
||||
$scope.page.menu.currentNode = null;
|
||||
$scope.page.menu.currentSection = appState.getSectionState("currentSection");
|
||||
$scope.page.listViewPath = null;
|
||||
$scope.page.isNew = $routeParams.create;
|
||||
$scope.page.buttonGroupState = "init";
|
||||
|
||||
function init(content) {
|
||||
|
||||
var buttons = contentEditingHelper.configureContentEditorButtons({
|
||||
create: $routeParams.create,
|
||||
content: content,
|
||||
methods: {
|
||||
saveAndPublish: $scope.saveAndPublish,
|
||||
sendToPublish: $scope.sendToPublish,
|
||||
save: $scope.save,
|
||||
unPublish: $scope.unPublish
|
||||
}
|
||||
});
|
||||
$scope.defaultButton = buttons.defaultButton;
|
||||
$scope.subButtons = buttons.subButtons;
|
||||
|
||||
editorState.set($scope.content);
|
||||
|
||||
//We fetch all ancestors of the node to generate the footer breadcrumb navigation
|
||||
if (!$routeParams.create) {
|
||||
if (content.parentId && content.parentId != -1) {
|
||||
entityResource.getAncestors(content.id, "document")
|
||||
.then(function (anc) {
|
||||
$scope.ancestors = anc;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Syncs the content item to it's tree node - this occurs on first load and after saving */
|
||||
function syncTreeNode(content, path, initialLoad) {
|
||||
|
||||
if (!$scope.content.isChildOfListView) {
|
||||
navigationService.syncTree({ tree: "content", path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) {
|
||||
$scope.page.menu.currentNode = syncArgs.node;
|
||||
});
|
||||
}
|
||||
else if (initialLoad === true) {
|
||||
|
||||
//it's a child item, just sync the ui node to the parent
|
||||
navigationService.syncTree({ tree: "content", path: path.substring(0, path.lastIndexOf(",")).split(","), forceReload: initialLoad !== true });
|
||||
|
||||
//if this is a child of a list view and it's the initial load of the editor, we need to get the tree node
|
||||
// from the server so that we can load in the actions menu.
|
||||
umbRequestHelper.resourcePromise(
|
||||
$http.get(content.treeNodeUrl),
|
||||
'Failed to retrieve data for child node ' + content.id).then(function (node) {
|
||||
$scope.page.menu.currentNode = node;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish
|
||||
function performSave(args) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
$scope.page.buttonGroupState = "busy";
|
||||
|
||||
contentEditingHelper.contentEditorPerformSave({
|
||||
statusMessage: args.statusMessage,
|
||||
saveMethod: args.saveMethod,
|
||||
scope: $scope,
|
||||
content: $scope.content,
|
||||
action: args.action
|
||||
}).then(function (data) {
|
||||
//success
|
||||
init($scope.content);
|
||||
syncTreeNode($scope.content, data.path);
|
||||
|
||||
$scope.page.buttonGroupState = "success";
|
||||
|
||||
deferred.resolve(data);
|
||||
}, function (err) {
|
||||
//error
|
||||
if (err) {
|
||||
editorState.set($scope.content);
|
||||
}
|
||||
|
||||
$scope.page.buttonGroupState = "error";
|
||||
|
||||
deferred.reject(err);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function resetLastListPageNumber(content) {
|
||||
// We're using rootScope to store the page number for list views, so if returning to the list
|
||||
// we can restore the page. If we've moved on to edit a piece of content that's not the list or it's children
|
||||
// we should remove this so as not to confuse if navigating to a different list
|
||||
if (!content.isChildOfListView && !content.isContainer) {
|
||||
$rootScope.lastListViewPageViewed = null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($routeParams.create) {
|
||||
|
||||
$scope.page.loading = true;
|
||||
|
||||
//we are creating so get an empty content item
|
||||
contentResource.getScaffold($routeParams.id, $routeParams.doctype)
|
||||
.then(function (data) {
|
||||
|
||||
$scope.content = data;
|
||||
|
||||
init($scope.content);
|
||||
|
||||
resetLastListPageNumber($scope.content);
|
||||
|
||||
$scope.page.loading = false;
|
||||
|
||||
});
|
||||
}
|
||||
else {
|
||||
|
||||
$scope.page.loading = true;
|
||||
|
||||
//we are editing so get the content item from the server
|
||||
contentResource.getById($routeParams.id)
|
||||
.then(function (data) {
|
||||
|
||||
$scope.content = data;
|
||||
|
||||
if (data.isChildOfListView && data.trashed === false) {
|
||||
$scope.page.listViewPath = ($routeParams.page)
|
||||
? "/content/content/edit/" + data.parentId + "?page=" + $routeParams.page
|
||||
: "/content/content/edit/" + data.parentId;
|
||||
}
|
||||
|
||||
init($scope.content);
|
||||
|
||||
//in one particular special case, after we've created a new item we redirect back to the edit
|
||||
// route but there might be server validation errors in the collection which we need to display
|
||||
// after the redirect, so we will bind all subscriptions which will show the server validation errors
|
||||
// if there are any and then clear them so the collection no longer persists them.
|
||||
serverValidationManager.executeAndClearAllSubscriptions();
|
||||
|
||||
syncTreeNode($scope.content, data.path, true);
|
||||
|
||||
resetLastListPageNumber($scope.content);
|
||||
|
||||
$scope.page.loading = false;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
$scope.unPublish = function () {
|
||||
|
||||
if (formHelper.submitForm({ scope: $scope, statusMessage: "Unpublishing...", skipValidation: true })) {
|
||||
|
||||
$scope.page.buttonGroupState = "busy";
|
||||
|
||||
contentResource.unPublish($scope.content.id)
|
||||
.then(function (data) {
|
||||
|
||||
formHelper.resetForm({ scope: $scope, notifications: data.notifications });
|
||||
|
||||
contentEditingHelper.handleSuccessfulSave({
|
||||
scope: $scope,
|
||||
savedContent: data,
|
||||
rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
|
||||
});
|
||||
|
||||
init($scope.content);
|
||||
|
||||
syncTreeNode($scope.content, data.path);
|
||||
|
||||
$scope.page.buttonGroupState = "success";
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
$scope.sendToPublish = function () {
|
||||
return performSave({ saveMethod: contentResource.sendToPublish, statusMessage: "Sending...", action: "sendToPublish" });
|
||||
};
|
||||
|
||||
$scope.saveAndPublish = function () {
|
||||
return performSave({ saveMethod: contentResource.publish, statusMessage: "Publishing...", action: "publish" });
|
||||
};
|
||||
|
||||
$scope.save = function () {
|
||||
return performSave({ saveMethod: contentResource.save, statusMessage: "Saving...", action: "save" });
|
||||
};
|
||||
|
||||
$scope.preview = function (content) {
|
||||
|
||||
|
||||
if (!$scope.busy) {
|
||||
|
||||
// Chromes popup blocker will kick in if a window is opened
|
||||
// outwith the initial scoped request. This trick will fix that.
|
||||
//
|
||||
var previewWindow = $window.open('preview/?id=' + content.id, 'umbpreview');
|
||||
$scope.save().then(function (data) {
|
||||
// Build the correct path so both /#/ and #/ work.
|
||||
var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?id=' + data.id;
|
||||
previewWindow.location.href = redirect;
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
function scaffoldEmpty() {
|
||||
return contentResource.getScaffold($routeParams.id, $routeParams.doctype);
|
||||
}
|
||||
function scaffoldBlueprint() {
|
||||
return contentResource.getBlueprintScaffold($routeParams.blueprintId);
|
||||
}
|
||||
|
||||
$scope.contentId = $routeParams.id;
|
||||
$scope.saveMethod = contentResource.save;
|
||||
$scope.getMethod = contentResource.getById;
|
||||
$scope.getScaffoldMethod = $routeParams.blueprintId ? scaffoldBlueprint : scaffoldEmpty;
|
||||
$scope.page = $routeParams.page;
|
||||
$scope.isNew = $routeParams.create;
|
||||
}
|
||||
|
||||
angular.module("umbraco").controller("Umbraco.Editors.Content.EditController", ContentEditController);
|
||||
|
||||
@@ -1,45 +1,53 @@
|
||||
<div class="umb-dialog-body with-footer" ng-controller="Umbraco.Editors.Content.CreateController" ng-cloak>
|
||||
|
||||
<div class="umb-pane">
|
||||
<h5><localize key="create_createUnder">Create a page under</localize> {{currentNode.name}}</h5>
|
||||
|
||||
<p class="abstract" ng-if="allowedTypes && allowedTypes.length === 0">
|
||||
<localize key="create_noDocumentTypes" />
|
||||
</p>
|
||||
<div class="umb-pane">
|
||||
<h5 ng-show="selectContentType"><localize key="create_createUnder">Create a page under</localize> {{currentNode.name}}</h5>
|
||||
<h5 ng-show="selectBlueprint"><localize key="content_selectBlueprint">Select a blueprint</localize></h5>
|
||||
|
||||
<ul class="umb-actions umb-actions-child">
|
||||
<p class="abstract" ng-if="allowedTypes && allowedTypes.length === 0">
|
||||
<localize key="create_noDocumentTypes" />
|
||||
</p>
|
||||
|
||||
<li ng-repeat="docType in allowedTypes | orderBy:'name':false">
|
||||
<a href="#content/content/edit/{{currentNode.id}}?doctype={{docType.alias}}&create=true" ng-click="nav.hideNavigation()">
|
||||
|
||||
<i class="large {{docType.icon}}"></i>
|
||||
<ul class="umb-actions umb-actions-child" ng-show="selectContentType">
|
||||
|
||||
<span class="menu-label">
|
||||
{{docType.name}}
|
||||
<small>
|
||||
{{docType.description}}
|
||||
</small>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<!--
|
||||
<li class="add">
|
||||
<a href="#settings/documenttype/create/{{currentNode.id}}?doctype={{docType.alias}}&create=true" ng-click="nav.hideNavigation()">
|
||||
<i class="large icon-add"></i>
|
||||
<span class="menu-label">
|
||||
Create a new document type
|
||||
<small>
|
||||
Design and configure a new document type
|
||||
</small>
|
||||
</span>
|
||||
</a>
|
||||
</li> -->
|
||||
</ul>
|
||||
</div>
|
||||
<li ng-repeat="docType in allowedTypes | orderBy:'name':false">
|
||||
<a ng-click="createOrSelectBlueprintIfAny(docType)">
|
||||
<i class="large {{docType.icon}}"></i>
|
||||
<span class="menu-label">
|
||||
{{docType.name}}
|
||||
<small>
|
||||
{{docType.description}}
|
||||
</small>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
<ul class="umb-actions umb-actions-child" ng-show="selectBlueprint">
|
||||
|
||||
<li ng-repeat="(key, value) in docType.blueprints | orderBy:'name':false">
|
||||
<a ng-click="createFromBlueprint(key)">
|
||||
<i class="large {{docType.icon}}"></i>
|
||||
<span class="menu-label">
|
||||
{{value}}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li ng-show="allowBlank">
|
||||
<a ng-click="createBlank(docType)">
|
||||
<i class="large {{docType.icon}}"></i>
|
||||
<span class="menu-label">
|
||||
<localize key="content_blankBlueprint">Blank</localize>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="umb-dialog-footer btn-toolbar umb-btn-toolbar">
|
||||
<button class="btn btn-info" ng-click="nav.hideDialog(true)">
|
||||
<localize key="buttons_somethingElse">Do something else</localize>
|
||||
</button>
|
||||
<button class="btn btn-info" ng-click="nav.hideDialog(true)">
|
||||
<localize key="buttons_somethingElse">Do something else</localize>
|
||||
</button>
|
||||
</div>
|
||||
@@ -0,0 +1,32 @@
|
||||
<div ng-controller="Umbraco.Editors.Content.CreateBlueprintController">
|
||||
|
||||
<form name="blueprintForm"
|
||||
novalidate
|
||||
ng-submit="create()"
|
||||
val-form-manager>
|
||||
|
||||
<div class="umbracoDialog umb-dialog-body with-footer"
|
||||
ng-cloak>
|
||||
<div class="umb-pane">
|
||||
<umb-control-group label="{{label}}" hide-label="false">
|
||||
<input type="text" name="name" ng-model="message.name" class="umb-textstring textstring input-block-level"
|
||||
val-server-field="name"
|
||||
required />
|
||||
<span class="help-inline" val-msg-for="name" val-toggle-msg="required"><localize key="required" /></span>
|
||||
<span class="help-inline" val-msg-for="name" val-toggle-msg="valServerField"></span>
|
||||
</umb-control-group>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="umb-dialog-footer btn-toolbar umb-btn-toolbar">
|
||||
<button type="button" class="btn umb-button btn-info" ng-click="cancel()">
|
||||
<localize key="general_cancel">Cancel</localize>
|
||||
</button>
|
||||
<button type="submit" class="btn umb-button btn-success">
|
||||
<localize key="general_create">Create blueprint</localize>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
@@ -1,86 +1,11 @@
|
||||
<div ng-controller="Umbraco.Editors.Content.EditController">
|
||||
|
||||
<umb-load-indicator ng-if="page.loading"></umb-load-indicator>
|
||||
|
||||
<form name="contentForm"
|
||||
ng-submit="save()"
|
||||
novalidate
|
||||
val-form-manager>
|
||||
|
||||
<umb-editor-view ng-if="!page.loading" umb-tabs>
|
||||
|
||||
<umb-editor-header
|
||||
menu="page.menu"
|
||||
name="content.name"
|
||||
tabs="content.tabs"
|
||||
hide-icon="true"
|
||||
hide-description="true"
|
||||
hide-alias="true">
|
||||
</umb-editor-header>
|
||||
|
||||
|
||||
<umb-editor-container>
|
||||
|
||||
<umb-tabs-content class="form-horizontal" view="true">
|
||||
<umb-tab id="tab{{tab.id}}" ng-repeat="tab in content.tabs" rel="{{tab.id}}">
|
||||
|
||||
<umb-property ng-repeat="property in tab.properties" property="property">
|
||||
<umb-editor model="property"></umb-editor>
|
||||
</umb-property>
|
||||
|
||||
</umb-tab>
|
||||
</umb-tabs-content>
|
||||
|
||||
</umb-editor-container>
|
||||
|
||||
|
||||
<umb-editor-footer>
|
||||
|
||||
<umb-editor-footer-content-left>
|
||||
|
||||
<umb-breadcrumbs
|
||||
ng-if="ancestors && ancestors.length > 0"
|
||||
ancestors="ancestors"
|
||||
entity-type="content">
|
||||
</umb-breadcrumbs>
|
||||
|
||||
</umb-editor-footer-content-left>
|
||||
|
||||
|
||||
<umb-editor-footer-content-right>
|
||||
|
||||
<umb-button
|
||||
ng-if="page.listViewPath"
|
||||
type="link"
|
||||
href="#{{page.listViewPath}}"
|
||||
label="Return to list"
|
||||
label-key="buttons_returnToList">
|
||||
</umb-button>
|
||||
|
||||
<umb-button
|
||||
ng-if="!page.isNew && content.allowPreview"
|
||||
type="button"
|
||||
button-style="info"
|
||||
action="preview(content)"
|
||||
label="Preview page"
|
||||
label-key="buttons_showPage">
|
||||
</umb-button>
|
||||
|
||||
<umb-button-group
|
||||
ng-if="defaultButton"
|
||||
default-button="defaultButton"
|
||||
sub-buttons="subButtons"
|
||||
state="page.buttonGroupState"
|
||||
direction="up"
|
||||
float="right">
|
||||
</umb-button-group>
|
||||
|
||||
|
||||
</umb-editor-footer-content-right>
|
||||
|
||||
</umb-editor-footer>
|
||||
|
||||
</umb-editor-view>
|
||||
|
||||
</form>
|
||||
<content-editor create-options="createOptions"
|
||||
content-id="contentId"
|
||||
page="page"
|
||||
save-method="saveMethod"
|
||||
get-method="getMethod"
|
||||
get-scaffold-method="getScaffoldMethod"
|
||||
tree-alias="content"
|
||||
is-new="isNew">
|
||||
</content-editor>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* @ngdoc controller
|
||||
* @name Umbraco.Editors.ContentBlueprint.DeleteController
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* The controller for deleting content blueprints
|
||||
*/
|
||||
function ContentBlueprintDeleteController($scope, contentResource, treeService, navigationService) {
|
||||
|
||||
$scope.performDelete = function() {
|
||||
|
||||
//mark it for deletion (used in the UI)
|
||||
$scope.currentNode.loading = true;
|
||||
|
||||
contentResource.deleteBlueprint($scope.currentNode.id)
|
||||
.then(function() {
|
||||
$scope.currentNode.loading = false;
|
||||
//get the root node before we remove it
|
||||
var rootNode = treeService.getTreeRoot($scope.currentNode);
|
||||
//TODO: Need to sync tree, etc...
|
||||
treeService.removeNode($scope.currentNode);
|
||||
navigationService.hideMenu();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.cancel = function() {
|
||||
navigationService.hideDialog();
|
||||
};
|
||||
}
|
||||
|
||||
angular.module("umbraco").controller("Umbraco.Editors.ContentBlueprint.DeleteController", ContentBlueprintDeleteController);
|
||||
@@ -0,0 +1,12 @@
|
||||
<div class="umb-dialog umb-pane" ng-controller="Umbraco.Editors.ContentBlueprint.DeleteController">
|
||||
<div class="umb-dialog-body">
|
||||
|
||||
<p class="umb-abstract">
|
||||
<localize key="defaultdialogs_confirmdelete">Are you sure you want to delete</localize> <strong>{{currentNode.name}}</strong> ?
|
||||
</p>
|
||||
|
||||
<umb-confirm on-confirm="performDelete" on-cancel="cancel">
|
||||
</umb-confirm>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @ngdoc controller
|
||||
* @name Umbraco.Editors.Content.EditController
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* The controller for the content editor
|
||||
*/
|
||||
function ContentBlueprintEditController($scope, $routeParams, contentResource) {
|
||||
var excludedProps = ["_umb_urls", "_umb_releasedate", "_umb_expiredate", "_umb_template"];
|
||||
|
||||
function getScaffold() {
|
||||
return contentResource.getScaffold(-1, $routeParams.doctype)
|
||||
.then(function (scaffold) {
|
||||
var lastTab = scaffold.tabs[scaffold.tabs.length - 1];
|
||||
lastTab.properties = _.filter(lastTab.properties,
|
||||
function(p) {
|
||||
return excludedProps.indexOf(p.alias) === -1;
|
||||
});
|
||||
scaffold.allowPreview = false;
|
||||
scaffold.allowedActions = ["A", "S", "C"];
|
||||
|
||||
return scaffold;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.contentId = $routeParams.id;
|
||||
$scope.isNew = $routeParams.id === "-1";
|
||||
$scope.saveMethod = contentResource.saveBlueprint;
|
||||
$scope.getMethod = contentResource.getBlueprintById;
|
||||
$scope.getScaffoldMethod = getScaffold;
|
||||
}
|
||||
|
||||
angular.module("umbraco").controller("Umbraco.Editors.ContentBlueprint.EditController", ContentBlueprintEditController);
|
||||
@@ -0,0 +1,9 @@
|
||||
<div ng-controller="Umbraco.Editors.ContentBlueprint.EditController">
|
||||
<content-editor content-id="contentId"
|
||||
save-method="saveMethod"
|
||||
get-method="getMethod"
|
||||
get-scaffold-method="getScaffoldMethod"
|
||||
is-new="isNew"
|
||||
tree-alias="contentblueprints">
|
||||
</content-editor>
|
||||
</div>
|
||||
@@ -32,7 +32,7 @@ module.exports = function(karma) {
|
||||
'test/config/app.unit.js',
|
||||
'src/common/mocks/umbraco.servervariables.js',
|
||||
|
||||
'src/common/directives/*.js',
|
||||
'src/common/directives/**/*.js',
|
||||
'src/common/filters/*.js',
|
||||
'src/common/services/*.js',
|
||||
'src/common/security/*.js',
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
(function() {
|
||||
|
||||
describe("create content dialog",
|
||||
function() {
|
||||
|
||||
var scope,
|
||||
allowedTypes = [
|
||||
{ id: 1, alias: "x" },
|
||||
{ id: 2, alias: "y", blueprints: { "1": "a", "2": "b" } }
|
||||
],
|
||||
location,
|
||||
searcher,
|
||||
controller,
|
||||
rootScope,
|
||||
contentTypeResource;
|
||||
|
||||
beforeEach(module("umbraco"));
|
||||
|
||||
function initialize(blueprintConfig) {
|
||||
scope = rootScope.$new();
|
||||
scope.currentNode = { id: 1234 };
|
||||
var dependencies = {
|
||||
$scope: scope,
|
||||
contentTypeResource: contentTypeResource
|
||||
};
|
||||
if (blueprintConfig) {
|
||||
dependencies.blueprintConfig = blueprintConfig;
|
||||
}
|
||||
controller("Umbraco.Editors.Content.CreateController",
|
||||
dependencies);
|
||||
|
||||
scope.$digest();
|
||||
|
||||
}
|
||||
|
||||
beforeEach(inject(function($controller, $rootScope, $q, $location) {
|
||||
contentTypeResource = {
|
||||
getAllowedTypes: function() {
|
||||
var def = $q.defer();
|
||||
def.resolve(allowedTypes);
|
||||
return def.promise;
|
||||
}
|
||||
};
|
||||
location = $location;
|
||||
controller = $controller;
|
||||
rootScope = $rootScope;
|
||||
|
||||
searcher = { search: function() {} };
|
||||
spyOn(location, "path").andReturn(searcher);
|
||||
spyOn(searcher, "search");
|
||||
|
||||
initialize();
|
||||
}));
|
||||
|
||||
it("shows available child document types for the given node",
|
||||
function() {
|
||||
expect(scope.selectContentType).toBe(true);
|
||||
expect(scope.allowedTypes).toBe(allowedTypes);
|
||||
});
|
||||
|
||||
it("creates content directly when there are no blueprints",
|
||||
function() {
|
||||
scope.createOrSelectBlueprintIfAny(allowedTypes[0]);
|
||||
|
||||
expect(location.path).toHaveBeenCalledWith("/content/content/edit/1234");
|
||||
expect(searcher.search).toHaveBeenCalledWith("doctype=x&create=true");
|
||||
});
|
||||
|
||||
it("shows list of blueprints when there are some",
|
||||
function() {
|
||||
scope.createOrSelectBlueprintIfAny(allowedTypes[1]);
|
||||
expect(scope.selectContentType).toBe(false);
|
||||
expect(scope.selectBlueprint).toBe(true);
|
||||
expect(scope.docType).toBe(allowedTypes[1]);
|
||||
});
|
||||
|
||||
it("creates blueprint when selected",
|
||||
function() {
|
||||
scope.createOrSelectBlueprintIfAny(allowedTypes[1]);
|
||||
scope.createFromBlueprint("1");
|
||||
|
||||
expect(location.path).toHaveBeenCalledWith("/content/content/edit/1234");
|
||||
expect(searcher.search).toHaveBeenCalledWith("doctype=y&create=true&blueprintId=1");
|
||||
});
|
||||
|
||||
it("skips selection and creates first blueprint when configured to",
|
||||
function() {
|
||||
initialize({
|
||||
allowBlank: true,
|
||||
skipSelect: true
|
||||
});
|
||||
|
||||
scope.createOrSelectBlueprintIfAny(allowedTypes[1]);
|
||||
|
||||
expect(location.path).toHaveBeenCalledWith("/content/content/edit/1234");
|
||||
expect(searcher.search).toHaveBeenCalledWith("doctype=y&create=true&blueprintId=1");
|
||||
});
|
||||
|
||||
it("allows blank to be selected",
|
||||
function() {
|
||||
expect(scope.allowBlank).toBe(true);
|
||||
});
|
||||
|
||||
it("creates blank when selected",
|
||||
function() {
|
||||
scope.createBlank(allowedTypes[1]);
|
||||
|
||||
expect(location.path).toHaveBeenCalledWith("/content/content/edit/1234");
|
||||
expect(searcher.search).toHaveBeenCalledWith("doctype=y&create=true");
|
||||
});
|
||||
|
||||
it("hides blank when configured to",
|
||||
function() {
|
||||
initialize({
|
||||
allowBlank: false,
|
||||
skipSelect: false
|
||||
});
|
||||
|
||||
expect(scope.allowBlank).toBe(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}());
|
||||
@@ -1,12 +1,16 @@
|
||||
// The content editor has been wrapped in a directive
|
||||
// The current setup will have problems with loading the HTML etc.
|
||||
// These tests are therefore ignored for now.
|
||||
|
||||
describe('edit content controller tests', function () {
|
||||
var scope, controller, routeParams, httpBackend;
|
||||
var scope, controller, routeParams, httpBackend, wasSaved, q;
|
||||
routeParams = {id: 1234, create: false};
|
||||
|
||||
beforeEach(module('umbraco'));
|
||||
|
||||
//inject the contentMocks service
|
||||
beforeEach(inject(function ($rootScope, $controller, angularHelper, $httpBackend, contentMocks, entityMocks, mocksUtils, localizationMocks) {
|
||||
|
||||
beforeEach(inject(function ($rootScope, $q, $controller, $compile, angularHelper, $httpBackend, contentMocks, entityMocks, mocksUtils, localizationMocks) {
|
||||
q = $q;
|
||||
//for these tests we don't want any authorization to occur
|
||||
mocksUtils.disableAuth();
|
||||
|
||||
@@ -20,27 +24,32 @@ describe('edit content controller tests', function () {
|
||||
localizationMocks.register();
|
||||
|
||||
//this controller requires an angular form controller applied to it
|
||||
scope.contentForm = angularHelper.getNullForm("contentForm");
|
||||
|
||||
controller = $controller('Umbraco.Editors.Content.EditController', {
|
||||
scope.contentForm = angularHelper.getNullForm("contentForm");
|
||||
|
||||
var deferred = $q.defer();
|
||||
wasSaved = false;
|
||||
scope.saveMethod = function() { wasSaved = true; };
|
||||
scope.getMethod = function() { return function() { return deferred.promise; } };
|
||||
scope.treeAlias = "content";
|
||||
|
||||
controller = $controller('Umbraco.Editors.Content.EditorDirectiveController', {
|
||||
$scope: scope,
|
||||
$routeParams: routeParams
|
||||
});
|
||||
|
||||
//For controller tests its easiest to have the digest and flush happen here
|
||||
//since its intially always the same $http calls made
|
||||
|
||||
//scope.$digest resolves the promise against the httpbackend
|
||||
scope.$digest();
|
||||
//httpbackend.flush() resolves all request against the httpbackend
|
||||
//to fake a async response, (which is what happens on a real setup)
|
||||
httpBackend.flush();
|
||||
|
||||
// Resolve the get method
|
||||
deferred.resolve(mocksUtils.getMockContent(1234));
|
||||
|
||||
//scope.$digest resolves the promise
|
||||
scope.$digest();
|
||||
}));
|
||||
|
||||
describe('content edit controller save and publish', function () {
|
||||
|
||||
it('it should have an content object', function() {
|
||||
|
||||
it('it should have an content object', function() {
|
||||
//controller should have a content object
|
||||
expect(scope.content).toNotBe(undefined);
|
||||
|
||||
|
||||
@@ -2376,9 +2376,9 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.1\x86\*.* "$(TargetDir)x86\"
|
||||
<WebProjectProperties>
|
||||
<UseIIS>True</UseIIS>
|
||||
<AutoAssignPort>True</AutoAssignPort>
|
||||
<DevelopmentServerPort>7630</DevelopmentServerPort>
|
||||
<DevelopmentServerPort>7640</DevelopmentServerPort>
|
||||
<DevelopmentServerVPath>/</DevelopmentServerVPath>
|
||||
<IISUrl>http://localhost:7630</IISUrl>
|
||||
<IISUrl>http://localhost:7640</IISUrl>
|
||||
<NTLMAuthentication>False</NTLMAuthentication>
|
||||
<UseCustomServer>False</UseCustomServer>
|
||||
<CustomServerUrl>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
<add application="settings" alias="languages" title="Languages" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.LanguageTreeController, umbraco" sortOrder="5" />
|
||||
<add application="settings" alias="dictionary" title="Dictionary" type="umbraco.loadDictionary, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="6" />
|
||||
<add initialize="true" sortOrder="7" alias="mediaTypes" application="settings" title="Media Types" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.MediaTypeTreeController, umbraco" />
|
||||
<add initialize="true" sortOrder="8" alias="contentBlueprints" application="settings" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.ContentBlueprintTreeController, umbraco" />
|
||||
<!--Developer-->
|
||||
<add initialize="true" sortOrder="0" alias="packager" application="developer" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.PackagesTreeController, umbraco" />
|
||||
<add initialize="true" sortOrder="1" alias="dataTypes" application="developer" title="Data Types" iconClosed="icon-folder" iconOpen="icon-folder" type="Umbraco.Web.Trees.DataTypeTreeController, umbraco" />
|
||||
@@ -40,5 +41,5 @@
|
||||
<add initialize="true" sortOrder="2" alias="datasource" application="forms" title="Datasources" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Forms.Web.Trees.DataSourceTreeController, Umbraco.Forms.Web" />
|
||||
<add initialize="true" sortOrder="0" alias="form" application="forms" title="Forms" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Forms.Web.Trees.FormTreeController, Umbraco.Forms.Web" />
|
||||
<add initialize="true" sortOrder="3" alias="prevaluesource" application="forms" title="Prevalue sources" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Forms.Web.Trees.PreValueSourceTreeController, Umbraco.Forms.Web" />
|
||||
<add initialize="true" sortOrder="3" alias="formsecurity" application="users" title="Forms Security" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Forms.Web.Trees.FormSecurityTreeController, Umbraco.Forms.Web" />
|
||||
<add initialize="true" sortOrder="3" alias="formsecurity" application="users" title="Forms Security" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Forms.Web.Trees.FormSecurityTreeController, Umbraco.Forms.Web" />
|
||||
</trees>
|
||||
@@ -40,6 +40,7 @@
|
||||
<key alias="translate">Translate</key>
|
||||
<key alias="update">Update</key>
|
||||
<key alias="defaultValue">Default value</key>
|
||||
<key alias="createblueprint">Create blueprint</key>
|
||||
</area>
|
||||
<area alias="assignDomain">
|
||||
<key alias="permissionDenied">Permission denied.</key>
|
||||
@@ -177,6 +178,12 @@
|
||||
<key alias="target" version="7.0">Target</key>
|
||||
<key alias="scheduledPublishServerTime">This translates to the following time on the server:</key>
|
||||
<key alias="scheduledPublishDocumentation"><![CDATA[<a href="https://our.umbraco.org/documentation/Getting-Started/Data/Scheduled-Publishing/#timezones" target="_blank">What does this mean?</a>]]></key>
|
||||
<key alias="createBlueprintFrom">Create a blueprint from %0%</key>
|
||||
<key alias="blankBlueprint">Blank</key>
|
||||
<key alias="selectBlueprint">Select a blueprint</key>
|
||||
<key alias="createdBlueprintHeading">Blueprint created</key>
|
||||
<key alias="createdBlueprintMessage">Blueprint was created from %0%</key>
|
||||
<key alias="duplicateBlueprintMessage">Another Blueprint with the same name already exists.</key>
|
||||
</area>
|
||||
<area alias="media">
|
||||
<key alias="clickToUpload">Click to upload</key>
|
||||
@@ -1340,6 +1347,7 @@ To manage your website, simply open the Umbraco back office and start adding con
|
||||
<key alias="uploadTranslationXml">Upload translation XML</key>
|
||||
</area>
|
||||
<area alias="treeHeaders">
|
||||
<key alias="contentBlueprints">Content Blueprints</key>
|
||||
<key alias="cacheBrowser">Cache Browser</key>
|
||||
<key alias="contentRecycleBin">Recycle Bin</key>
|
||||
<key alias="createdPackages">Created packages</key>
|
||||
|
||||
@@ -1337,6 +1337,7 @@ To manage your website, simply open the Umbraco back office and start adding con
|
||||
<key alias="uploadTranslationXml">Upload translation XML</key>
|
||||
</area>
|
||||
<area alias="treeHeaders">
|
||||
<key alias="contentBlueprints">Content Blueprints</key>
|
||||
<key alias="cacheBrowser">Cache Browser</key>
|
||||
<key alias="contentRecycleBin">Recycle Bin</key>
|
||||
<key alias="createdPackages">Created packages</key>
|
||||
|
||||
@@ -24,6 +24,7 @@ using Umbraco.Web.WebApi.Binders;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
using umbraco.cms.businesslogic.web;
|
||||
using umbraco.presentation.preview;
|
||||
using Umbraco.Core.Events;
|
||||
using Constants = Umbraco.Core.Constants;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
@@ -104,6 +105,36 @@ namespace Umbraco.Web.Editors
|
||||
return display;
|
||||
}
|
||||
|
||||
public ContentItemDisplay GetBlueprintById(int id)
|
||||
{
|
||||
var foundContent = Services.ContentService.GetBlueprintById(id);
|
||||
if (foundContent == null)
|
||||
{
|
||||
HandleContentNotFound(id);
|
||||
}
|
||||
|
||||
var content = Mapper.Map<IContent, ContentItemDisplay>(foundContent);
|
||||
|
||||
SetupBlueprint(content, foundContent);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
private static void SetupBlueprint(ContentItemDisplay content, IContent persistedContent)
|
||||
{
|
||||
content.AllowPreview = false;
|
||||
|
||||
//set a custom path since the tree that renders this has the content type id as the parent
|
||||
content.Path = string.Format("-1,{0},{1}", persistedContent.ContentTypeId, content.Id);
|
||||
|
||||
content.AllowedActions = new[] {'A'};
|
||||
|
||||
var excludeProps = new[] {"_umb_urls", "_umb_releasedate", "_umb_expiredate", "_umb_template"};
|
||||
var propsTab = content.Tabs.Last();
|
||||
propsTab.Properties = propsTab.Properties
|
||||
.Where(p => excludeProps.Contains(p.Alias) == false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content json for the content id
|
||||
/// </summary>
|
||||
@@ -163,6 +194,26 @@ namespace Umbraco.Web.Editors
|
||||
return mapped;
|
||||
}
|
||||
|
||||
[OutgoingEditorModelEvent]
|
||||
public ContentItemDisplay GetEmpty(int blueprintId)
|
||||
{
|
||||
var blueprint = Services.ContentService.GetBlueprintById(blueprintId);
|
||||
if (blueprint == null)
|
||||
{
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
blueprint.Id = 0;
|
||||
blueprint.Name = string.Empty;
|
||||
|
||||
var mapped = Mapper.Map<ContentItemDisplay>(blueprint);
|
||||
|
||||
//remove this tab if it exists: umbContainerView
|
||||
var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName);
|
||||
mapped.Tabs = mapped.Tabs.Except(new[] { containerTab });
|
||||
return mapped;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Url for a given node ID
|
||||
/// </summary>
|
||||
@@ -275,6 +326,69 @@ namespace Umbraco.Web.Editors
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a blueprint from a content item
|
||||
/// </summary>
|
||||
/// <param name="contentId">The content id to copy</param>
|
||||
/// <param name="name">The name of the blueprint</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public SimpleNotificationModel CreateBlueprintFromContent([FromUri]int contentId, [FromUri]string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name");
|
||||
|
||||
var content = Services.ContentService.GetById(contentId);
|
||||
if (content == null)
|
||||
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
|
||||
|
||||
EnsureUniqueName(name, content, "name");
|
||||
|
||||
var blueprint = Services.ContentService.CreateContentFromBlueprint(content, name, Security.GetUserId());
|
||||
|
||||
Services.ContentService.SaveBlueprint(blueprint, Security.GetUserId());
|
||||
|
||||
var notificationModel = new SimpleNotificationModel();
|
||||
notificationModel.AddSuccessNotification(
|
||||
Services.TextService.Localize("content/createdBlueprintHeading"),
|
||||
Services.TextService.Localize("content/createdBlueprintMessage", new[]{ content.Name})
|
||||
);
|
||||
|
||||
return notificationModel;
|
||||
}
|
||||
|
||||
private void EnsureUniqueName(string name, IContent content, string modelName)
|
||||
{
|
||||
var existing = Services.ContentService.GetBlueprintsForContentTypes(content.ContentTypeId);
|
||||
if (existing.Any(x => x.Name == name && x.Id != content.Id))
|
||||
{
|
||||
ModelState.AddModelError(modelName, Services.TextService.Localize("content/duplicateBlueprintMessage"));
|
||||
throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves content
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[FileUploadCleanupFilter]
|
||||
[ContentPostValidate]
|
||||
public ContentItemDisplay PostSaveBlueprint(
|
||||
[ModelBinder(typeof(ContentItemBinder))] ContentItemSave contentItem)
|
||||
{
|
||||
var contentItemDisplay = PostSaveInternal(contentItem,
|
||||
content =>
|
||||
{
|
||||
EnsureUniqueName(content.Name, content, "Name");
|
||||
|
||||
Services.ContentService.SaveBlueprint(contentItem.PersistedContent, Security.CurrentUser.Id);
|
||||
//we need to reuse the underlying logic so return the result that it wants
|
||||
return Attempt<OperationStatus>.Succeed(new OperationStatus(OperationStatusType.Success, new EventMessages()));
|
||||
});
|
||||
SetupBlueprint(contentItemDisplay, contentItemDisplay.PersistedContent);
|
||||
|
||||
return contentItemDisplay;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves content
|
||||
/// </summary>
|
||||
@@ -284,6 +398,12 @@ namespace Umbraco.Web.Editors
|
||||
public ContentItemDisplay PostSave(
|
||||
[ModelBinder(typeof(ContentItemBinder))]
|
||||
ContentItemSave contentItem)
|
||||
{
|
||||
return PostSaveInternal(contentItem,
|
||||
content => Services.ContentService.WithResult().Save(contentItem.PersistedContent, Security.CurrentUser.Id));
|
||||
}
|
||||
|
||||
private ContentItemDisplay PostSaveInternal(ContentItemSave contentItem, Func<IContent, Attempt<OperationStatus>> saveMethod)
|
||||
{
|
||||
//If we've reached here it means:
|
||||
// * Our model has been bound
|
||||
@@ -291,7 +411,6 @@ namespace Umbraco.Web.Editors
|
||||
// * any file attachments have been saved to their temporary location for us to use
|
||||
// * we have a reference to the DTO object and the persisted object
|
||||
// * Permissions are valid
|
||||
|
||||
MapPropertyValues(contentItem);
|
||||
|
||||
//We need to manually check the validation results here because:
|
||||
@@ -331,7 +450,7 @@ namespace Umbraco.Web.Editors
|
||||
if (contentItem.Action == ContentSaveAction.Save || contentItem.Action == ContentSaveAction.SaveNew)
|
||||
{
|
||||
//save the item
|
||||
var saveResult = Services.ContentService.WithResult().Save(contentItem.PersistedContent, Security.CurrentUser.Id);
|
||||
var saveResult = saveMethod(contentItem.PersistedContent);
|
||||
|
||||
wasCancelled = saveResult.Success == false && saveResult.Result.StatusType == OperationStatusType.FailedCancelledByEvent;
|
||||
}
|
||||
@@ -361,8 +480,8 @@ namespace Umbraco.Web.Editors
|
||||
if (wasCancelled == false)
|
||||
{
|
||||
display.AddSuccessNotification(
|
||||
Services.TextService.Localize("speechBubbles/editContentSavedHeader"),
|
||||
Services.TextService.Localize("speechBubbles/editContentSavedText"));
|
||||
Services.TextService.Localize("speechBubbles/editContentSavedHeader"),
|
||||
Services.TextService.Localize("speechBubbles/editContentSavedText"));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -374,8 +493,8 @@ namespace Umbraco.Web.Editors
|
||||
if (wasCancelled == false)
|
||||
{
|
||||
display.AddSuccessNotification(
|
||||
Services.TextService.Localize("speechBubbles/editContentSendToPublish"),
|
||||
Services.TextService.Localize("speechBubbles/editContentSendToPublishText"));
|
||||
Services.TextService.Localize("speechBubbles/editContentSendToPublish"),
|
||||
Services.TextService.Localize("speechBubbles/editContentSendToPublishText"));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -398,8 +517,10 @@ namespace Umbraco.Web.Editors
|
||||
throw new HttpResponseException(Request.CreateValidationErrorResponse(display));
|
||||
}
|
||||
|
||||
display.PersistedContent = contentItem.PersistedContent;
|
||||
|
||||
return display;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Publishes a document with a given ID
|
||||
@@ -434,6 +555,22 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
}
|
||||
|
||||
[HttpDelete]
|
||||
[HttpPost]
|
||||
public HttpResponseMessage DeleteBlueprint(int id)
|
||||
{
|
||||
var found = Services.ContentService.GetBlueprintById(id);
|
||||
|
||||
if (found == null)
|
||||
{
|
||||
return HandleContentNotFound(id, false);
|
||||
}
|
||||
|
||||
Services.ContentService.DeleteBlueprint(found);
|
||||
|
||||
return Request.CreateResponse(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves an item to the recycle bin, if it is already there then it will permanently delete it
|
||||
/// </summary>
|
||||
|
||||
@@ -302,7 +302,18 @@ namespace Umbraco.Web.Editors
|
||||
{
|
||||
basic.Name = localizedTextService.UmbracoDictionaryTranslate(basic.Name);
|
||||
basic.Description = localizedTextService.UmbracoDictionaryTranslate(basic.Description);
|
||||
}
|
||||
}
|
||||
|
||||
//map the blueprints
|
||||
var blueprints = Services.ContentService.GetBlueprintsForContentTypes(types.Select(x => x.Id).ToArray()).ToArray();
|
||||
foreach (var basic in basics)
|
||||
{
|
||||
var docTypeBluePrints = blueprints.Where(x => x.ContentTypeId == (int) basic.Id).ToArray();
|
||||
foreach (var blueprint in docTypeBluePrints)
|
||||
{
|
||||
basic.Blueprints[blueprint.Id] = blueprint.Name;
|
||||
}
|
||||
}
|
||||
|
||||
return basics;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Runtime.Serialization;
|
||||
@@ -18,6 +19,11 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
[DataContract(Name = "contentType", Namespace = "")]
|
||||
public class ContentTypeBasic : EntityBasic
|
||||
{
|
||||
public ContentTypeBasic()
|
||||
{
|
||||
Blueprints = new Dictionary<int, string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overridden to apply our own validation attributes since this is not always required for other classes
|
||||
/// </summary>
|
||||
@@ -105,5 +111,9 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
: IOHelper.ResolveUrl("~/umbraco/images/thumbnails/" + Thumbnail);
|
||||
}
|
||||
}
|
||||
|
||||
[DataMember(Name = "blueprints")]
|
||||
[ReadOnly(true)]
|
||||
public IDictionary<int, string> Blueprints { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,11 +163,14 @@ namespace Umbraco.Web.Models.Mapping
|
||||
});
|
||||
|
||||
config.CreateMap<IMemberType, ContentTypeBasic>()
|
||||
.ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.MemberType, content.Key)));
|
||||
.ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.MemberType, content.Key)))
|
||||
.ForMember(x => x.Blueprints, expression => expression.Ignore());
|
||||
config.CreateMap<IMediaType, ContentTypeBasic>()
|
||||
.ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.MediaType, content.Key)));
|
||||
.ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.MediaType, content.Key)))
|
||||
.ForMember(x => x.Blueprints, expression => expression.Ignore());
|
||||
config.CreateMap<IContentType, ContentTypeBasic>()
|
||||
.ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.DocumentType, content.Key)));
|
||||
.ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.DocumentType, content.Key)))
|
||||
.ForMember(x => x.Blueprints, expression => expression.Ignore());
|
||||
|
||||
config.CreateMap<PropertyTypeBasic, PropertyType>()
|
||||
|
||||
|
||||
@@ -129,6 +129,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
return mapping
|
||||
.ForMember(x => x.Udi, expression => expression.ResolveUsing(new ContentTypeUdiResolver()))
|
||||
.ForMember(display => display.Notifications, expression => expression.Ignore())
|
||||
.ForMember(display => display.Blueprints, expression => expression.Ignore())
|
||||
.ForMember(display => display.Errors, expression => expression.Ignore())
|
||||
.ForMember(display => display.AllowAsRoot, expression => expression.MapFrom(type => type.AllowedAsRoot))
|
||||
.ForMember(display => display.ListViewEditorName, expression => expression.Ignore())
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Net.Http.Formatting;
|
||||
using System.Web.Http;
|
||||
using umbraco;
|
||||
using umbraco.businesslogic.Actions;
|
||||
using umbraco.BusinessLogic.Actions;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Web.Models.Trees;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
using Constants = Umbraco.Core.Constants;
|
||||
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
/// <summary>
|
||||
/// The content blueprint tree controller
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This authorizes based on access to the content section even though it exists in the settings
|
||||
/// </remarks>
|
||||
[UmbracoApplicationAuthorize(Constants.Applications.Content)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.ContentBlueprints, null, sortOrder: 8)]
|
||||
[PluginController("UmbracoTrees")]
|
||||
[CoreTree]
|
||||
public class ContentBlueprintTreeController : TreeController
|
||||
{
|
||||
protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings)
|
||||
{
|
||||
var nodes = new TreeNodeCollection();
|
||||
|
||||
//get all blueprints
|
||||
var entities = Services.EntityService.GetChildren(Constants.System.Root, UmbracoObjectTypes.DocumentBlueprint).ToArray();
|
||||
|
||||
//check if we're rendering the root in which case we'll render the content types that have blueprints
|
||||
if (id == Constants.System.Root.ToInvariantString())
|
||||
{
|
||||
//get all blueprint content types
|
||||
var contentTypeAliases = entities.Select(x => ((UmbracoEntity) x).ContentTypeAlias).Distinct();
|
||||
//get the ids
|
||||
var contentTypeIds = Services.ContentTypeService.GetAllContentTypeIds(contentTypeAliases.ToArray());
|
||||
//now get the entities ... it's a bit round about but still smaller queries than getting all document types
|
||||
var docTypeEntities = Services.EntityService.GetAll(UmbracoObjectTypes.DocumentType, contentTypeIds.ToArray()).ToArray();
|
||||
|
||||
nodes.AddRange(docTypeEntities
|
||||
.Select(entity =>
|
||||
{
|
||||
var treeNode = CreateTreeNode(entity, Constants.ObjectTypes.DocumentBlueprintGuid, id, queryStrings, "icon-item-arrangement", true);
|
||||
treeNode.Path = string.Format("-1,{0}", entity.Id);
|
||||
treeNode.NodeType = "contentType";
|
||||
//TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now.
|
||||
treeNode.AdditionalData["jsClickCallback"] = "javascript:void(0);";
|
||||
return treeNode;
|
||||
}));
|
||||
|
||||
return nodes;
|
||||
}
|
||||
else
|
||||
{
|
||||
var intId = id.TryConvertTo<int>();
|
||||
//Get the content type
|
||||
var ct = Services.ContentTypeService.GetContentType(intId.Result);
|
||||
if (ct == null) return nodes;
|
||||
|
||||
var blueprintsForDocType = entities.Where(x => ct.Alias == ((UmbracoEntity) x).ContentTypeAlias);
|
||||
nodes.AddRange(blueprintsForDocType
|
||||
.Select(entity =>
|
||||
{
|
||||
var treeNode = CreateTreeNode(entity, Constants.ObjectTypes.DocumentBlueprintGuid, id, queryStrings, "icon-blueprint", false);
|
||||
treeNode.Path = string.Format("-1,{0},{1}", ct.Id, entity.Id);
|
||||
return treeNode;
|
||||
}));
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings)
|
||||
{
|
||||
var menu = new MenuItemCollection();
|
||||
|
||||
if (id == Constants.System.Root.ToInvariantString())
|
||||
{
|
||||
// root actions
|
||||
menu.Items.Add<RefreshNode, ActionRefresh>(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true);
|
||||
return menu;
|
||||
}
|
||||
var cte = Services.EntityService.Get(int.Parse(id), UmbracoObjectTypes.DocumentType);
|
||||
//only refresh & create if it's a content type
|
||||
if (cte != null)
|
||||
{
|
||||
var ct = Services.ContentTypeService.GetContentType(cte.Id);
|
||||
var createItem = menu.Items.Add<ActionCreateBlueprintFromContent>(Services.TextService.Localize(string.Format("actions/{0}", ActionCreateBlueprintFromContent.Instance.Alias)));
|
||||
createItem.NavigateToRoute("/settings/contentBlueprints/edit/-1?create=true&doctype=" + ct.Alias);
|
||||
|
||||
menu.Items.Add<RefreshNode, ActionRefresh>(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true);
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
menu.Items.Add<ActionDelete>(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias)));
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ using Umbraco.Web.WebApi.Filters;
|
||||
using umbraco;
|
||||
using umbraco.BusinessLogic.Actions;
|
||||
using umbraco.businesslogic;
|
||||
using umbraco.businesslogic.Actions;
|
||||
using umbraco.cms.businesslogic.web;
|
||||
using umbraco.interfaces;
|
||||
using Constants = Umbraco.Core.Constants;
|
||||
@@ -225,7 +226,9 @@ namespace Umbraco.Web.Trees
|
||||
var menu = new MenuItemCollection();
|
||||
menu.Items.Add<ActionNew>(ui.Text("actions", ActionNew.Instance.Alias));
|
||||
menu.Items.Add<ActionDelete>(ui.Text("actions", ActionDelete.Instance.Alias));
|
||||
|
||||
|
||||
menu.Items.Add<ActionCreateBlueprintFromContent>(ui.Text("actions", ActionCreateBlueprintFromContent.Instance.Alias));
|
||||
|
||||
//need to ensure some of these are converted to the legacy system - until we upgrade them all to be angularized.
|
||||
menu.Items.Add<ActionMove>(ui.Text("actions", ActionMove.Instance.Alias), true);
|
||||
menu.Items.Add<ActionCopy>(ui.Text("actions", ActionCopy.Instance.Alias));
|
||||
|
||||
@@ -465,6 +465,7 @@
|
||||
<Compile Include="Models\Mapping\PropertyTypeGroupResolver.cs" />
|
||||
<Compile Include="Security\Identity\PreviewAuthenticationMiddleware.cs" />
|
||||
<Compile Include="SingletonHttpContextAccessor.cs" />
|
||||
<Compile Include="Trees\ContentBlueprintTreeController.cs" />
|
||||
<Compile Include="Trees\ContentTypeTreeController.cs" />
|
||||
<Compile Include="Trees\PackagesTreeController.cs" />
|
||||
<Compile Include="Trees\MediaTypeTreeController.cs" />
|
||||
|
||||
@@ -343,5 +343,24 @@ namespace Umbraco.Web
|
||||
{
|
||||
return url.SurfaceAction(action, typeof (T), additionalRouteVals);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a Absolute Media Item URL based on the current context
|
||||
/// </summary>
|
||||
/// <param name="urlHelper"></param>
|
||||
/// <param name="mediaItem"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetAbsoluteMediaUrl(this UrlHelper urlHelper, IPublishedContent mediaItem)
|
||||
{
|
||||
if (urlHelper == null) throw new ArgumentNullException("urlHelper");
|
||||
if (mediaItem == null) throw new ArgumentNullException("mediaItem");
|
||||
|
||||
if (urlHelper.RequestContext.HttpContext.Request.Url != null)
|
||||
{
|
||||
var requestUrl = urlHelper.RequestContext.HttpContext.Request.Url.GetLeftPart(UriPartial.Authority);
|
||||
return string.Format("{0}{1}", requestUrl, mediaItem.Url);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using umbraco.interfaces;
|
||||
|
||||
namespace umbraco.businesslogic.Actions
|
||||
{
|
||||
public class ActionCreateBlueprintFromContent : IAction
|
||||
{
|
||||
private static readonly ActionCreateBlueprintFromContent instance = new ActionCreateBlueprintFromContent();
|
||||
|
||||
public static ActionCreateBlueprintFromContent Instance
|
||||
{
|
||||
get { return instance; }
|
||||
}
|
||||
|
||||
public char Letter { get; private set; }
|
||||
public bool ShowInNotifier { get; private set; }
|
||||
public bool CanBePermissionAssigned { get; private set; }
|
||||
public string Icon { get; private set; }
|
||||
public string Alias { get; private set; }
|
||||
public string JsFunctionName { get; private set; }
|
||||
public string JsSource { get; private set; }
|
||||
|
||||
public ActionCreateBlueprintFromContent()
|
||||
{
|
||||
Letter = 'ï';
|
||||
CanBePermissionAssigned = true;
|
||||
Icon = "blueprint";
|
||||
Alias = "createblueprint";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,6 +183,7 @@
|
||||
<Compile Include="Actions\ActionBrowse.cs" />
|
||||
<Compile Include="Actions\ActionChangeDocType.cs" />
|
||||
<Compile Include="Actions\ActionCopy.cs" />
|
||||
<Compile Include="Actions\ActionCreateBlueprintFromContent.cs" />
|
||||
<Compile Include="Actions\ActionDelete.cs" />
|
||||
<Compile Include="Actions\ActionDisable.cs" />
|
||||
<Compile Include="Actions\ActionEmptyTranscan.cs" />
|
||||
|
||||
Reference in New Issue
Block a user