Merge remote-tracking branch 'origin/temp8' into temp8-logviewer
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.Repositories.Implement;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Services.Implement;
|
||||
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
|
||||
//TODO: This should just exist in the content service/repo!
|
||||
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
|
||||
public sealed class RelateOnCopyComponent : UmbracoComponentBase, IUmbracoCoreComponent
|
||||
|
||||
@@ -363,22 +363,28 @@ namespace Umbraco.Core.Models
|
||||
/// <param name="propertyTypeAlias">Alias of the <see cref="PropertyType"/> to remove</param>
|
||||
public void RemovePropertyType(string propertyTypeAlias)
|
||||
{
|
||||
//check if the property exist in one of our collections
|
||||
if (PropertyGroups.Any(group => group.PropertyTypes.Any(pt => pt.Alias == propertyTypeAlias))
|
||||
|| _propertyTypes.Any(x => x.Alias == propertyTypeAlias))
|
||||
{
|
||||
//set the flag that a property has been removed
|
||||
HasPropertyTypeBeenRemoved = true;
|
||||
}
|
||||
|
||||
//check through each property group to see if we can remove the property type by alias from it
|
||||
foreach (var propertyGroup in PropertyGroups)
|
||||
{
|
||||
propertyGroup.PropertyTypes.RemoveItem(propertyTypeAlias);
|
||||
if (propertyGroup.PropertyTypes.RemoveItem(propertyTypeAlias))
|
||||
{
|
||||
if (!HasPropertyTypeBeenRemoved)
|
||||
{
|
||||
HasPropertyTypeBeenRemoved = true;
|
||||
OnPropertyChanged(Ps.Value.PropertyTypeCollectionSelector);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_propertyTypes.Any(x => x.Alias == propertyTypeAlias))
|
||||
//check through each local property type collection (not assigned to a tab)
|
||||
if (_propertyTypes.RemoveItem(propertyTypeAlias))
|
||||
{
|
||||
_propertyTypes.RemoveItem(propertyTypeAlias);
|
||||
if (!HasPropertyTypeBeenRemoved)
|
||||
{
|
||||
HasPropertyTypeBeenRemoved = true;
|
||||
OnPropertyChanged(Ps.Value.PropertyTypeCollectionSelector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,23 +414,9 @@ namespace Umbraco.Core.Models
|
||||
/// PropertyTypes that are not part of a PropertyGroup
|
||||
/// </summary>
|
||||
[IgnoreDataMember]
|
||||
//fixme should we mark this as EditorBrowsable hidden since it really isn't ever used?
|
||||
internal PropertyTypeCollection PropertyTypeCollection => _propertyTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether a specific property on the current <see cref="IContent"/> entity is dirty.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">Name of the property to check</param>
|
||||
/// <returns>True if Property is dirty, otherwise False</returns>
|
||||
public override bool IsPropertyDirty(string propertyName)
|
||||
{
|
||||
bool existsInEntity = base.IsPropertyDirty(propertyName);
|
||||
|
||||
bool anyDirtyGroups = PropertyGroups.Any(x => x.IsPropertyDirty(propertyName));
|
||||
bool anyDirtyTypes = PropertyTypes.Any(x => x.IsPropertyDirty(propertyName));
|
||||
|
||||
return existsInEntity || anyDirtyGroups || anyDirtyTypes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the current entity is dirty.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
@@ -16,5 +20,63 @@ namespace Umbraco.Core.Models
|
||||
else if (typeof(IMemberType).IsAssignableFrom(type)) itemType = PublishedItemType.Member;
|
||||
return itemType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to check if any property type was changed between variant/invariant
|
||||
/// </summary>
|
||||
/// <param name="contentType"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool WasPropertyTypeVariationChanged(this IContentTypeBase contentType)
|
||||
{
|
||||
return contentType.WasPropertyTypeVariationChanged(out var _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to check if any property type was changed between variant/invariant
|
||||
/// </summary>
|
||||
/// <param name="contentType"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool WasPropertyTypeVariationChanged(this IContentTypeBase contentType, out IReadOnlyCollection<string> aliases)
|
||||
{
|
||||
var a = new List<string>();
|
||||
|
||||
// property variation change?
|
||||
var hasAnyPropertyVariationChanged = contentType.PropertyTypes.Any(propertyType =>
|
||||
{
|
||||
if (!(propertyType is IRememberBeingDirty dirtyProperty))
|
||||
throw new Exception("oops");
|
||||
|
||||
// skip new properties
|
||||
//TODO: This used to be WasPropertyDirty("HasIdentity") but i don't think that actually worked for detecting new entities this does seem to work properly
|
||||
var isNewProperty = dirtyProperty.WasPropertyDirty("Id");
|
||||
if (isNewProperty) return false;
|
||||
|
||||
// variation change?
|
||||
var dirty = dirtyProperty.WasPropertyDirty("Variations");
|
||||
if (dirty)
|
||||
a.Add(propertyType.Alias);
|
||||
|
||||
return dirty;
|
||||
|
||||
});
|
||||
|
||||
aliases = a;
|
||||
return hasAnyPropertyVariationChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the list of content types the composition is used in
|
||||
/// </summary>
|
||||
/// <param name="allContentTypes"></param>
|
||||
/// <param name="source"></param>
|
||||
/// <returns></returns>
|
||||
internal static IEnumerable<IContentTypeComposition> GetWhereCompositionIsUsedInContentTypes(this IContentTypeComposition source,
|
||||
IContentTypeComposition[] allContentTypes)
|
||||
{
|
||||
var sourceId = source != null ? source.Id : 0;
|
||||
|
||||
// find which content types are using this composition
|
||||
return allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == sourceId)).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace Umbraco.Core.Models
|
||||
/// <summary>
|
||||
/// Gets or sets the content types that compose this content type.
|
||||
/// </summary>
|
||||
//fixme: we should be storing key references, not the object else we are caching way too much
|
||||
IEnumerable<IContentTypeComposition> ContentTypeComposition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -124,10 +124,11 @@ namespace Umbraco.Core.Models
|
||||
return this.Any(x => x.Alias == propertyAlias);
|
||||
}
|
||||
|
||||
public void RemoveItem(string propertyTypeAlias)
|
||||
public bool RemoveItem(string propertyTypeAlias)
|
||||
{
|
||||
var key = IndexOfKey(propertyTypeAlias);
|
||||
if (key != -1) RemoveItem(key);
|
||||
return key != -1;
|
||||
}
|
||||
|
||||
public int IndexOfKey(string key)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a section defined in the app.config file
|
||||
/// Represents a section defined in the app.config file.
|
||||
/// </summary>
|
||||
public class Section
|
||||
{
|
||||
@@ -13,9 +13,7 @@
|
||||
}
|
||||
|
||||
public Section()
|
||||
{
|
||||
|
||||
}
|
||||
{ }
|
||||
|
||||
public string Name { get; set; }
|
||||
public string Alias { get; set; }
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Umbraco.Core.Persistence.Dtos
|
||||
[ExplicitColumns]
|
||||
internal class PropertyDataDto
|
||||
{
|
||||
private const string TableName = Constants.DatabaseSchema.Tables.PropertyData;
|
||||
public const string TableName = Constants.DatabaseSchema.Tables.PropertyData;
|
||||
public const int VarcharLength = 512;
|
||||
public const int SegmentLength = 256;
|
||||
|
||||
|
||||
@@ -1036,7 +1036,7 @@ namespace Umbraco.Core.Persistence
|
||||
{
|
||||
var pd = sql.SqlContext.PocoDataFactory.ForType(typeof (TDto));
|
||||
var tableName = tableAlias ?? pd.TableInfo.TableName;
|
||||
var queryColumns = pd.QueryColumns;
|
||||
var queryColumns = pd.QueryColumns.ToList();
|
||||
|
||||
Dictionary<string, string> aliases = null;
|
||||
|
||||
@@ -1056,7 +1056,11 @@ namespace Umbraco.Core.Persistence
|
||||
return fieldName;
|
||||
}).ToArray();
|
||||
|
||||
queryColumns = queryColumns.Where(x => names.Contains(x.Key)).ToArray();
|
||||
//only get the columns that exist in the selected names
|
||||
queryColumns = queryColumns.Where(x => names.Contains(x.Key)).ToList();
|
||||
|
||||
//ensure the order of the columns in the expressions is the order in the result
|
||||
queryColumns.Sort((a, b) => names.IndexOf(a.Key).CompareTo(names.IndexOf(b.Key)));
|
||||
}
|
||||
|
||||
string GetAlias(PocoColumn column)
|
||||
|
||||
@@ -10,6 +10,12 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
TItem Get(string alias);
|
||||
IEnumerable<MoveEventInfo<TItem>> Move(TItem moving, EntityContainer container);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the content types that are direct compositions of the content type
|
||||
/// </summary>
|
||||
/// <param name="id">The content type id</param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<TItem> GetTypesDirectlyComposedOf(int id);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -608,6 +608,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
#region UnitOfWork Events
|
||||
|
||||
//fixme: The reason these events are in the repository is for legacy, the events should exist at the service
|
||||
// level now since we can fire these events within the transaction... so move the events to service level
|
||||
|
||||
public class ScopedEntityEventArgs : EventArgs
|
||||
{
|
||||
public ScopedEntityEventArgs(IScope scope, TEntity entity)
|
||||
|
||||
@@ -274,6 +274,8 @@ AND umbracoNode.id <> @id",
|
||||
if (compositionBase != null && compositionBase.RemovedContentTypeKeyTracker != null &&
|
||||
compositionBase.RemovedContentTypeKeyTracker.Any())
|
||||
{
|
||||
//TODO: Could we do the below with bulk SQL statements instead of looking everything up and then manipulating?
|
||||
|
||||
// find Content based on the current ContentType
|
||||
var sql = Sql()
|
||||
.SelectAll()
|
||||
@@ -292,6 +294,7 @@ AND umbracoNode.id <> @id",
|
||||
// based on the PropertyTypes that belong to the removed ContentType.
|
||||
foreach (var contentDto in contentDtos)
|
||||
{
|
||||
//TODO: This could be done with bulk SQL statements
|
||||
foreach (var propertyType in propertyTypes)
|
||||
{
|
||||
var nodeId = contentDto.NodeId;
|
||||
@@ -323,9 +326,7 @@ AND umbracoNode.id <> @id",
|
||||
});
|
||||
}
|
||||
|
||||
// fixme below, manage the property type
|
||||
|
||||
// delete ??? fixme wtf is this?
|
||||
// delete property types
|
||||
// ... by excepting entries from db with entries from collections
|
||||
if (entity.IsPropertyDirty("PropertyTypes") || entity.PropertyTypes.Any(x => x.IsDirty()))
|
||||
{
|
||||
@@ -404,10 +405,49 @@ AND umbracoNode.id <> @id",
|
||||
propertyType.PropertyGroupId = new Lazy<int>(() => groupId);
|
||||
}
|
||||
|
||||
//check if the content type variation has been changed
|
||||
var ctVariationChanging = entity.IsPropertyDirty("Variations");
|
||||
if (ctVariationChanging)
|
||||
{
|
||||
//we've already looked up the previous version of the content type so we know it's previous variation state
|
||||
MoveVariantData(entity, (ContentVariation)dtoPk.Variations, entity.Variations);
|
||||
Clear301Redirects(entity);
|
||||
ClearScheduledPublishing(entity);
|
||||
}
|
||||
|
||||
//track any content type/property types that are changing variation which will require content updates
|
||||
var propertyTypeVariationChanges = new Dictionary<int, ContentVariation>();
|
||||
|
||||
// insert or update properties
|
||||
// all of them, no-group and in-groups
|
||||
foreach (var propertyType in entity.PropertyTypes)
|
||||
{
|
||||
//if the content type variation isn't changing track if any property type is changing
|
||||
if (!ctVariationChanging)
|
||||
{
|
||||
if (propertyType.IsPropertyDirty("Variations"))
|
||||
{
|
||||
propertyTypeVariationChanges[propertyType.Id] = propertyType.Variations;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch(entity.Variations)
|
||||
{
|
||||
case ContentVariation.Nothing:
|
||||
//if the content type is changing to Nothing, then all property type's must change to nothing
|
||||
propertyType.Variations = ContentVariation.Nothing;
|
||||
break;
|
||||
case ContentVariation.Culture:
|
||||
//we don't need to modify the property type in this case
|
||||
break;
|
||||
case ContentVariation.CultureAndSegment:
|
||||
case ContentVariation.Segment:
|
||||
default:
|
||||
throw new NotSupportedException(); //TODO: Support this
|
||||
}
|
||||
}
|
||||
|
||||
var groupId = propertyType.PropertyGroupId?.Value ?? default(int);
|
||||
// if the Id of the DataType is not set, we resolve it from the db by its PropertyEditorAlias
|
||||
if (propertyType.DataTypeId == 0 || propertyType.DataTypeId == default(int))
|
||||
@@ -431,6 +471,28 @@ AND umbracoNode.id <> @id",
|
||||
orphanPropertyTypeIds.Remove(typeId);
|
||||
}
|
||||
|
||||
//check if any property types were changing variation
|
||||
if (propertyTypeVariationChanges.Count > 0)
|
||||
{
|
||||
var changes = new Dictionary<int, (ContentVariation, ContentVariation)>();
|
||||
|
||||
//now get the current property type variations for the changed ones so that we know which variation they
|
||||
//are going from and to
|
||||
var from = Database.Dictionary<int, byte>(Sql()
|
||||
.Select<PropertyTypeDto>(x => x.Id, x => x.Variations)
|
||||
.From<PropertyTypeDto>()
|
||||
.WhereIn<PropertyTypeDto>(x => x.Id, propertyTypeVariationChanges.Keys));
|
||||
|
||||
foreach (var f in from)
|
||||
{
|
||||
changes[f.Key] = (propertyTypeVariationChanges[f.Key], (ContentVariation)f.Value);
|
||||
}
|
||||
|
||||
//perform the move
|
||||
MoveVariantData(changes);
|
||||
}
|
||||
|
||||
|
||||
// deal with orphan properties: those that were in a deleted tab,
|
||||
// and have not been re-mapped to another tab or to 'generic properties'
|
||||
if (orphanPropertyTypeIds != null)
|
||||
@@ -438,6 +500,221 @@ AND umbracoNode.id <> @id",
|
||||
DeletePropertyType(entity.Id, id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear any redirects associated with content for a content type
|
||||
/// </summary>
|
||||
private void Clear301Redirects(IContentTypeComposition contentType)
|
||||
{
|
||||
//first clear out any existing property data that might already exists under the default lang
|
||||
var sqlSelect = Sql().Select<NodeDto>(x => x.UniqueId)
|
||||
.From<NodeDto>()
|
||||
.InnerJoin<ContentDto>().On<ContentDto, NodeDto>(x => x.NodeId, x => x.NodeId)
|
||||
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id);
|
||||
var sqlDelete = Sql()
|
||||
.Delete<RedirectUrlDto>()
|
||||
.WhereIn((System.Linq.Expressions.Expression<Func<RedirectUrlDto, object>>)(x => x.ContentKey), sqlSelect);
|
||||
|
||||
Database.Execute(sqlDelete);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear any scheduled publishing associated with content for a content type
|
||||
/// </summary>
|
||||
private void ClearScheduledPublishing(IContentTypeComposition contentType)
|
||||
{
|
||||
//TODO: Fill this in when scheduled publishing is enabled for variants
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves variant data for property type changes
|
||||
/// </summary>
|
||||
/// <param name="propertyTypeChanges"></param>
|
||||
private void MoveVariantData(IDictionary<int, (ContentVariation, ContentVariation)> propertyTypeChanges)
|
||||
{
|
||||
var defaultLangId = Database.First<int>(Sql().Select<LanguageDto>(x => x.Id).From<LanguageDto>().Where<LanguageDto>(x => x.IsDefault));
|
||||
|
||||
//Group by the "To" variation so we can bulk update in the correct batches
|
||||
foreach(var g in propertyTypeChanges.GroupBy(x => x.Value.Item2))
|
||||
{
|
||||
var propertyTypeIds = g.Select(s => s.Key).ToList();
|
||||
|
||||
//the ContentVariation that the data is moving "To"
|
||||
var toVariantType = g.Key;
|
||||
|
||||
switch(toVariantType)
|
||||
{
|
||||
case ContentVariation.Culture:
|
||||
MovePropertyDataToVariantCulture(defaultLangId, propertyTypeIds: propertyTypeIds);
|
||||
break;
|
||||
case ContentVariation.Nothing:
|
||||
MovePropertyDataToVariantNothing(defaultLangId, propertyTypeIds: propertyTypeIds);
|
||||
break;
|
||||
case ContentVariation.CultureAndSegment:
|
||||
case ContentVariation.Segment:
|
||||
default:
|
||||
throw new NotSupportedException(); //TODO: Support this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves variant data for a content type variation change
|
||||
/// </summary>
|
||||
/// <param name="contentType"></param>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="to"></param>
|
||||
private void MoveVariantData(IContentTypeComposition contentType, ContentVariation from, ContentVariation to)
|
||||
{
|
||||
var defaultLangId = Database.First<int>(Sql().Select<LanguageDto>(x => x.Id).From<LanguageDto>().Where<LanguageDto>(x => x.IsDefault));
|
||||
|
||||
var sqlPropertyTypeIds = Sql().Select<PropertyTypeDto>(x => x.Id).From<PropertyTypeDto>().Where<PropertyTypeDto>(x => x.ContentTypeId == contentType.Id);
|
||||
switch (to)
|
||||
{
|
||||
case ContentVariation.Culture:
|
||||
//move the property data
|
||||
|
||||
MovePropertyDataToVariantCulture(defaultLangId, sqlPropertyTypeIds: sqlPropertyTypeIds);
|
||||
|
||||
//now we need to move the names
|
||||
//first clear out any existing names that might already exists under the default lang
|
||||
//there's 2x tables to update
|
||||
|
||||
//clear out the versionCultureVariation table
|
||||
var sqlSelect = Sql().Select<ContentVersionCultureVariationDto>(x => x.Id)
|
||||
.From<ContentVersionCultureVariationDto>()
|
||||
.InnerJoin<ContentVersionDto>().On<ContentVersionDto, ContentVersionCultureVariationDto>(x => x.Id, x => x.VersionId)
|
||||
.InnerJoin<ContentDto>().On<ContentDto, ContentVersionDto>(x => x.NodeId, x => x.NodeId)
|
||||
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id)
|
||||
.Where<ContentVersionCultureVariationDto>(x => x.LanguageId == defaultLangId);
|
||||
var sqlDelete = Sql()
|
||||
.Delete<ContentVersionCultureVariationDto>()
|
||||
.WhereIn<ContentVersionCultureVariationDto>(x => x.Id, sqlSelect);
|
||||
Database.Execute(sqlDelete);
|
||||
|
||||
//clear out the documentCultureVariation table
|
||||
sqlSelect = Sql().Select<DocumentCultureVariationDto>(x => x.Id)
|
||||
.From<DocumentCultureVariationDto>()
|
||||
.InnerJoin<ContentDto>().On<ContentDto, DocumentCultureVariationDto>(x => x.NodeId, x => x.NodeId)
|
||||
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id)
|
||||
.Where<DocumentCultureVariationDto>(x => x.LanguageId == defaultLangId);
|
||||
sqlDelete = Sql()
|
||||
.Delete<DocumentCultureVariationDto>()
|
||||
.WhereIn<DocumentCultureVariationDto>(x => x.Id, sqlSelect);
|
||||
Database.Execute(sqlDelete);
|
||||
|
||||
//now we need to insert names into these 2 tables based on the invariant data
|
||||
|
||||
//insert rows into the versionCultureVariationDto table based on the data from contentVersionDto for the default lang
|
||||
var cols = Sql().Columns<ContentVersionCultureVariationDto>(x => x.VersionId, x => x.Name, x => x.UpdateUserId, x => x.UpdateDate, x => x.LanguageId);
|
||||
sqlSelect = Sql().Select<ContentVersionDto>(x => x.Id, x => x.Text, x => x.UserId, x => x.VersionDate)
|
||||
.Append($", {defaultLangId}") //default language ID
|
||||
.From<ContentVersionDto>()
|
||||
.InnerJoin<ContentDto>().On<ContentDto, ContentVersionDto>(x => x.NodeId, x => x.NodeId)
|
||||
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id);
|
||||
var sqlInsert = Sql($"INSERT INTO {ContentVersionCultureVariationDto.TableName} ({cols})").Append(sqlSelect);
|
||||
Database.Execute(sqlInsert);
|
||||
|
||||
//insert rows into the documentCultureVariation table
|
||||
cols = Sql().Columns<DocumentCultureVariationDto>(x => x.NodeId, x => x.Edited, x => x.Published, x => x.Name, x => x.Available, x => x.LanguageId);
|
||||
sqlSelect = Sql().Select<DocumentDto>(x => x.NodeId, x => x.Edited, x => x.Published)
|
||||
.AndSelect<NodeDto>(x => x.Text)
|
||||
.Append($", 1, {defaultLangId}") //make Available + default language ID
|
||||
.From<DocumentDto>()
|
||||
.InnerJoin<NodeDto>().On<NodeDto, DocumentDto>(x => x.NodeId, x => x.NodeId)
|
||||
.InnerJoin<ContentDto>().On<ContentDto, NodeDto>(x => x.NodeId, x => x.NodeId)
|
||||
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id);
|
||||
sqlInsert = Sql($"INSERT INTO {DocumentCultureVariationDto.TableName} ({cols})").Append(sqlSelect);
|
||||
Database.Execute(sqlInsert);
|
||||
|
||||
break;
|
||||
case ContentVariation.Nothing:
|
||||
//move the property data
|
||||
|
||||
MovePropertyDataToVariantNothing(defaultLangId, sqlPropertyTypeIds: sqlPropertyTypeIds);
|
||||
|
||||
//we dont need to move the names! this is because we always keep the invariant names with the name of the default language.
|
||||
|
||||
//however, if we were to move names, we could do this: BUT this doesn't work with SQLCE, for that we'd have to update row by row :(
|
||||
// if we want these SQL statements back, look into GIT history
|
||||
|
||||
break;
|
||||
case ContentVariation.CultureAndSegment:
|
||||
case ContentVariation.Segment:
|
||||
default:
|
||||
throw new NotSupportedException(); //TODO: Support this
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will move all property data from variant to invariant
|
||||
/// </summary>
|
||||
/// <param name="defaultLangId"></param>
|
||||
/// <param name="propertyTypeIds">Optional list of property type ids of the properties to be updated</param>
|
||||
/// <param name="sqlPropertyTypeIds">Optional SQL statement used for the sub-query to select the properties type ids for the properties to be updated</param>
|
||||
private void MovePropertyDataToVariantNothing(int defaultLangId, IReadOnlyCollection<int> propertyTypeIds = null, Sql<ISqlContext> sqlPropertyTypeIds = null)
|
||||
{
|
||||
//first clear out any existing property data that might already exists under the default lang
|
||||
var sqlDelete = Sql()
|
||||
.Delete<PropertyDataDto>()
|
||||
.Where<PropertyDataDto>(x => x.LanguageId == null);
|
||||
if (sqlPropertyTypeIds != null)
|
||||
sqlDelete.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, sqlPropertyTypeIds);
|
||||
if (propertyTypeIds != null)
|
||||
sqlDelete.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, propertyTypeIds);
|
||||
|
||||
Database.Execute(sqlDelete);
|
||||
|
||||
//now insert all property data into the default language that exists under the invariant lang
|
||||
var cols = Sql().Columns<PropertyDataDto>(x => x.VersionId, x => x.PropertyTypeId, x => x.Segment, x => x.IntegerValue, x => x.DecimalValue, x => x.DateValue, x => x.VarcharValue, x => x.TextValue, x => x.LanguageId);
|
||||
var sqlSelectData = Sql().Select<PropertyDataDto>(x => x.VersionId, x => x.PropertyTypeId, x => x.Segment, x => x.IntegerValue, x => x.DecimalValue, x => x.DateValue, x => x.VarcharValue, x => x.TextValue)
|
||||
.Append(", NULL") //null language ID
|
||||
.From<PropertyDataDto>()
|
||||
.Where<PropertyDataDto>(x => x.LanguageId == defaultLangId);
|
||||
if (sqlPropertyTypeIds != null)
|
||||
sqlSelectData.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, sqlPropertyTypeIds);
|
||||
if (propertyTypeIds != null)
|
||||
sqlSelectData.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, propertyTypeIds);
|
||||
|
||||
var sqlInsert = Sql($"INSERT INTO {PropertyDataDto.TableName} ({cols})").Append(sqlSelectData);
|
||||
|
||||
Database.Execute(sqlInsert);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will move all property data from invariant to variant
|
||||
/// </summary>
|
||||
/// <param name="defaultLangId"></param>
|
||||
/// <param name="propertyTypeIds">Optional list of property type ids of the properties to be updated</param>
|
||||
/// <param name="sqlPropertyTypeIds">Optional SQL statement used for the sub-query to select the properties type ids for the properties to be updated</param>
|
||||
private void MovePropertyDataToVariantCulture(int defaultLangId, IReadOnlyCollection<int> propertyTypeIds = null, Sql<ISqlContext> sqlPropertyTypeIds = null)
|
||||
{
|
||||
//first clear out any existing property data that might already exists under the default lang
|
||||
var sqlDelete = Sql()
|
||||
.Delete<PropertyDataDto>()
|
||||
.Where<PropertyDataDto>(x => x.LanguageId == defaultLangId);
|
||||
if (sqlPropertyTypeIds != null)
|
||||
sqlDelete.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, sqlPropertyTypeIds);
|
||||
if (propertyTypeIds != null)
|
||||
sqlDelete.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, propertyTypeIds);
|
||||
|
||||
Database.Execute(sqlDelete);
|
||||
|
||||
//now insert all property data into the default language that exists under the invariant lang
|
||||
var cols = Sql().Columns<PropertyDataDto>(x => x.VersionId, x => x.PropertyTypeId, x => x.Segment, x => x.IntegerValue, x => x.DecimalValue, x => x.DateValue, x => x.VarcharValue, x => x.TextValue, x => x.LanguageId);
|
||||
var sqlSelectData = Sql().Select<PropertyDataDto>(x => x.VersionId, x => x.PropertyTypeId, x => x.Segment, x => x.IntegerValue, x => x.DecimalValue, x => x.DateValue, x => x.VarcharValue, x => x.TextValue)
|
||||
.Append($", {defaultLangId}") //default language ID
|
||||
.From<PropertyDataDto>()
|
||||
.Where<PropertyDataDto>(x => x.LanguageId == null);
|
||||
if (sqlPropertyTypeIds != null)
|
||||
sqlSelectData.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, sqlPropertyTypeIds);
|
||||
if (propertyTypeIds != null)
|
||||
sqlSelectData.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, propertyTypeIds);
|
||||
|
||||
var sqlInsert = Sql($"INSERT INTO {PropertyDataDto.TableName} ({cols})").Append(sqlSelectData);
|
||||
|
||||
Database.Execute(sqlInsert);
|
||||
}
|
||||
|
||||
private void DeletePropertyType(int contentTypeId, int propertyTypeId)
|
||||
{
|
||||
// first clear dependencies
|
||||
@@ -571,8 +848,11 @@ AND umbracoNode.id <> @id",
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TEntity> GetTypesDirectlyComposedOf(int id)
|
||||
{
|
||||
//fixme - this will probably be more efficient to simply load all content types and do the calculation, see GetWhereCompositionIsUsedInContentTypes
|
||||
|
||||
var sql = Sql()
|
||||
.SelectAll()
|
||||
.From<NodeDto>()
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
return new FullDataSetRepositoryCachePolicy<ILanguage, int>(GlobalIsolatedCache, ScopeAccessor, GetEntityId, /*expires:*/ false);
|
||||
}
|
||||
|
||||
private FullDataSetRepositoryCachePolicy<ILanguage, int> TypedCachePolicy => (FullDataSetRepositoryCachePolicy<ILanguage, int>) CachePolicy;
|
||||
private FullDataSetRepositoryCachePolicy<ILanguage, int> TypedCachePolicy => CachePolicy as FullDataSetRepositoryCachePolicy<ILanguage, int>;
|
||||
|
||||
#region Overrides of RepositoryBase<int,Language>
|
||||
|
||||
@@ -225,7 +225,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
public ILanguage GetByIsoCode(string isoCode)
|
||||
{
|
||||
TypedCachePolicy.GetAllCached(PerformGetAll); // ensure cache is populated, in a non-expensive way
|
||||
// ensure cache is populated, in a non-expensive way
|
||||
if (TypedCachePolicy != null)
|
||||
TypedCachePolicy.GetAllCached(PerformGetAll);
|
||||
|
||||
|
||||
var id = GetIdByIsoCode(isoCode, throwOnNotFound: false);
|
||||
return id.HasValue ? Get(id.Value) : null;
|
||||
}
|
||||
@@ -238,7 +242,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
if (isoCode == null) return null;
|
||||
|
||||
TypedCachePolicy.GetAllCached(PerformGetAll); // ensure cache is populated, in a non-expensive way
|
||||
// ensure cache is populated, in a non-expensive way
|
||||
if (TypedCachePolicy != null)
|
||||
TypedCachePolicy.GetAllCached(PerformGetAll);
|
||||
else
|
||||
PerformGetAll(); //we don't have a typed cache (i.e. unit tests) but need to populate the _codeIdMap
|
||||
|
||||
lock (_codeIdMap)
|
||||
{
|
||||
if (_codeIdMap.TryGetValue(isoCode, out var id)) return id;
|
||||
@@ -256,7 +265,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
if (id == null) return null;
|
||||
|
||||
TypedCachePolicy.GetAllCached(PerformGetAll); // ensure cache is populated, in a non-expensive way
|
||||
// ensure cache is populated, in a non-expensive way
|
||||
if (TypedCachePolicy != null)
|
||||
TypedCachePolicy.GetAllCached(PerformGetAll);
|
||||
else
|
||||
PerformGetAll();
|
||||
|
||||
lock (_codeIdMap) // yes, we want to lock _codeIdMap
|
||||
{
|
||||
if (_idCodeMap.TryGetValue(id.Value, out var isoCode)) return isoCode;
|
||||
@@ -279,8 +293,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
// do NOT leak that language, it's not deep-cloned!
|
||||
private ILanguage GetDefault()
|
||||
{
|
||||
// get all cached, non-cloned
|
||||
var languages = TypedCachePolicy.GetAllCached(PerformGetAll).ToList();
|
||||
// get all cached
|
||||
var languages = (TypedCachePolicy?.GetAllCached(PerformGetAll) //try to get all cached non-cloned if using the correct cache policy (not the case in unit tests)
|
||||
?? CachePolicy.GetAll(Array.Empty<int>(), PerformGetAll)).ToList();
|
||||
|
||||
var language = languages.FirstOrDefault(x => x.IsDefault);
|
||||
if (language != null) return language;
|
||||
|
||||
|
||||
@@ -175,7 +175,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
var list = new List<string>
|
||||
{
|
||||
"DELETE FROM cmsTask WHERE nodeId = @id",
|
||||
"DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @id",
|
||||
"DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @id",
|
||||
"DELETE FROM umbracoRelation WHERE parentId = @id",
|
||||
|
||||
@@ -421,8 +421,6 @@ ORDER BY colName";
|
||||
{
|
||||
var list = new List<string>
|
||||
{
|
||||
"DELETE FROM cmsTask WHERE userId = @id",
|
||||
"DELETE FROM cmsTask WHERE parentUserId = @id",
|
||||
"DELETE FROM umbracoUser2UserGroup WHERE userId = @id",
|
||||
"DELETE FROM umbracoUser2NodeNotify WHERE userId = @id",
|
||||
"DELETE FROM umbracoUser WHERE id = @id",
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Umbraco.Core.PropertyEditors.Validators
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ValidationResult> Validate(object value, string valueType, object dataTypeConfiguration)
|
||||
{
|
||||
var asString = value.ToString();
|
||||
var asString = value == null ? "" : value.ToString();
|
||||
|
||||
var emailVal = new EmailAddressAttribute();
|
||||
|
||||
|
||||
@@ -109,23 +109,7 @@ namespace Umbraco.Core.Services
|
||||
|
||||
return new ContentTypeAvailableCompositionsResults(ancestors, result);
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns the list of content types the composition is used in
|
||||
/// </summary>
|
||||
/// <param name="allContentTypes"></param>
|
||||
/// <param name="ctService"></param>
|
||||
/// <param name="source"></param>
|
||||
/// <returns></returns>
|
||||
internal static IEnumerable<IContentTypeComposition> GetWhereCompositionIsUsedInContentTypes(this IContentTypeService ctService,
|
||||
IContentTypeComposition source,
|
||||
IContentTypeComposition[] allContentTypes)
|
||||
{
|
||||
|
||||
var sourceId = source != null ? source.Id : 0;
|
||||
|
||||
// find which content types are using this composition
|
||||
return allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == sourceId)).ToArray();
|
||||
}
|
||||
|
||||
|
||||
private static IContentTypeComposition[] GetAncestors(IContentTypeComposition ctype, IContentTypeComposition[] allContentTypes)
|
||||
{
|
||||
|
||||
@@ -20,12 +20,12 @@ namespace Umbraco.Core.Services.Implement
|
||||
internal static event TypedEventHandler<TService, ContentTypeChange<TItem>.EventArgs> Changed;
|
||||
|
||||
// that one is always immediate (transactional)
|
||||
public static event TypedEventHandler<TService, ContentTypeChange<TItem>.EventArgs> UowRefreshedEntity;
|
||||
public static event TypedEventHandler<TService, ContentTypeChange<TItem>.EventArgs> ScopedRefreshedEntity;
|
||||
|
||||
// used by tests to clear events
|
||||
internal static void ClearScopeEvents()
|
||||
{
|
||||
UowRefreshedEntity = null;
|
||||
ScopedRefreshedEntity = null;
|
||||
}
|
||||
|
||||
// these must be dispatched
|
||||
@@ -48,7 +48,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
protected void OnUowRefreshedEntity(ContentTypeChange<TItem>.EventArgs args)
|
||||
{
|
||||
// that one is always immediate (not dispatched, transactional)
|
||||
UowRefreshedEntity.RaiseEvent(args, This);
|
||||
ScopedRefreshedEntity.RaiseEvent(args, This);
|
||||
}
|
||||
|
||||
protected bool OnSavingCancelled(IScope scope, SaveEventArgs<TItem> args)
|
||||
|
||||
+18
-5
@@ -118,6 +118,8 @@ namespace Umbraco.Core.Services.Implement
|
||||
// - content type alias changed
|
||||
// - content type property removed, or alias changed
|
||||
// - content type composition removed (not testing if composition had properties...)
|
||||
// - content type variation changed
|
||||
// - property type variation changed
|
||||
//
|
||||
// because these are the changes that would impact the raw content data
|
||||
|
||||
@@ -132,7 +134,8 @@ namespace Umbraco.Core.Services.Implement
|
||||
var dirty = (IRememberBeingDirty)contentType;
|
||||
|
||||
// skip new content types
|
||||
var isNewContentType = dirty.WasPropertyDirty("HasIdentity");
|
||||
//TODO: This used to be WasPropertyDirty("HasIdentity") but i don't think that actually worked for detecting new entities this does seem to work properly
|
||||
var isNewContentType = dirty.WasPropertyDirty("Id");
|
||||
if (isNewContentType)
|
||||
{
|
||||
AddChange(changes, contentType, ContentTypeChangeTypes.Create);
|
||||
@@ -149,12 +152,12 @@ namespace Umbraco.Core.Services.Implement
|
||||
throw new Exception("oops");
|
||||
|
||||
// skip new properties
|
||||
var isNewProperty = dirtyProperty.WasPropertyDirty("HasIdentity");
|
||||
//TODO: This used to be WasPropertyDirty("HasIdentity") but i don't think that actually worked for detecting new entities this does seem to work properly
|
||||
var isNewProperty = dirtyProperty.WasPropertyDirty("Id");
|
||||
if (isNewProperty) return false;
|
||||
|
||||
// alias change?
|
||||
var hasPropertyAliasBeenChanged = dirtyProperty.WasPropertyDirty("Alias");
|
||||
return hasPropertyAliasBeenChanged;
|
||||
return dirtyProperty.WasPropertyDirty("Alias");
|
||||
});
|
||||
|
||||
// removed properties?
|
||||
@@ -163,8 +166,15 @@ namespace Umbraco.Core.Services.Implement
|
||||
// removed compositions?
|
||||
var hasAnyCompositionBeenRemoved = dirty.WasPropertyDirty("HasCompositionTypeBeenRemoved");
|
||||
|
||||
// variation changed?
|
||||
var hasContentTypeVariationChanged = dirty.WasPropertyDirty("Variations");
|
||||
|
||||
// property variation change?
|
||||
var hasAnyPropertyVariationChanged = contentType.WasPropertyTypeVariationChanged();
|
||||
|
||||
// main impact on properties?
|
||||
var hasPropertyMainImpact = hasAnyCompositionBeenRemoved || hasAnyPropertyBeenRemoved || hasAnyPropertyChangedAlias;
|
||||
var hasPropertyMainImpact = hasContentTypeVariationChanged || hasAnyPropertyVariationChanged
|
||||
|| hasAnyCompositionBeenRemoved || hasAnyPropertyBeenRemoved || hasAnyPropertyChangedAlias;
|
||||
|
||||
if (hasAliasChanged || hasPropertyMainImpact)
|
||||
{
|
||||
@@ -336,6 +346,9 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
public IEnumerable<TItem> GetComposedOf(int id)
|
||||
{
|
||||
//fixme: this is essentially the same as ContentTypeServiceExtensions.GetWhereCompositionIsUsedInContentTypes which loads
|
||||
// all content types to figure this out, this instead makes quite a few queries so should be replaced
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(ReadLockIds);
|
||||
|
||||
@@ -258,7 +258,7 @@ AnotherContentFinder
|
||||
public void Resolves_Assigned_Mappers()
|
||||
{
|
||||
var foundTypes1 = _typeLoader.GetAssignedMapperTypes();
|
||||
Assert.AreEqual(30, foundTypes1.Count());
|
||||
Assert.AreEqual(29, foundTypes1.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -279,7 +279,7 @@ AnotherContentFinder
|
||||
public void Resolves_Trees()
|
||||
{
|
||||
var trees = _typeLoader.GetTrees();
|
||||
Assert.AreEqual(3, trees.Count());
|
||||
Assert.AreEqual(1, trees.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -63,8 +63,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
}
|
||||
|
||||
//TODO Add test to verify SetDefaultTemplates updates both AllowedTemplates and DefaultTemplate(id).
|
||||
|
||||
|
||||
|
||||
[Test]
|
||||
public void Maps_Templates_Correctly()
|
||||
{
|
||||
@@ -377,7 +376,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
repository.Save(contentType);
|
||||
|
||||
|
||||
var dirty = ((ICanBeDirty)contentType).IsDirty();
|
||||
var dirty = contentType.IsDirty();
|
||||
|
||||
// Assert
|
||||
Assert.That(contentType.HasIdentity, Is.True);
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
{
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
|
||||
public class ContentRepositoryTest : TestWithDatabaseBase
|
||||
public class DocumentRepositoryTest : TestWithDatabaseBase
|
||||
{
|
||||
public override void SetUp()
|
||||
{
|
||||
@@ -62,17 +62,21 @@ namespace Umbraco.Tests.PublishedContent
|
||||
|
||||
internal override void PopulateCache(PublishedContentTypeFactory factory, SolidPublishedContentCache cache)
|
||||
{
|
||||
var prop1Type = factory.CreatePropertyType("prop1", 1);
|
||||
var welcomeType = factory.CreatePropertyType("welcomeText", 1);
|
||||
var welcome2Type = factory.CreatePropertyType("welcomeText2", 1);
|
||||
var props = new[]
|
||||
{
|
||||
factory.CreatePropertyType("prop1", 1),
|
||||
factory.CreatePropertyType("welcomeText", 1),
|
||||
factory.CreatePropertyType("welcomeText2", 1),
|
||||
prop1Type,
|
||||
welcomeType,
|
||||
welcome2Type,
|
||||
};
|
||||
var contentType1 = factory.CreateContentType(1, "ContentType1", Enumerable.Empty<string>(), props);
|
||||
|
||||
var prop1 = new SolidPublishedPropertyWithLanguageVariants
|
||||
{
|
||||
Alias = "welcomeText",
|
||||
PropertyType = welcomeType
|
||||
};
|
||||
prop1.SetSourceValue("en-US", "Welcome", true);
|
||||
prop1.SetValue("en-US", "Welcome", true);
|
||||
@@ -84,6 +88,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
var prop2 = new SolidPublishedPropertyWithLanguageVariants
|
||||
{
|
||||
Alias = "welcomeText2",
|
||||
PropertyType = welcome2Type
|
||||
};
|
||||
prop2.SetSourceValue("en-US", "Welcome", true);
|
||||
prop2.SetValue("en-US", "Welcome", true);
|
||||
@@ -91,6 +96,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
var prop3 = new SolidPublishedPropertyWithLanguageVariants
|
||||
{
|
||||
Alias = "welcomeText",
|
||||
PropertyType = welcomeType
|
||||
};
|
||||
prop3.SetSourceValue("en-US", "Welcome", true);
|
||||
prop3.SetValue("en-US", "Welcome", true);
|
||||
|
||||
@@ -15,6 +15,7 @@ using Umbraco.Core.Services.Implement;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.TestHelpers.Entities;
|
||||
using Umbraco.Tests.Testing;
|
||||
using Umbraco.Core.Components;
|
||||
|
||||
namespace Umbraco.Tests.Services
|
||||
{
|
||||
@@ -23,6 +24,361 @@ namespace Umbraco.Tests.Services
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, PublishedRepositoryEvents = true)]
|
||||
public class ContentTypeServiceTests : TestWithSomeContentBase
|
||||
{
|
||||
[Test]
|
||||
public void Change_Content_Type_Variation_Clears_Redirects()
|
||||
{
|
||||
//create content type with a property type that varies by culture
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
contentType.Variations = ContentVariation.Nothing;
|
||||
var contentCollection = new PropertyTypeCollection(true);
|
||||
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "title",
|
||||
Name = "Title",
|
||||
Description = "",
|
||||
Mandatory = false,
|
||||
SortOrder = 1,
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Nothing
|
||||
});
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
var contentType2 = MockedContentTypes.CreateBasicContentType("test");
|
||||
ServiceContext.ContentTypeService.Save(contentType2);
|
||||
|
||||
//create some content of this content type
|
||||
IContent doc = MockedContent.CreateBasicContent(contentType);
|
||||
doc.Name = "Hello1";
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
|
||||
IContent doc2 = MockedContent.CreateBasicContent(contentType2);
|
||||
ServiceContext.ContentService.Save(doc2);
|
||||
|
||||
ServiceContext.RedirectUrlService.Register("hello/world", doc.Key);
|
||||
ServiceContext.RedirectUrlService.Register("hello2/world2", doc2.Key);
|
||||
|
||||
Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
|
||||
Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count());
|
||||
|
||||
//change variation
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
Assert.AreEqual(0, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
|
||||
Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count());
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Content_Type_From_Invariant_Variant()
|
||||
{
|
||||
//create content type with a property type that varies by culture
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
contentType.Variations = ContentVariation.Nothing;
|
||||
var contentCollection = new PropertyTypeCollection(true);
|
||||
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "title",
|
||||
Name = "Title",
|
||||
Description = "",
|
||||
Mandatory = false,
|
||||
SortOrder = 1,
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Nothing
|
||||
});
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
//create some content of this content type
|
||||
IContent doc = MockedContent.CreateBasicContent(contentType);
|
||||
doc.Name = "Hello1";
|
||||
doc.SetValue("title", "hello world");
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
|
||||
Assert.AreEqual("Hello1", doc.Name);
|
||||
Assert.AreEqual("hello world", doc.GetValue("title"));
|
||||
|
||||
//change the content type to be variant, we will also update the name here to detect the copy changes
|
||||
doc.Name = "Hello2";
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
Assert.AreEqual("Hello2", doc.GetCultureName("en-US"));
|
||||
Assert.AreEqual("hello world", doc.GetValue("title")); //We are not checking against en-US here because properties will remain invariant
|
||||
|
||||
//change back property type to be invariant, we will also update the name here to detect the copy changes
|
||||
doc.SetCultureName("Hello3", "en-US");
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
contentType.Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
Assert.AreEqual("Hello3", doc.Name);
|
||||
Assert.AreEqual("hello world", doc.GetValue("title"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Content_Type_From_Variant_Invariant()
|
||||
{
|
||||
//create content type with a property type that varies by culture
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
var contentCollection = new PropertyTypeCollection(true);
|
||||
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "title",
|
||||
Name = "Title",
|
||||
Description = "",
|
||||
Mandatory = false,
|
||||
SortOrder = 1,
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Culture
|
||||
});
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
//create some content of this content type
|
||||
IContent doc = MockedContent.CreateBasicContent(contentType);
|
||||
doc.SetCultureName("Hello1", "en-US");
|
||||
doc.SetValue("title", "hello world", "en-US");
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
|
||||
Assert.AreEqual("Hello1", doc.GetCultureName("en-US"));
|
||||
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
|
||||
|
||||
//change the content type to be invariant, we will also update the name here to detect the copy changes
|
||||
doc.SetCultureName("Hello2", "en-US");
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
contentType.Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
Assert.AreEqual("Hello2", doc.Name);
|
||||
Assert.AreEqual("hello world", doc.GetValue("title"));
|
||||
|
||||
//change back property type to be variant, we will also update the name here to detect the copy changes
|
||||
doc.Name = "Hello3";
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
//at this stage all property types were switched to invariant so even though the variant value
|
||||
//exists it will not be returned because the property type is invariant,
|
||||
//so this check proves that null will be returned
|
||||
Assert.IsNull(doc.GetValue("title", "en-US"));
|
||||
|
||||
//we can now switch the property type to be variant and the value can be returned again
|
||||
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
Assert.AreEqual("Hello3", doc.GetCultureName("en-US"));
|
||||
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Property_Type_From_Invariant_Variant()
|
||||
{
|
||||
//create content type with a property type that varies by culture
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
contentType.Variations = ContentVariation.Nothing;
|
||||
var contentCollection = new PropertyTypeCollection(true);
|
||||
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "title",
|
||||
Name = "Title",
|
||||
Description = "",
|
||||
Mandatory = false,
|
||||
SortOrder = 1,
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Nothing
|
||||
});
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
//create some content of this content type
|
||||
IContent doc = MockedContent.CreateBasicContent(contentType);
|
||||
doc.Name = "Home";
|
||||
doc.SetValue("title", "hello world");
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
|
||||
Assert.AreEqual("hello world", doc.GetValue("title"));
|
||||
|
||||
//change the property type to be variant
|
||||
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
|
||||
|
||||
//change back property type to be invariant
|
||||
contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
Assert.AreEqual("hello world", doc.GetValue("title"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Property_Type_From_Variant_Invariant()
|
||||
{
|
||||
//create content type with a property type that varies by culture
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
var contentCollection = new PropertyTypeCollection(true);
|
||||
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "title",
|
||||
Name = "Title",
|
||||
Description = "",
|
||||
Mandatory = false,
|
||||
SortOrder = 1,
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Culture
|
||||
});
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
//create some content of this content type
|
||||
IContent doc = MockedContent.CreateBasicContent(contentType);
|
||||
doc.SetCultureName("Home", "en-US");
|
||||
doc.SetValue("title", "hello world", "en-US");
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
|
||||
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
|
||||
|
||||
//change the property type to be invariant
|
||||
contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
Assert.AreEqual("hello world", doc.GetValue("title"));
|
||||
|
||||
//change back property type to be variant
|
||||
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Property_Type_From_Variant_Invariant_On_A_Composition()
|
||||
{
|
||||
//create content type with a property type that varies by culture
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
var contentCollection = new PropertyTypeCollection(true);
|
||||
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "title",
|
||||
Name = "Title",
|
||||
Description = "",
|
||||
Mandatory = false,
|
||||
SortOrder = 1,
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Culture
|
||||
});
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
//compose this from the other one
|
||||
var contentType2 = MockedContentTypes.CreateBasicContentType("test");
|
||||
contentType2.Variations = ContentVariation.Culture;
|
||||
contentType2.AddContentType(contentType);
|
||||
ServiceContext.ContentTypeService.Save(contentType2);
|
||||
|
||||
//create some content of this content type
|
||||
IContent doc = MockedContent.CreateBasicContent(contentType);
|
||||
doc.SetCultureName("Home", "en-US");
|
||||
doc.SetValue("title", "hello world", "en-US");
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
|
||||
IContent doc2 = MockedContent.CreateBasicContent(contentType2);
|
||||
doc2.SetCultureName("Home", "en-US");
|
||||
doc2.SetValue("title", "hello world", "en-US");
|
||||
ServiceContext.ContentService.Save(doc2);
|
||||
|
||||
//change the property type to be invariant
|
||||
contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
|
||||
|
||||
Assert.AreEqual("hello world", doc.GetValue("title"));
|
||||
Assert.AreEqual("hello world", doc2.GetValue("title"));
|
||||
|
||||
//change back property type to be variant
|
||||
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
|
||||
|
||||
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
|
||||
Assert.AreEqual("hello world", doc2.GetValue("title", "en-US"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Content_Type_From_Variant_Invariant_On_A_Composition()
|
||||
{
|
||||
//create content type with a property type that varies by culture
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
var contentCollection = new PropertyTypeCollection(true);
|
||||
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "title",
|
||||
Name = "Title",
|
||||
Description = "",
|
||||
Mandatory = false,
|
||||
SortOrder = 1,
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Culture
|
||||
});
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
//compose this from the other one
|
||||
var contentType2 = MockedContentTypes.CreateBasicContentType("test");
|
||||
contentType2.Variations = ContentVariation.Culture;
|
||||
contentType2.AddContentType(contentType);
|
||||
ServiceContext.ContentTypeService.Save(contentType2);
|
||||
|
||||
//create some content of this content type
|
||||
IContent doc = MockedContent.CreateBasicContent(contentType);
|
||||
doc.SetCultureName("Home", "en-US");
|
||||
doc.SetValue("title", "hello world", "en-US");
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
|
||||
IContent doc2 = MockedContent.CreateBasicContent(contentType2);
|
||||
doc2.SetCultureName("Home", "en-US");
|
||||
doc2.SetValue("title", "hello world", "en-US");
|
||||
ServiceContext.ContentService.Save(doc2);
|
||||
|
||||
//change the content type to be invariant
|
||||
contentType.Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
|
||||
|
||||
Assert.AreEqual("hello world", doc.GetValue("title"));
|
||||
Assert.AreEqual("hello world", doc2.GetValue("title"));
|
||||
|
||||
//change back content type to be variant
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
|
||||
|
||||
//this will be null because the doc type was changed back to variant but it's property types don't get changed back
|
||||
Assert.IsNull(doc.GetValue("title", "en-US"));
|
||||
Assert.IsNull(doc2.GetValue("title", "en-US"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Deleting_Media_Type_With_Hierarchy_Of_Media_Items_Moves_Orphaned_Media_To_Recycle_Bin()
|
||||
{
|
||||
|
||||
@@ -123,6 +123,7 @@
|
||||
<Compile Include="Migrations\MigrationTests.cs" />
|
||||
<Compile Include="Models\PathValidationTests.cs" />
|
||||
<Compile Include="Models\VariationTests.cs" />
|
||||
<Compile Include="Persistence\Repositories\DocumentRepositoryTest.cs" />
|
||||
<Compile Include="PublishedContent\PublishedContentLanguageVariantTests.cs" />
|
||||
<Compile Include="PublishedContent\PublishedContentSnapshotTestBase.cs" />
|
||||
<Compile Include="PublishedContent\SolidPublishedSnapshot.cs" />
|
||||
@@ -391,7 +392,6 @@
|
||||
<Compile Include="Persistence\Mappers\RelationTypeMapperTest.cs" />
|
||||
<Compile Include="Persistence\NPocoTests\NPocoSqlTests.cs" />
|
||||
<Compile Include="Persistence\Querying\QueryBuilderTests.cs" />
|
||||
<Compile Include="Persistence\Repositories\ContentRepositoryTest.cs" />
|
||||
<Compile Include="Persistence\Repositories\ContentTypeRepositoryTest.cs" />
|
||||
<Compile Include="Persistence\Repositories\DataTypeDefinitionRepositoryTest.cs" />
|
||||
<Compile Include="Persistence\Repositories\DictionaryRepositoryTest.cs" />
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"presets": ["@babel/preset-env"]
|
||||
}
|
||||
@@ -7,6 +7,10 @@
|
||||
"comma-dangle": ["error", "never"]
|
||||
},
|
||||
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6
|
||||
},
|
||||
|
||||
"globals": {
|
||||
"angular": false,
|
||||
"_": false,
|
||||
|
||||
@@ -6,6 +6,7 @@ var wrap = require("gulp-wrap-js");
|
||||
var sort = require('gulp-sort');
|
||||
var connect = require('gulp-connect');
|
||||
var open = require('gulp-open');
|
||||
const babel = require("gulp-babel");
|
||||
var runSequence = require('run-sequence');
|
||||
const imagemin = require('gulp-imagemin');
|
||||
|
||||
@@ -33,17 +34,18 @@ Helper functions
|
||||
function processJs(files, out) {
|
||||
|
||||
return gulp.src(files)
|
||||
// check for js errors
|
||||
.pipe(eslint())
|
||||
// outputs the lint results to the console
|
||||
.pipe(eslint.format())
|
||||
// sort files in stream by path or any custom sort comparator
|
||||
.pipe(sort())
|
||||
.pipe(concat(out))
|
||||
.pipe(wrap('(function(){\n%= body %\n})();'))
|
||||
.pipe(gulp.dest(root + targets.js));
|
||||
// check for js errors
|
||||
.pipe(eslint())
|
||||
// outputs the lint results to the console
|
||||
.pipe(eslint.format())
|
||||
// sort files in stream by path or any custom sort comparator
|
||||
.pipe(babel())
|
||||
.pipe(sort())
|
||||
.pipe(concat(out))
|
||||
.pipe(wrap('(function(){\n%= body %\n})();'))
|
||||
.pipe(gulp.dest(root + targets.js));
|
||||
|
||||
console.log(out + " compiled");
|
||||
console.log(out + " compiled");
|
||||
}
|
||||
|
||||
function processLess(files, out) {
|
||||
@@ -142,7 +144,7 @@ gulp.task('docserve', function(cb) {
|
||||
**************************/
|
||||
gulp.task('dependencies', function () {
|
||||
|
||||
//bower component specific copy rules
|
||||
//bower component/npm specific copy rules
|
||||
//this is to patch the sometimes wonky rules these libs are distrbuted under
|
||||
|
||||
//as we do multiple things in this task, we merge the multiple streams
|
||||
@@ -199,6 +201,16 @@ gulp.task('dependencies', function () {
|
||||
.pipe(gulp.dest(root + targets.lib + "/codemirror"))
|
||||
);
|
||||
|
||||
// npm dependencies
|
||||
// flatpickr
|
||||
stream.add(
|
||||
gulp.src([
|
||||
"./node_modules/flatpickr/dist/flatpickr.js",
|
||||
"./node_modules/flatpickr/dist/flatpickr.css"],
|
||||
{ base: "./node_modules/flatpickr/dist" })
|
||||
.pipe(gulp.dest(root + targets.lib + "/flatpickr"))
|
||||
);
|
||||
|
||||
//copy over libs which are not on bower (/lib) and
|
||||
//libraries that have been managed by bower-installer (/lib-bower)
|
||||
stream.add(
|
||||
|
||||
+3510
-2557
File diff suppressed because it is too large
Load Diff
@@ -5,13 +5,17 @@
|
||||
"build": "gulp"
|
||||
},
|
||||
"dependencies": {
|
||||
"flatpickr": "4.5.2",
|
||||
"npm": "^6.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.1.2",
|
||||
"@babel/preset-env": "^7.1.0",
|
||||
"autoprefixer": "^6.5.0",
|
||||
"bower-installer": "^1.2.0",
|
||||
"cssnano": "^3.7.6",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-babel": "^8.0.0-beta.2",
|
||||
"gulp-concat": "^2.6.0",
|
||||
"gulp-connect": "5.0.0",
|
||||
"gulp-eslint": "^5.0.0",
|
||||
|
||||
+27
-10
@@ -97,17 +97,9 @@
|
||||
eventsService.unsubscribe(evts[e]);
|
||||
}
|
||||
|
||||
evts.push(eventsService.on("editors.content.changePublishDate", function (event, args) {
|
||||
createButtons(args.node);
|
||||
}));
|
||||
|
||||
evts.push(eventsService.on("editors.content.changeUnpublishDate", function (event, args) {
|
||||
createButtons(args.node);
|
||||
}));
|
||||
|
||||
evts.push(eventsService.on("editors.documentType.saved", function (name, args) {
|
||||
// if this content item uses the updated doc type we need to reload the content item
|
||||
if (args && args.documentType && args.documentType.key === content.documentType.key) {
|
||||
if (args && args.documentType && args.documentType.key === $scope.content.documentType.key) {
|
||||
loadContent();
|
||||
}
|
||||
}));
|
||||
@@ -177,7 +169,8 @@
|
||||
methods: {
|
||||
saveAndPublish: $scope.saveAndPublish,
|
||||
sendToPublish: $scope.sendToPublish,
|
||||
unpublish: $scope.unpublish
|
||||
unpublish: $scope.unpublish,
|
||||
schedulePublish: $scope.schedule
|
||||
}
|
||||
});
|
||||
|
||||
@@ -611,6 +604,30 @@
|
||||
|
||||
};
|
||||
|
||||
$scope.schedule = function() {
|
||||
clearNotifications($scope.content);
|
||||
//before we launch the dialog we want to execute all client side validations first
|
||||
if (formHelper.submitForm({ scope: $scope, action: "schedule" })) {
|
||||
|
||||
var dialog = {
|
||||
parentScope: $scope,
|
||||
view: "views/content/overlays/schedule.html",
|
||||
variants: $scope.content.variants, //set a model property for the dialog
|
||||
skipFormValidation: true, //when submitting the overlay form, skip any client side validation
|
||||
submitButtonLabel: "Schedule",
|
||||
submit: function (model) {
|
||||
model.submitButtonState = "busy";
|
||||
clearNotifications($scope.content);
|
||||
model.submitButtonState = "success";
|
||||
},
|
||||
close: function () {
|
||||
overlayService.close();
|
||||
}
|
||||
};
|
||||
overlayService.open(dialog);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.preview = function (content) {
|
||||
|
||||
|
||||
|
||||
-124
@@ -46,19 +46,6 @@
|
||||
|
||||
});
|
||||
|
||||
scope.datePickerConfig = {
|
||||
pickDate: true,
|
||||
pickTime: true,
|
||||
useSeconds: false,
|
||||
format: "YYYY-MM-DD HH:mm",
|
||||
icons: {
|
||||
time: "icon-time",
|
||||
date: "icon-calendar",
|
||||
up: "icon-chevron-up",
|
||||
down: "icon-chevron-down"
|
||||
}
|
||||
};
|
||||
|
||||
scope.auditTrailOptions = {
|
||||
"id": scope.node.id
|
||||
};
|
||||
@@ -69,9 +56,6 @@
|
||||
// get document type details
|
||||
scope.documentType = scope.node.documentType;
|
||||
|
||||
// make sure dates are formatted to the user's locale
|
||||
formatDatesToLocal();
|
||||
|
||||
//default setting for redirect url management
|
||||
scope.urlTrackerDisabled = false;
|
||||
|
||||
@@ -117,22 +101,6 @@
|
||||
scope.node.template = templateAlias;
|
||||
};
|
||||
|
||||
scope.datePickerChange = function (event, type) {
|
||||
if (type === 'publish') {
|
||||
setPublishDate(event.date.format("YYYY-MM-DD HH:mm"));
|
||||
} else if (type === 'unpublish') {
|
||||
setUnpublishDate(event.date.format("YYYY-MM-DD HH:mm"));
|
||||
}
|
||||
};
|
||||
|
||||
scope.clearPublishDate = function () {
|
||||
clearPublishDate();
|
||||
};
|
||||
|
||||
scope.clearUnpublishDate = function () {
|
||||
clearUnpublishDate();
|
||||
};
|
||||
|
||||
function loadAuditTrail() {
|
||||
|
||||
scope.loadingAuditTrail = true;
|
||||
@@ -243,97 +211,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
function setPublishDate(date) {
|
||||
|
||||
if (!date) {
|
||||
return;
|
||||
}
|
||||
|
||||
//The date being passed in here is the user's local date/time that they have selected
|
||||
//we need to convert this date back to the server date on the model.
|
||||
|
||||
var serverTime = dateHelper.convertToServerStringTime(moment(date), Umbraco.Sys.ServerVariables.application.serverTimeOffset);
|
||||
|
||||
// update publish value
|
||||
scope.node.releaseDate = serverTime;
|
||||
|
||||
// make sure dates are formatted to the user's locale
|
||||
formatDatesToLocal();
|
||||
|
||||
// emit event
|
||||
var args = { node: scope.node, date: date };
|
||||
eventsService.emit("editors.content.changePublishDate", args);
|
||||
|
||||
}
|
||||
|
||||
function clearPublishDate() {
|
||||
|
||||
// update publish value
|
||||
scope.node.releaseDate = null;
|
||||
|
||||
// emit event
|
||||
var args = { node: scope.node, date: null };
|
||||
eventsService.emit("editors.content.changePublishDate", args);
|
||||
|
||||
}
|
||||
|
||||
function setUnpublishDate(date) {
|
||||
|
||||
if (!date) {
|
||||
return;
|
||||
}
|
||||
|
||||
//The date being passed in here is the user's local date/time that they have selected
|
||||
//we need to convert this date back to the server date on the model.
|
||||
|
||||
var serverTime = dateHelper.convertToServerStringTime(moment(date), Umbraco.Sys.ServerVariables.application.serverTimeOffset);
|
||||
|
||||
// update publish value
|
||||
scope.node.removeDate = serverTime;
|
||||
|
||||
// make sure dates are formatted to the user's locale
|
||||
formatDatesToLocal();
|
||||
|
||||
// emit event
|
||||
var args = { node: scope.node, date: date };
|
||||
eventsService.emit("editors.content.changeUnpublishDate", args);
|
||||
|
||||
}
|
||||
|
||||
function clearUnpublishDate() {
|
||||
|
||||
// update publish value
|
||||
scope.node.removeDate = null;
|
||||
|
||||
// emit event
|
||||
var args = { node: scope.node, date: null };
|
||||
eventsService.emit("editors.content.changeUnpublishDate", args);
|
||||
|
||||
}
|
||||
|
||||
function ucfirst(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
||||
function formatDatesToLocal() {
|
||||
// get current backoffice user and format dates
|
||||
userService.getCurrentUser().then(function (currentUser) {
|
||||
scope.node.createDateFormatted = dateHelper.getLocalDate(scope.node.createDate, currentUser.locale, 'LLL');
|
||||
|
||||
scope.node.releaseDateYear = scope.node.releaseDate ? ucfirst(dateHelper.getLocalDate(scope.node.releaseDate, currentUser.locale, 'YYYY')) : null;
|
||||
scope.node.releaseDateMonth = scope.node.releaseDate ? ucfirst(dateHelper.getLocalDate(scope.node.releaseDate, currentUser.locale, 'MMMM')) : null;
|
||||
scope.node.releaseDateDayNumber = scope.node.releaseDate ? ucfirst(dateHelper.getLocalDate(scope.node.releaseDate, currentUser.locale, 'DD')) : null;
|
||||
scope.node.releaseDateDay = scope.node.releaseDate ? ucfirst(dateHelper.getLocalDate(scope.node.releaseDate, currentUser.locale, 'dddd')) : null;
|
||||
scope.node.releaseDateTime = scope.node.releaseDate ? ucfirst(dateHelper.getLocalDate(scope.node.releaseDate, currentUser.locale, 'HH:mm')) : null;
|
||||
|
||||
scope.node.removeDateYear = scope.node.removeDate ? ucfirst(dateHelper.getLocalDate(scope.node.removeDate, currentUser.locale, 'YYYY')) : null;
|
||||
scope.node.removeDateMonth = scope.node.removeDate ? ucfirst(dateHelper.getLocalDate(scope.node.removeDate, currentUser.locale, 'MMMM')) : null;
|
||||
scope.node.removeDateDayNumber = scope.node.removeDate ? ucfirst(dateHelper.getLocalDate(scope.node.removeDate, currentUser.locale, 'DD')) : null;
|
||||
scope.node.removeDateDay = scope.node.removeDate ? ucfirst(dateHelper.getLocalDate(scope.node.removeDate, currentUser.locale, 'dddd')) : null;
|
||||
scope.node.removeDateTime = scope.node.removeDate ? ucfirst(dateHelper.getLocalDate(scope.node.removeDate, currentUser.locale, 'HH:mm')) : null;
|
||||
});
|
||||
}
|
||||
|
||||
// load audit trail and redirects when on the info tab
|
||||
evts.push(eventsService.on("app.tabChange", function (event, args) {
|
||||
$timeout(function () {
|
||||
@@ -356,7 +233,6 @@
|
||||
if(isInfoTab) {
|
||||
loadAuditTrail();
|
||||
loadRedirectUrls();
|
||||
formatDatesToLocal();
|
||||
setNodePublishStatus(scope.node);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -173,6 +173,12 @@ angular.module('umbraco.directives')
|
||||
return;
|
||||
}
|
||||
|
||||
// ignore clicks in flatpickr datepicker
|
||||
var flatpickr = $(event.target).closest(".flatpickr-calendar");
|
||||
if (flatpickr.length === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
//ignore clicks inside this element
|
||||
if( $(element).has( $(event.target) ).length > 0 ){
|
||||
return;
|
||||
|
||||
+4
-4
@@ -44,11 +44,11 @@ angular.module("umbraco.directives")
|
||||
var stylesheets = [];
|
||||
|
||||
var styleFormats = [];
|
||||
var await = [];
|
||||
var requests = [];
|
||||
|
||||
//queue file loading
|
||||
if (typeof (tinymce) === "undefined") {
|
||||
await.push(assetsService.loadJs("lib/tinymce/tinymce.min.js", scope));
|
||||
requests.push(assetsService.loadJs("lib/tinymce/tinymce.min.js", scope));
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ angular.module("umbraco.directives")
|
||||
angular.forEach(scope.configuration.stylesheets, function(stylesheet, key){
|
||||
|
||||
stylesheets.push(Umbraco.Sys.ServerVariables.umbracoSettings.cssPath + "/" + stylesheet + ".css");
|
||||
await.push(stylesheetResource.getRulesByName(stylesheet).then(function (rules) {
|
||||
requests.push(stylesheetResource.getRulesByName(stylesheet).then(function (rules) {
|
||||
angular.forEach(rules, function (rule) {
|
||||
var r = {};
|
||||
var split = "";
|
||||
@@ -97,7 +97,7 @@ angular.module("umbraco.directives")
|
||||
|
||||
//stores a reference to the editor
|
||||
var tinyMceEditor = null;
|
||||
$q.all(await).then(function () {
|
||||
$q.all(requests).then(function () {
|
||||
|
||||
var uniqueId = scope.uniqueId;
|
||||
|
||||
|
||||
+6
@@ -700,6 +700,12 @@ Opens an overlay to show a custom YSOD. </br>
|
||||
|
||||
};
|
||||
|
||||
scope.outSideClick = function() {
|
||||
if(!scope.model.disableBackdropClick) {
|
||||
scope.closeOverLay();
|
||||
}
|
||||
};
|
||||
|
||||
unsubscribe.push(unregisterOverlay);
|
||||
scope.$on('$destroy', function () {
|
||||
for (var i = 0; i < unsubscribe.length; i++) {
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
/**
|
||||
@ngdoc directive
|
||||
@name umbraco.directives.directive:umbFlatpickr
|
||||
@restrict E
|
||||
@scope
|
||||
|
||||
@description
|
||||
<b>Added in Umbraco version 8.0</b>
|
||||
This directive is a wrapper of the flatpickr library. Use it to render a date time picker.
|
||||
For extra details about options and events take a look here: https://flatpickr.js.org/
|
||||
|
||||
Use this directive to render a date time picker
|
||||
|
||||
<h3>Markup example</h3>
|
||||
<pre>
|
||||
<div ng-controller="My.Controller as vm">
|
||||
|
||||
<umb-flatpickr
|
||||
ng-model="vm.date"
|
||||
options="vm.config"
|
||||
on-change="vm.datePickerChange(selectedDates, dateStr, instance)">
|
||||
</umb-flatpickr>
|
||||
|
||||
</div>
|
||||
</pre>
|
||||
|
||||
<h3>Controller example</h3>
|
||||
<pre>
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function Controller() {
|
||||
|
||||
var vm = this;
|
||||
|
||||
vm.date = "2018-10-10 10:00";
|
||||
|
||||
vm.config = {
|
||||
enableTime: true,
|
||||
dateFormat: "Y-m-d H:i",
|
||||
time_24hr: true
|
||||
};
|
||||
|
||||
vm.datePickerChange = datePickerChange;
|
||||
|
||||
function datePickerChange(selectedDates, dateStr, instance) {
|
||||
// handle change
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
angular.module("umbraco").controller("My.Controller", Controller);
|
||||
|
||||
})();
|
||||
</pre>
|
||||
|
||||
@param {object} ngModel (<code>binding</code>): Config object for the date picker.
|
||||
@param {object} options (<code>binding</code>): Config object for the date picker.
|
||||
@param {callback} onSetup (<code>callback</code>): onSetup gets triggered when the date picker is initialized
|
||||
@param {callback} onChange (<code>callback</code>): onChange gets triggered when the user selects a date, or changes the time on a selected date.
|
||||
@param {callback} onOpen (<code>callback</code>): onOpen gets triggered when the calendar is opened.
|
||||
@param {callback} onClose (<code>callback</code>): onClose gets triggered when the calendar is closed.
|
||||
@param {callback} onMonthChange (<code>callback</code>): onMonthChange gets triggered when the month is changed, either by the user or programmatically.
|
||||
@param {callback} onYearChange (<code>callback</code>): onMonthChange gets triggered when the year is changed, either by the user or programmatically.
|
||||
@param {callback} onReady (<code>callback</code>): onReady gets triggered once the calendar is in a ready state.
|
||||
@param {callback} onValueUpdate (<code>callback</code>): onValueUpdate gets triggered when the input value is updated with a new date string.
|
||||
@param {callback} onDayCreate (<code>callback</code>): Take full control of every date cell with theonDayCreate()hook.
|
||||
**/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var umbFlatpickr = {
|
||||
template: '<ng-transclude>' +
|
||||
'<input type="text" ng-if="!$ctrl.options.inline" ng-model="$ctrl.ngModel" placeholder="Select Date.."></input>' +
|
||||
'<div ng-if="$ctrl.options.inline"></div>' +
|
||||
'</ng-transclude>',
|
||||
controller: umbFlatpickrCtrl,
|
||||
transclude: true,
|
||||
bindings: {
|
||||
ngModel: '<',
|
||||
options: '<',
|
||||
onSetup: '&?',
|
||||
onChange: '&?',
|
||||
onOpen: '&?',
|
||||
onClose: '&?',
|
||||
onMonthChange: '&?',
|
||||
onYearChange: '&?',
|
||||
onReady: '&?',
|
||||
onValueUpdate: '&?',
|
||||
onDayCreate: '&?'
|
||||
}
|
||||
};
|
||||
|
||||
function umbFlatpickrCtrl($element, $timeout, $scope, assetsService) {
|
||||
var ctrl = this;
|
||||
var loaded = false;
|
||||
|
||||
ctrl.$onInit = function() {
|
||||
|
||||
// load css file for the date picker
|
||||
assetsService.loadCss('lib/flatpickr/flatpickr.css', $scope);
|
||||
|
||||
// load the js file for the date picker
|
||||
assetsService.loadJs('lib/flatpickr/flatpickr.js', $scope).then(function () {
|
||||
// init date picker
|
||||
loaded = true;
|
||||
grabElementAndRunFlatpickr();
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
function grabElementAndRunFlatpickr() {
|
||||
$timeout(function() {
|
||||
var transcludeEl = $element.find('ng-transclude')[0];
|
||||
var element = transcludeEl.children[0];
|
||||
|
||||
setDatepicker(element);
|
||||
}, 0, true);
|
||||
}
|
||||
|
||||
function setDatepicker(element) {
|
||||
var fpLib = flatpickr ? flatpickr : FlatpickrInstance;
|
||||
|
||||
if (!fpLib) {
|
||||
return console.warn('Unable to find any flatpickr installation');
|
||||
}
|
||||
|
||||
setUpCallbacks();
|
||||
|
||||
var fpInstance = new fpLib(element, ctrl.options);
|
||||
|
||||
if (ctrl.onSetup) {
|
||||
ctrl.onSetup({
|
||||
fpItem: fpInstance
|
||||
});
|
||||
}
|
||||
|
||||
// If has ngModel set the date
|
||||
if (ctrl.ngModel) {
|
||||
fpInstance.setDate(ctrl.ngModel);
|
||||
}
|
||||
|
||||
// destroy the flatpickr instance when the dom element is removed
|
||||
angular.element(element).on('$destroy', function() {
|
||||
fpInstance.destroy();
|
||||
});
|
||||
|
||||
// Refresh the scope
|
||||
$scope.$applyAsync();
|
||||
}
|
||||
|
||||
function setUpCallbacks() {
|
||||
// bind hook for onChange
|
||||
if(ctrl.options && ctrl.onChange) {
|
||||
ctrl.options.onChange = function(selectedDates, dateStr, instance) {
|
||||
$timeout(function() {
|
||||
ctrl.onChange({selectedDates: selectedDates, dateStr: dateStr, instance: instance});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// bind hook for onOpen
|
||||
if(ctrl.options && ctrl.onOpen) {
|
||||
ctrl.options.onOpen = function(selectedDates, dateStr, instance) {
|
||||
$timeout(function() {
|
||||
ctrl.onOpen({selectedDates: selectedDates, dateStr: dateStr, instance: instance});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// bind hook for onOpen
|
||||
if(ctrl.options && ctrl.onClose) {
|
||||
ctrl.options.onClose = function(selectedDates, dateStr, instance) {
|
||||
$timeout(function() {
|
||||
ctrl.onClose({selectedDates: selectedDates, dateStr: dateStr, instance: instance});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// bind hook for onMonthChange
|
||||
if(ctrl.options && ctrl.onMonthChange) {
|
||||
ctrl.options.onMonthChange = function(selectedDates, dateStr, instance) {
|
||||
$timeout(function() {
|
||||
ctrl.onMonthChange({selectedDates: selectedDates, dateStr: dateStr, instance: instance});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// bind hook for onYearChange
|
||||
if(ctrl.options && ctrl.onYearChange) {
|
||||
ctrl.options.onYearChange = function(selectedDates, dateStr, instance) {
|
||||
$timeout(function() {
|
||||
ctrl.onYearChange({selectedDates: selectedDates, dateStr: dateStr, instance: instance});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// bind hook for onReady
|
||||
if(ctrl.options && ctrl.onReady) {
|
||||
ctrl.options.onReady = function(selectedDates, dateStr, instance) {
|
||||
$timeout(function() {
|
||||
ctrl.onReady({selectedDates: selectedDates, dateStr: dateStr, instance: instance});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// bind hook for onValueUpdate
|
||||
if(ctrl.onValueUpdate) {
|
||||
ctrl.options.onValueUpdate = function(selectedDates, dateStr, instance) {
|
||||
$timeout(function() {
|
||||
ctrl.onValueUpdate({selectedDates: selectedDates, dateStr: dateStr, instance: instance});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// bind hook for onDayCreate
|
||||
if(ctrl.onDayCreate) {
|
||||
ctrl.options.onDayCreate = function(selectedDates, dateStr, instance) {
|
||||
$timeout(function() {
|
||||
ctrl.onDayCreate({selectedDates: selectedDates, dateStr: dateStr, instance: instance});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
angular.module('umbraco.directives').component('umbFlatpickr', umbFlatpickr);
|
||||
|
||||
})();
|
||||
@@ -104,52 +104,52 @@ function packageResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
* @returns {Int} the ID assigned to the saved package manifest
|
||||
*
|
||||
*/
|
||||
import: function (package) {
|
||||
import: function (umbPackage) {
|
||||
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.post(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"packageInstallApiBaseUrl",
|
||||
"Import"), package),
|
||||
"Import"), umbPackage),
|
||||
'Failed to install package. Error during the step "Import" ');
|
||||
},
|
||||
|
||||
installFiles: function (package) {
|
||||
installFiles: function (umbPackage) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.post(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"packageInstallApiBaseUrl",
|
||||
"InstallFiles"), package),
|
||||
"InstallFiles"), umbPackage),
|
||||
'Failed to install package. Error during the step "InstallFiles" ');
|
||||
},
|
||||
|
||||
checkRestart: function (package) {
|
||||
checkRestart: function (umbPackage) {
|
||||
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.post(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"packageInstallApiBaseUrl",
|
||||
"CheckRestart"), package),
|
||||
"CheckRestart"), umbPackage),
|
||||
'Failed to install package. Error during the step "CheckRestart" ');
|
||||
},
|
||||
|
||||
installData: function (package) {
|
||||
installData: function (umbPackage) {
|
||||
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.post(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"packageInstallApiBaseUrl",
|
||||
"InstallData"), package),
|
||||
"InstallData"), umbPackage),
|
||||
'Failed to install package. Error during the step "InstallData" ');
|
||||
},
|
||||
|
||||
cleanUp: function (package) {
|
||||
cleanUp: function (umbPackage) {
|
||||
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.post(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"packageInstallApiBaseUrl",
|
||||
"CleanUp"), package),
|
||||
"CleanUp"), umbPackage),
|
||||
'Failed to install package. Error during the step "CleanUp" ');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -146,7 +146,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
|
||||
if (!args.methods) {
|
||||
throw "args.methods is not defined";
|
||||
}
|
||||
if (!args.methods.saveAndPublish || !args.methods.sendToPublish || !args.methods.unpublish) {
|
||||
if (!args.methods.saveAndPublish || !args.methods.sendToPublish || !args.methods.unpublish || !args.methods.schedulePublish) {
|
||||
throw "args.methods does not contain all required defined methods";
|
||||
}
|
||||
|
||||
@@ -190,6 +190,16 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
|
||||
alias: "unpublish",
|
||||
addEllipsis: args.content.variants && args.content.variants.length > 1 ? "true" : "false"
|
||||
};
|
||||
case "SCHEDULE":
|
||||
//schedule publish - schedule doesn't have a permission letter so
|
||||
// the button letter is made unique so it doesn't collide with anything else
|
||||
return {
|
||||
letter: ch,
|
||||
labelKey: "buttons_schedulePublish",
|
||||
handler: args.methods.schedulePublish,
|
||||
alias: "schedulePublish",
|
||||
addEllipsis: "true"
|
||||
};
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -200,7 +210,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
|
||||
|
||||
//This is the ideal button order but depends on circumstance, we'll use this array to create the button list
|
||||
// Publish, SendToPublish
|
||||
var buttonOrder = ["U", "H"];
|
||||
var buttonOrder = ["U", "H", "SCHEDULE"];
|
||||
|
||||
//Create the first button (primary button)
|
||||
//We cannot have the Save or SaveAndPublish buttons if they don't have create permissions when we are creating a new item.
|
||||
@@ -215,6 +225,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Here's the special check, if the button still isn't set and we are creating and they have create access
|
||||
//we need to add the Save button
|
||||
if (!buttons.defaultButton && args.create && _.contains(args.content.allowedActions, "C")) {
|
||||
@@ -237,6 +248,12 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
|
||||
}
|
||||
}
|
||||
|
||||
// if publishing is allowed also allow schedule publish
|
||||
// we add this manually becuase it doesn't have a permission so it wont
|
||||
// get picked up by the loop through permissions
|
||||
if( _.contains(args.content.allowedActions, "U")) {
|
||||
buttons.subButtons.push(createButtonDefinition("SCHEDULE"));
|
||||
}
|
||||
|
||||
// if we are not creating, then we should add unpublish too,
|
||||
// so long as it's already published and if the user has access to publish
|
||||
@@ -249,40 +266,6 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a scheduled publish or unpublish date change the default button to
|
||||
// "save" and update the label to "save and schedule
|
||||
if (args.content.releaseDate || args.content.removeDate) {
|
||||
|
||||
// if save button is alread the default don't change it just update the label
|
||||
if (buttons.defaultButton && buttons.defaultButton.letter === "A") {
|
||||
buttons.defaultButton.labelKey = "buttons_saveAndSchedule";
|
||||
return buttons;
|
||||
}
|
||||
|
||||
if (buttons.defaultButton && buttons.subButtons && buttons.subButtons.length > 0) {
|
||||
// save a copy of the default so we can push it to the sub buttons later
|
||||
var defaultButtonCopy = angular.copy(buttons.defaultButton);
|
||||
var newSubButtons = [];
|
||||
|
||||
// if save button is not the default button - find it and make it the default
|
||||
angular.forEach(buttons.subButtons, function (subButton) {
|
||||
|
||||
if (subButton.letter === "A") {
|
||||
buttons.defaultButton = subButton;
|
||||
buttons.defaultButton.labelKey = "buttons_saveAndSchedule";
|
||||
} else {
|
||||
newSubButtons.push(subButton);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// push old default button into subbuttons
|
||||
newSubButtons.push(defaultButtonCopy);
|
||||
buttons.subButtons = newSubButtons;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return buttons;
|
||||
},
|
||||
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
@import "components/tree/umb-actions.less";
|
||||
@import "components/tree/umb-tree-item.less";
|
||||
|
||||
|
||||
@import "components/editor.less";
|
||||
@import "components/overlays.less";
|
||||
@import "components/card.less";
|
||||
@@ -104,6 +105,7 @@
|
||||
@import "components/umb-editor-navigation.less";
|
||||
@import "components/umb-editor-sub-views.less";
|
||||
@import "components/editor/subheader/umb-editor-sub-header.less";
|
||||
@import "components/umb-flatpickr.less";
|
||||
@import "components/umb-grid-selector.less";
|
||||
@import "components/umb-child-selector.less";
|
||||
@import "components/umb-group-builder.less";
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
/* ---------- OVERLAY CENTER ---------- */
|
||||
.umb-overlay.umb-overlay-center {
|
||||
position: absolute;
|
||||
width: 500px;
|
||||
width: 600px;
|
||||
height: auto;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
.flatpickr-calendar.flatpickr-calendar {
|
||||
border-radius: @baseBorderRadius;
|
||||
box-shadow: 0 5px 10px 0 rgba(0,0,0,0.16);
|
||||
}
|
||||
|
||||
span.flatpickr-day {
|
||||
border-radius: @baseBorderRadius;
|
||||
border: none;
|
||||
}
|
||||
|
||||
span.flatpickr-day:hover {
|
||||
background-color: @gray-10;
|
||||
}
|
||||
|
||||
span.flatpickr-day.selected {
|
||||
background-color: @turquoise;
|
||||
}
|
||||
|
||||
span.flatpickr-day.selected:hover {
|
||||
background-color: @turquoise-d1;
|
||||
}
|
||||
@@ -48,6 +48,7 @@ h5.-black {
|
||||
border: none
|
||||
}
|
||||
.bootstrap-datetimepicker-widget {
|
||||
width: auto !important;
|
||||
td {
|
||||
&.active, span.active {
|
||||
background: @turquoise !important;
|
||||
|
||||
@@ -1,61 +1,19 @@
|
||||
//----- SCHEDULED PUBLISH ------
|
||||
|
||||
.place-holder {
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
margin: 15px auto;
|
||||
background-color: @gray-8;
|
||||
}
|
||||
|
||||
.date-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
flex-direction: row;
|
||||
border-top: 1px solid @gray-10;
|
||||
border-bottom: 1px solid @gray-10;
|
||||
}
|
||||
|
||||
.date-container {
|
||||
text-align: center;
|
||||
.date-wrapper__date {
|
||||
padding: 10px;
|
||||
flex: 1 1 50%;
|
||||
}
|
||||
|
||||
.date-wrapper__number{
|
||||
font-size: 40px;
|
||||
line-height: 50px;
|
||||
color: @gray-2;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.date-container__title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: @gray-3;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.date-container__date {
|
||||
padding: 0 10px;
|
||||
}
|
||||
.date-container__date:hover {
|
||||
background-color: @gray-10;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.date-wrapper__date{
|
||||
font-size: 13px;
|
||||
color: @gray-6;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.data-wrapper__add{
|
||||
font-size: 18px;
|
||||
line-height: 10px;
|
||||
color: @gray-8;
|
||||
font-weight: 900;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.date-separate {
|
||||
width: 1px;
|
||||
background-color: @gray-8;
|
||||
.date-wrapper__date:last-of-type {
|
||||
border-left: 1px solid @gray-10;
|
||||
}
|
||||
|
||||
//------------------- HISTORY ------------------
|
||||
|
||||
@@ -112,78 +112,7 @@
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__sidebar">
|
||||
<umb-box data-element="node-info-scheduled-publishing">
|
||||
<umb-box-header title-key="general_scheduledPublishing"></umb-box-header>
|
||||
<umb-box-content class="block-form">
|
||||
|
||||
<div class="date-wrapper">
|
||||
|
||||
<div class="flex items-center flex-column">
|
||||
|
||||
|
||||
<umb-date-time-picker data-element="node-info-publish"
|
||||
options="datePickerConfig"
|
||||
on-change="datePickerChange(event, 'publish')">
|
||||
|
||||
<div class="date-container">
|
||||
|
||||
<div class="date-container__title">
|
||||
<localize key="content_releaseDate"></localize>
|
||||
</div>
|
||||
|
||||
<div class="date-container__date" ng-if="node.releaseDate">
|
||||
<div class="date-wrapper__date">{{node.releaseDateMonth}} {{node.releaseDateYear}}</div>
|
||||
<div class="date-wrapper__number">{{node.releaseDateDayNumber}}</div>
|
||||
<div class="date-wrapper__date">{{node.releaseDateDay}} {{node.releaseDateTime}}</div>
|
||||
</div>
|
||||
|
||||
<a href="" ng-if="!node.releaseDate" class="bold" style="color: #00aea2; text-decoration: underline;"><localize key="content_setDate">Set date</localize></a>
|
||||
|
||||
</div>
|
||||
|
||||
</umb-date-time-picker>
|
||||
|
||||
<a ng-if="node.releaseDate" ng-click="clearPublishDate()" href="" style="text-decoration: underline;">
|
||||
<small><localize key="content_removeDate">Clear date</localize></small>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="date-separate"></div>
|
||||
|
||||
<div class="flex items-center flex-column">
|
||||
<umb-date-time-picker data-element="node-info-unpublish"
|
||||
options="datePickerConfig"
|
||||
on-change="datePickerChange(event, 'unpublish')">
|
||||
|
||||
<div class="date-container">
|
||||
|
||||
<div class="date-container__title">
|
||||
<localize key="content_unpublishDate"></localize>
|
||||
</div>
|
||||
|
||||
<div class="date-container__date" ng-if="node.removeDate">
|
||||
<div class="date-wrapper__date">{{node.removeDateMonth}} {{node.removeDateYear}}</div>
|
||||
<div class="date-wrapper__number">{{node.removeDateDayNumber}}</div>
|
||||
<div class="date-wrapper__date">{{node.removeDateDay}} {{node.removeDateTime}}</div>
|
||||
</div>
|
||||
|
||||
<a href="" ng-if="!node.removeDate" class="bold" style="color: #00aea2; text-decoration: underline;"><localize key="content_setDate">Set date</localize></a>
|
||||
|
||||
</div>
|
||||
|
||||
</umb-date-time-picker>
|
||||
|
||||
<a ng-if="node.removeDate" ng-click="clearUnpublishDate()" href="" style="text-decoration: underline;">
|
||||
<small><localize key="content_removeDate">Clear date</localize></small>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
|
||||
<umb-box data-element="node-info-general">
|
||||
<umb-box-header title-key="general_general"></umb-box-header>
|
||||
<umb-box-content class="block-form">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div data-element="overlay" class="umb-overlay umb-overlay-{{position}}" on-outside-click="closeOverLay()">
|
||||
<div data-element="overlay" class="umb-overlay umb-overlay-{{position}}" on-outside-click="outSideClick()">
|
||||
<ng-form class="umb-overlay__form" name="overlayForm" novalidate val-form-manager>
|
||||
|
||||
<div data-element="overlay-header" class="umb-overlay-header">
|
||||
|
||||
@@ -0,0 +1,335 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function ScheduleContentController($scope, $timeout, localizationService, dateHelper, userService) {
|
||||
|
||||
var vm = this;
|
||||
|
||||
vm.datePickerSetup = datePickerSetup;
|
||||
vm.datePickerChange = datePickerChange;
|
||||
vm.datePickerShow = datePickerShow;
|
||||
vm.datePickerClose = datePickerClose;
|
||||
vm.clearPublishDate = clearPublishDate;
|
||||
vm.clearUnpublishDate = clearUnpublishDate;
|
||||
vm.dirtyVariantFilter = dirtyVariantFilter;
|
||||
vm.pristineVariantFilter = pristineVariantFilter;
|
||||
vm.changeSelection = changeSelection;
|
||||
|
||||
vm.firstSelectedDates = {};
|
||||
vm.currentUser = null;
|
||||
|
||||
function onInit() {
|
||||
|
||||
vm.variants = $scope.model.variants;
|
||||
vm.hasPristineVariants = false;
|
||||
|
||||
if(!$scope.model.title) {
|
||||
localizationService.localize("general_scheduledPublishing").then(function(value){
|
||||
$scope.model.title = value;
|
||||
});
|
||||
}
|
||||
|
||||
// Check for variants: if a node is invariant it will still have the default language in variants
|
||||
// so we have to check for length > 1
|
||||
if (vm.variants.length > 1) {
|
||||
|
||||
_.each(vm.variants,
|
||||
function (variant) {
|
||||
variant.compositeId = variant.language.culture + "_" + (variant.segment ? variant.segment : "");
|
||||
variant.htmlId = "_content_variant_" + variant.compositeId;
|
||||
|
||||
//check for pristine variants
|
||||
if (!vm.hasPristineVariants) {
|
||||
vm.hasPristineVariants = pristineVariantFilter(variant);
|
||||
}
|
||||
});
|
||||
|
||||
//now sort it so that the current one is at the top
|
||||
vm.variants = _.sortBy(vm.variants, function (v) {
|
||||
return v.active ? 0 : 1;
|
||||
});
|
||||
|
||||
var active = _.find(vm.variants, function (v) {
|
||||
return v.active;
|
||||
});
|
||||
|
||||
if (active) {
|
||||
//ensure that the current one is selected
|
||||
active.schedule = true;
|
||||
active.save = true;
|
||||
}
|
||||
|
||||
$scope.model.disableSubmitButton = !canSchedule();
|
||||
|
||||
}
|
||||
|
||||
// get current backoffice user and format dates
|
||||
userService.getCurrentUser().then(function (currentUser) {
|
||||
|
||||
vm.currentUser = currentUser;
|
||||
|
||||
angular.forEach(vm.variants, function(variant) {
|
||||
|
||||
// prevent selecting publish/unpublish date before today
|
||||
var now = new Date();
|
||||
var nowFormatted = moment(now).format("YYYY-MM-DD HH:mm");
|
||||
|
||||
var datePickerConfig = {
|
||||
enableTime: true,
|
||||
dateFormat: "Y-m-d H:i",
|
||||
time_24hr: true,
|
||||
minDate: nowFormatted,
|
||||
defaultDate: nowFormatted
|
||||
};
|
||||
|
||||
variant.datePickerConfig = datePickerConfig;
|
||||
|
||||
// format all dates to local
|
||||
if(variant.releaseDate || variant.removeDate) {
|
||||
formatDatesToLocal(variant);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback when date is set up
|
||||
* @param {any} variant
|
||||
* @param {any} type publish or unpublish
|
||||
* @param {any} datePickerInstance The date picker instance
|
||||
*/
|
||||
function datePickerSetup(variant, type, datePickerInstance) {
|
||||
// store a date picker instance for publish and unpublish picker
|
||||
// so we can change the settings independently.
|
||||
if (type === 'publish') {
|
||||
variant.releaseDatePickerInstance = datePickerInstance;
|
||||
} else if (type === 'unpublish') {
|
||||
variant.removeDatePickerInstance = datePickerInstance;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback when date picker date changes
|
||||
* @param {any} variant
|
||||
* @param {any} dateStr Date string from the date picker
|
||||
* @param {any} type publish or unpublish
|
||||
*/
|
||||
function datePickerChange(variant, dateStr, type) {
|
||||
if (type === 'publish') {
|
||||
setPublishDate(variant, dateStr);
|
||||
} else if (type === 'unpublish') {
|
||||
setUnpublishDate(variant, dateStr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add flag when a date picker opens is we can prevent the overlay from closing
|
||||
* @param {any} variant
|
||||
* @param {any} type publish or unpublish
|
||||
*/
|
||||
function datePickerShow(variant, type) {
|
||||
if (type === 'publish') {
|
||||
variant.releaseDatePickerOpen = true;
|
||||
} else if (type === 'unpublish') {
|
||||
variant.removeDatePickerOpen = true;
|
||||
}
|
||||
checkForBackdropClick();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove flag when a date picker closes so the overlay can be closed again
|
||||
* @param {any} variant
|
||||
* @param {any} type publish or unpublish
|
||||
*/
|
||||
function datePickerClose(variant, type) {
|
||||
$timeout(function(){
|
||||
if (type === 'publish') {
|
||||
variant.releaseDatePickerOpen = false;
|
||||
} else if (type === 'unpublish') {
|
||||
variant.removeDatePickerOpen = false;
|
||||
}
|
||||
checkForBackdropClick();
|
||||
}, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent the overlay from closing if any date pickers are open
|
||||
*/
|
||||
function checkForBackdropClick() {
|
||||
|
||||
var open = _.find(vm.variants, function (variant) {
|
||||
return variant.releaseDatePickerOpen || variant.removeDatePickerOpen;
|
||||
});
|
||||
|
||||
if(open) {
|
||||
$scope.model.disableBackdropClick = true;
|
||||
} else {
|
||||
$scope.model.disableBackdropClick = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selected publish date
|
||||
* @param {any} variant
|
||||
* @param {any} date The selected date
|
||||
*/
|
||||
function setPublishDate(variant, date) {
|
||||
|
||||
if (!date) {
|
||||
return;
|
||||
}
|
||||
|
||||
//The date being passed in here is the user's local date/time that they have selected
|
||||
//we need to convert this date back to the server date on the model.
|
||||
var serverTime = dateHelper.convertToServerStringTime(moment(date), Umbraco.Sys.ServerVariables.application.serverTimeOffset);
|
||||
|
||||
// update publish value
|
||||
variant.releaseDate = serverTime;
|
||||
|
||||
// make sure dates are formatted to the user's locale
|
||||
formatDatesToLocal(variant);
|
||||
|
||||
// make sure the unpublish date can't be before the publish date
|
||||
variant.removeDatePickerInstance.set("minDate", moment(variant.releaseDate).format("YYYY-MM-DD HH:mm"));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selected unpublish date
|
||||
* @param {any} variant
|
||||
* @param {any} date The selected date
|
||||
*/
|
||||
function setUnpublishDate(variant, date) {
|
||||
|
||||
if (!date) {
|
||||
return;
|
||||
}
|
||||
|
||||
//The date being passed in here is the user's local date/time that they have selected
|
||||
//we need to convert this date back to the server date on the model.
|
||||
var serverTime = dateHelper.convertToServerStringTime(moment(date), Umbraco.Sys.ServerVariables.application.serverTimeOffset);
|
||||
|
||||
// update publish value
|
||||
variant.removeDate = serverTime;
|
||||
|
||||
// make sure dates are formatted to the user's locale
|
||||
formatDatesToLocal(variant);
|
||||
|
||||
// make sure the publish date can't be after the publish date
|
||||
variant.releaseDatePickerInstance.set("maxDate", moment(variant.removeDate).format("YYYY-MM-DD HH:mm"));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the publish date
|
||||
* @param {any} variant
|
||||
*/
|
||||
function clearPublishDate(variant) {
|
||||
if(variant && variant.releaseDate) {
|
||||
variant.releaseDate = null;
|
||||
// we don't have a publish date anymore so we can clear the min date for unpublish
|
||||
var now = new Date();
|
||||
var nowFormatted = moment(now).format("YYYY-MM-DD HH:mm");
|
||||
variant.removeDatePickerInstance.set("minDate", nowFormatted);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the unpublish date
|
||||
* @param {any} variant
|
||||
*/
|
||||
function clearUnpublishDate(variant) {
|
||||
if(variant && variant.removeDate) {
|
||||
variant.removeDate = null;
|
||||
// we don't have a unpublish date anymore so we can clear the max date for publish
|
||||
variant.releaseDatePickerInstance.set("maxDate", null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formates the selected dates to fit the user culture
|
||||
* @param {any} variant
|
||||
*/
|
||||
function formatDatesToLocal(variant) {
|
||||
if(variant && variant.releaseDate) {
|
||||
variant.releaseDateFormatted = dateHelper.getLocalDate(variant.releaseDate, vm.currentUser.locale, "MMM Do YYYY, HH:mm");
|
||||
}
|
||||
if(variant && variant.removeDate) {
|
||||
variant.removeDateFormatted = dateHelper.getLocalDate(variant.removeDate, vm.currentUser.locale, "MMM Do YYYY, HH:mm");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when new variants are selected or deselected
|
||||
* @param {any} variant
|
||||
*/
|
||||
function changeSelection(variant) {
|
||||
$scope.model.disableSubmitButton = !canSchedule();
|
||||
//need to set the Save state to true if publish is true
|
||||
variant.save = variant.schedule;
|
||||
}
|
||||
|
||||
function dirtyVariantFilter(variant) {
|
||||
//determine a variant is 'dirty' (meaning it will show up as publish-able) if it's
|
||||
// * the active one
|
||||
// * it's editor is in a $dirty state
|
||||
// * it has pending saves
|
||||
// * it is unpublished
|
||||
// * it is in NotCreated state
|
||||
return (variant.active || variant.isDirty || variant.state === "Draft" || variant.state === "PublishedPendingChanges" || variant.state === "NotCreated");
|
||||
}
|
||||
|
||||
function pristineVariantFilter(variant) {
|
||||
return !(dirtyVariantFilter(variant));
|
||||
}
|
||||
|
||||
/** Returns true if publishing is possible based on if there are un-published mandatory languages */
|
||||
function canSchedule() {
|
||||
var selected = [];
|
||||
for (var i = 0; i < vm.variants.length; i++) {
|
||||
var variant = vm.variants[i];
|
||||
|
||||
//if this variant will show up in the publish-able list
|
||||
var publishable = dirtyVariantFilter(variant);
|
||||
|
||||
if ((variant.language.isMandatory && (variant.state === "NotCreated" || variant.state === "Draft"))
|
||||
&& (!publishable || !variant.schedule)) {
|
||||
//if a mandatory variant isn't published and it's not publishable or not selected to be published
|
||||
//then we cannot publish anything
|
||||
|
||||
//TODO: Show a message when this occurs
|
||||
return false;
|
||||
}
|
||||
|
||||
if (variant.schedule) {
|
||||
selected.push(variant.schedule);
|
||||
}
|
||||
}
|
||||
return selected.length > 0;
|
||||
}
|
||||
|
||||
onInit();
|
||||
|
||||
//when this dialog is closed, clean up
|
||||
$scope.$on('$destroy', function () {
|
||||
for (var i = 0; i < vm.variants.length; i++) {
|
||||
vm.variants[i].save = false;
|
||||
vm.variants[i].schedule = false;
|
||||
// remove properties only needed for this dialog
|
||||
delete vm.variants[i].releaseDateFormatted;
|
||||
delete vm.variants[i].removeDateFormatted;
|
||||
delete vm.variants[i].datePickerConfig;
|
||||
delete vm.variants[i].releaseDatePickerInstance;
|
||||
delete vm.variants[i].removeDatePickerInstance;
|
||||
delete vm.variants[i].releaseDatePickerOpen;
|
||||
delete vm.variants[i].removeDatePickerOpen;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
angular.module("umbraco").controller("Umbraco.Overlays.ScheduleContentController", ScheduleContentController);
|
||||
|
||||
})();
|
||||
@@ -0,0 +1,191 @@
|
||||
<div ng-controller="Umbraco.Overlays.ScheduleContentController as vm">
|
||||
|
||||
<!-- invariant nodes -->
|
||||
<div ng-if="vm.variants.length === 1">
|
||||
|
||||
<div style="margin-bottom: 15px;">
|
||||
<p><localize key="content_schedulePublishHelp"></localize></p>
|
||||
</div>
|
||||
|
||||
<div class="date-wrapper">
|
||||
<div class="date-wrapper__date">
|
||||
<label class="bold">
|
||||
<localize key="content_releaseDate"></localize>
|
||||
</label>
|
||||
|
||||
<div class="btn-group flex" style="font-size: 15px;">
|
||||
|
||||
<umb-flatpickr
|
||||
ng-model="vm.variants[0].releaseDate"
|
||||
options="vm.variants[0].datePickerConfig"
|
||||
on-setup="vm.datePickerSetup(vm.variants[0], 'publish', fpItem)"
|
||||
on-change="vm.datePickerChange(vm.variants[0], dateStr, 'publish')"
|
||||
on-open="vm.datePickerShow(vm.variants[0], 'publish')"
|
||||
on-close="vm.datePickerClose(vm.variants[0], 'publish')">
|
||||
|
||||
<div>
|
||||
<button ng-if="vm.variants[0].releaseDate" class="btn umb-button--xs" style="outline: none;">
|
||||
{{vm.variants[0].releaseDateFormatted}}
|
||||
</button>
|
||||
|
||||
<a ng-hide="vm.variants[0].releaseDate" href="" class="bold" style="color: #00aea2; text-decoration: underline;">
|
||||
<localize key="content_setDate">Set date</localize>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</umb-flatpickr>
|
||||
|
||||
<a ng-if="vm.variants[0].releaseDate" ng-click="vm.clearPublishDate(vm.variants[0])" class="btn umb-button--xs dropdown-toggle umb-button-group__toggle" style="margin-left: -2px;">
|
||||
<span class="icon icon-wrong"></span>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="date-wrapper__date">
|
||||
<label class="bold">
|
||||
<localize key="content_unpublishDate"></localize>
|
||||
</label>
|
||||
|
||||
<div class="btn-group flex" style="font-size: 15px;">
|
||||
<umb-flatpickr
|
||||
ng-model="vm.variants[0].removeDate"
|
||||
options="vm.variants[0].datePickerConfig"
|
||||
on-setup="vm.datePickerSetup(vm.variants[0], 'unpublish', fpItem)"
|
||||
on-change="vm.datePickerChange(vm.variants[0], dateStr, 'unpublish')"
|
||||
on-open="vm.datePickerShow(vm.variants[0], 'unpublish')"
|
||||
on-close="vm.datePickerClose(vm.variants[0], 'unpublish')">
|
||||
|
||||
<div>
|
||||
<button ng-if="vm.variants[0].removeDate" class="btn umb-button--xs" style="outline: none;">
|
||||
{{vm.variants[0].removeDateFormatted}}
|
||||
</button>
|
||||
|
||||
<a ng-hide="vm.variants[0].removeDate" href="" class="bold" style="color: #00aea2; text-decoration: underline;">
|
||||
<localize key="content_setDate">Set date</localize>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</umb-flatpickr>
|
||||
|
||||
<a ng-if="vm.variants[0].removeDate" ng-click="vm.clearUnpublishDate(vm.variants[0])" class="btn umb-button--xs dropdown-toggle umb-button-group__toggle" style="margin-left: -2px;">
|
||||
<span class="icon icon-wrong"></span>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- nodes with variants -->
|
||||
<div ng-if="vm.variants.length > 1">
|
||||
|
||||
<div style="margin-bottom: 15px;">
|
||||
<p><localize key="content_languagesToSchedule"></localize></p>
|
||||
</div>
|
||||
|
||||
<div class="umb-list umb-list--condensed">
|
||||
|
||||
<div class="umb-list-item" ng-repeat="variant in vm.variants | filter:vm.dirtyVariantFilter">
|
||||
<ng-form name="scheduleSelectorForm">
|
||||
<div class="flex">
|
||||
<input
|
||||
id="{{variant.language.culture}}"
|
||||
name="saveVariantSelector"
|
||||
type="checkbox"
|
||||
ng-model="variant.schedule"
|
||||
ng-change="vm.changeSelection(variant)"
|
||||
style="margin-right: 8px;" />
|
||||
<div>
|
||||
|
||||
<label for="{{variant.language.culture}}" style="margin-bottom: 2px;">
|
||||
<span>{{ variant.language.name }}</span>
|
||||
</label>
|
||||
|
||||
<div class="umb-permission__description">
|
||||
<umb-variant-state variant="variant"></umb-variant-state>
|
||||
<span ng-if="variant.language.isMandatory"> - <localize key="languages_mandatoryLanguage"></localize></span>
|
||||
</div>
|
||||
|
||||
<div ng-if="variant.schedule" class="flex items-center" style="margin-top: 10px; margin-bottom: 10px;">
|
||||
<div style="font-size: 13px; margin-right: 5px;">Publish: </div>
|
||||
|
||||
<div class="btn-group flex" style="font-size: 14px; margin-right: 10px;">
|
||||
<umb-flatpickr
|
||||
ng-model="variant.releaseDate"
|
||||
options="variant.datePickerConfig"
|
||||
on-setup="vm.datePickerSetup(variant, 'publish', fpItem)"
|
||||
on-change="vm.datePickerChange(variant, dateStr, 'publish')"
|
||||
on-open="vm.datePickerShow(variant, 'publish')"
|
||||
on-close="vm.datePickerClose(variant, 'publish')">
|
||||
<div>
|
||||
<button ng-if="variant.releaseDate" class="btn umb-button--xxs" style="outline: none;">
|
||||
{{variant.releaseDateFormatted}}
|
||||
</button>
|
||||
|
||||
<a ng-hide="variant.releaseDate" href="" class="bold" style="color: #00aea2; text-decoration: underline;">
|
||||
<localize key="content_setDate">Set date</localize>
|
||||
</a>
|
||||
</div>
|
||||
</umb-flatpickr>
|
||||
<a ng-if="variant.releaseDate" ng-click="vm.clearPublishDate(variant)" class="btn umb-button--xxs dropdown-toggle umb-button-group__toggle" style="margin-left: -2px;">
|
||||
<span class="icon icon-wrong"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div style="font-size: 13px; margin-right: 5px;">Unpublish:</div>
|
||||
|
||||
<div class="btn-group flex" style="font-size: 14px;">
|
||||
<umb-flatpickr
|
||||
ng-model="variant.removeDate"
|
||||
options="variant.datePickerConfig"
|
||||
on-setup="vm.datePickerSetup(variant, 'unpublish', fpItem)"
|
||||
on-change="vm.datePickerChange(variant, dateStr, 'unpublish')"
|
||||
on-open="vm.datePickerShow(variant, 'unpublish')"
|
||||
on-close="vm.datePickerClose(variant, 'unpublish')">
|
||||
<div>
|
||||
<button ng-if="variant.removeDate" class="btn umb-button--xxs" style="outline: none;">
|
||||
{{variant.removeDateFormatted}}
|
||||
</button>
|
||||
|
||||
<a ng-hide="variant.removeDate" href="" class="bold" style="color: #00aea2; text-decoration: underline;">
|
||||
<localize key="content_setDate">Set date</localize>
|
||||
</a>
|
||||
</div>
|
||||
</umb-flatpickr>
|
||||
<a ng-if="variant.removeDate" ng-click="vm.clearUnpublishDate(variant)" class="btn umb-button--xxs dropdown-toggle umb-button-group__toggle" style="margin-left: -2px;">
|
||||
<span class="icon icon-wrong"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</ng-form>
|
||||
</div>
|
||||
<br/>
|
||||
</div>
|
||||
|
||||
<div class="umb-list umb-list--condensed" ng-if="vm.hasPristineVariants">
|
||||
<div style="margin-bottom: 15px; font-weight: bold;">
|
||||
<p><localize key="content_publishedLanguages"></localize></p>
|
||||
</div>
|
||||
|
||||
<div class="umb-list-item" ng-repeat="variant in vm.variants | filter:vm.pristineVariantFilter track by variant.language.culture">
|
||||
<div>
|
||||
<div style="margin-bottom: 2px;">
|
||||
<span>{{ variant.language.name }}</span>
|
||||
</div>
|
||||
<div class="umb-permission__description">
|
||||
<umb-variant-state variant="variant"></umb-variant-state>
|
||||
<span ng-if="variant.language.isMandatory"> - <localize key="languages_mandatoryLanguage"></localize></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
+1
-1
@@ -28,7 +28,7 @@ angular.module("umbraco")
|
||||
if(toggle){
|
||||
collection = [];
|
||||
}else{
|
||||
delete collection;
|
||||
collection = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ function RowConfigController($scope) {
|
||||
collection = [];
|
||||
}
|
||||
else {
|
||||
delete collection;
|
||||
collection = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -189,7 +189,7 @@ angular.module("umbraco")
|
||||
if(toggle){
|
||||
collection = [];
|
||||
}else{
|
||||
delete collection;
|
||||
collection = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -84,20 +84,20 @@ angular.module("umbraco")
|
||||
var toolbar = editorConfig.toolbar.join(" | ");
|
||||
var stylesheets = [];
|
||||
var styleFormats = [];
|
||||
var await = [];
|
||||
var requests = [];
|
||||
if (!editorConfig.maxImageSize && editorConfig.maxImageSize != 0) {
|
||||
editorConfig.maxImageSize = tinyMceService.defaultPrevalues().maxImageSize;
|
||||
}
|
||||
|
||||
//queue file loading
|
||||
if (typeof tinymce === "undefined") { // Don't reload tinymce if already loaded
|
||||
await.push(assetsService.loadJs("lib/tinymce/tinymce.min.js", $scope));
|
||||
requests.push(assetsService.loadJs("lib/tinymce/tinymce.min.js", $scope));
|
||||
}
|
||||
|
||||
//queue rules loading
|
||||
angular.forEach(editorConfig.stylesheets, function (val, key) {
|
||||
stylesheets.push(Umbraco.Sys.ServerVariables.umbracoSettings.cssPath + "/" + val + ".css?" + new Date().getTime());
|
||||
await.push(stylesheetResource.getRulesByName(val).then(function (rules) {
|
||||
requests.push(stylesheetResource.getRulesByName(val).then(function (rules) {
|
||||
angular.forEach(rules, function (rule) {
|
||||
var r = {};
|
||||
r.title = rule.name;
|
||||
@@ -170,7 +170,7 @@ angular.module("umbraco")
|
||||
}
|
||||
|
||||
//wait for queue to end
|
||||
$q.all(await).then(function () {
|
||||
$q.all(requests).then(function () {
|
||||
|
||||
//create a baseline Config to exten upon
|
||||
var baseLineConfigObj = {
|
||||
|
||||
@@ -207,7 +207,7 @@ angular.module("umbraco")
|
||||
tagsHound.clearPrefetchCache();
|
||||
tagsHound.clearRemoteCache();
|
||||
$element.find('.tags-' + $scope.model.alias).typeahead('destroy');
|
||||
delete tagsHound;
|
||||
tagsHound = null;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -126,6 +126,7 @@
|
||||
<key alias="saveAndSchedule">Save and schedule</key>
|
||||
<key alias="saveToPublish">Send for approval</key>
|
||||
<key alias="saveListView">Save list view</key>
|
||||
<key alias="schedulePublish">Schedule</key>
|
||||
<key alias="showPage">Preview</key>
|
||||
<key alias="showPageDisabled">Preview is disabled because there's no template assigned</key>
|
||||
<key alias="styleChoose">Choose style</key>
|
||||
@@ -223,7 +224,48 @@
|
||||
<key alias="publish">Publish</key>
|
||||
<key alias="published">Published</key>
|
||||
<key alias="publishedPendingChanges">Published (pending changes)</key>>
|
||||
<key alias="publishStatus">Publication Status</key><key alias="releaseDate">Publish at</key><key alias="unpublishDate">Unpublish at</key><key alias="removeDate">Clear Date</key><key alias="setDate">Set date</key><key alias="sortDone">Sortorder is updated</key><key alias="sortHelp">To sort the nodes, simply drag the nodes or click one of the column headers. You can select multiple nodes by holding the "shift" or "control" key while selecting</key><key alias="statistics">Statistics</key><key alias="titleOptional">Title (optional)</key><key alias="altTextOptional">Alternative text (optional)</key><key alias="type">Type</key><key alias="unpublish">Unpublish</key><key alias="unpublished">Draft</key><key alias="notCreated">Not created</key><key alias="updateDate">Last edited</key><key alias="updateDateDesc" version="7.0">Date/time this document was edited</key><key alias="uploadClear">Remove file(s)</key><key alias="urls">Link to document</key><key alias="memberof">Member of group(s)</key><key alias="notmemberof">Not a member of group(s)</key><key alias="childItems" version="7.0">Child items</key><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.com/documentation/Getting-Started/Data/Scheduled-Publishing/#timezones" target="_blank">What does this mean?</a>]]></key><key alias="nestedContentDeleteItem">Are you sure you want to delete this item?</key><key alias="nestedContentEditorNotSupported">Property %0% uses editor %1% which is not supported by Nested Content.</key><key alias="addTextBox">Add another text box</key><key alias="removeTextBox">Remove this text box</key><key alias="contentRoot">Content root</key><key alias="isSensitiveValue">This value is hidden. If you need access to view this value please contact your website administrator.</key><key alias="isSensitiveValue_short">This value is hidden.</key><key alias="languagesToPublish">What languages would you like to publish?</key><key alias="languagesToSave">What languages would you like to save?</key><key alias="languagesToSendForApproval">What languages would you like to send for approval?</key><key alias="languagesToUnpublish">Select the languages to unpublish. Unpublishing a mandatory language will unpublish all languages.</key><key alias="publishedLanguages">Published Languages</key><key alias="unpublishedLanguages">Unpublished Languages</key><key alias="unmodifiedLanguages">Unmodified Languages</key><key alias="readyToPublish">Ready to Publish?</key><key alias="readyToSave">Ready to Save?</key><key alias="sendForApproval">Send for approval</key></area>
|
||||
<key alias="publishStatus">Publication Status</key>
|
||||
<key alias="releaseDate">Publish at</key>
|
||||
<key alias="unpublishDate">Unpublish at</key>
|
||||
<key alias="removeDate">Clear Date</key>
|
||||
<key alias="setDate">Set date</key>
|
||||
<key alias="sortDone">Sortorder is updated</key>
|
||||
<key alias="sortHelp">To sort the nodes, simply drag the nodes or click one of the column headers. You can select multiple nodes by holding the "shift" or "control" key while selecting</key>
|
||||
<key alias="statistics">Statistics</key>
|
||||
<key alias="titleOptional">Title (optional)</key>
|
||||
<key alias="altTextOptional">Alternative text (optional)</key>
|
||||
<key alias="type">Type</key>
|
||||
<key alias="unpublish">Unpublish</key>
|
||||
<key alias="unpublished">Draft</key>
|
||||
<key alias="notCreated">Not created</key>
|
||||
<key alias="updateDate">Last edited</key>
|
||||
<key alias="updateDateDesc" version="7.0">Date/time this document was edited</key>
|
||||
<key alias="uploadClear">Remove file(s)</key>
|
||||
<key alias="urls">Link to document</key>
|
||||
<key alias="memberof">Member of group(s)</key>
|
||||
<key alias="notmemberof">Not a member of group(s)</key>
|
||||
<key alias="childItems" version="7.0">Child items</key>
|
||||
<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.com/documentation/Getting-Started/Data/Scheduled-Publishing/#timezones" target="_blank">What does this mean?</a>]]></key><key alias="nestedContentDeleteItem">Are you sure you want to delete this item?</key><key alias="nestedContentEditorNotSupported">Property %0% uses editor %1% which is not supported by Nested Content.</key>
|
||||
<key alias="addTextBox">Add another text box</key>
|
||||
<key alias="removeTextBox">Remove this text box</key>
|
||||
<key alias="contentRoot">Content root</key>
|
||||
<key alias="isSensitiveValue">This value is hidden. If you need access to view this value please contact your website administrator.</key>
|
||||
<key alias="isSensitiveValue_short">This value is hidden.</key>
|
||||
<key alias="languagesToPublish">What languages would you like to publish?</key>
|
||||
<key alias="languagesToSave">What languages would you like to save?</key>
|
||||
<key alias="languagesToSendForApproval">What languages would you like to send for approval?</key>
|
||||
<key alias="languagesToSchedule">What languages would you like to schedule?</key>
|
||||
<key alias="languagesToUnpublish">Select the languages to unpublish. Unpublishing a mandatory language will unpublish all languages.</key>
|
||||
<key alias="publishedLanguages">Published Languages</key>
|
||||
<key alias="unpublishedLanguages">Unpublished Languages</key>
|
||||
<key alias="unmodifiedLanguages">Unmodified Languages</key>
|
||||
<key alias="readyToPublish">Ready to Publish?</key>
|
||||
<key alias="readyToSave">Ready to Save?</key>
|
||||
<key alias="sendForApproval">Send for approval</key>
|
||||
<key alias="schedulePublishHelp">Select the date and time to publish and/or unpublish the content item.</key>
|
||||
</area>
|
||||
<area alias="blueprints">
|
||||
<key alias="createBlueprintFrom">Create a new Content Template from '%0%'</key>
|
||||
<key alias="blankBlueprint">Blank</key>
|
||||
|
||||
@@ -161,7 +161,7 @@ namespace Umbraco.Web.Editors
|
||||
throw new ArgumentOutOfRangeException("The entity type was not a content type");
|
||||
}
|
||||
|
||||
var contentTypesWhereCompositionIsUsed = Services.ContentTypeService.GetWhereCompositionIsUsedInContentTypes(source, allContentTypes);
|
||||
var contentTypesWhereCompositionIsUsed = source.GetWhereCompositionIsUsedInContentTypes(allContentTypes);
|
||||
return contentTypesWhereCompositionIsUsed
|
||||
.Select(x => Mapper.Map<IContentTypeComposition, EntityBasic>(x))
|
||||
.Select(x =>
|
||||
|
||||
@@ -11,8 +11,9 @@ namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
CreateMap<Core.Models.Section, Section>()
|
||||
.ForMember(dest => dest.RoutePath, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Icon, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => textService.Localize("sections/" + src.Alias, (IDictionary<string, string>)null)))
|
||||
.ReverseMap(); //backwards too!
|
||||
.ReverseMap(); //backwards too!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,6 +198,9 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
|
||||
private void InitializeRepositoryEvents()
|
||||
{
|
||||
//fixme: The reason these events are in the repository is for legacy, the events should exist at the service
|
||||
// level now since we can fire these events within the transaction... so move the events to service level
|
||||
|
||||
// plug repository event handlers
|
||||
// these trigger within the transaction to ensure consistency
|
||||
// and are used to maintain the central, database-level XML cache
|
||||
@@ -212,9 +215,9 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
MemberRepository.ScopedEntityRefresh += OnMemberRefreshedEntity;
|
||||
|
||||
// plug
|
||||
ContentTypeService.UowRefreshedEntity += OnContentTypeRefreshedEntity;
|
||||
MediaTypeService.UowRefreshedEntity += OnMediaTypeRefreshedEntity;
|
||||
MemberTypeService.UowRefreshedEntity += OnMemberTypeRefreshedEntity;
|
||||
ContentTypeService.ScopedRefreshedEntity += OnContentTypeRefreshedEntity;
|
||||
MediaTypeService.ScopedRefreshedEntity += OnMediaTypeRefreshedEntity;
|
||||
MemberTypeService.ScopedRefreshedEntity += OnMemberTypeRefreshedEntity;
|
||||
}
|
||||
|
||||
private void TearDownRepositoryEvents()
|
||||
@@ -229,9 +232,9 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
//MemberRepository.RemovedVersion -= OnMemberRemovedVersion;
|
||||
MemberRepository.ScopedEntityRefresh -= OnMemberRefreshedEntity;
|
||||
|
||||
ContentTypeService.UowRefreshedEntity -= OnContentTypeRefreshedEntity;
|
||||
MediaTypeService.UowRefreshedEntity -= OnMediaTypeRefreshedEntity;
|
||||
MemberTypeService.UowRefreshedEntity -= OnMemberTypeRefreshedEntity;
|
||||
ContentTypeService.ScopedRefreshedEntity -= OnContentTypeRefreshedEntity;
|
||||
MediaTypeService.ScopedRefreshedEntity -= OnMediaTypeRefreshedEntity;
|
||||
MemberTypeService.ScopedRefreshedEntity -= OnMemberTypeRefreshedEntity;
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
|
||||
@@ -194,9 +194,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
MemberRepository.ScopedEntityRefresh += OnMemberRefreshedEntity;
|
||||
|
||||
// plug
|
||||
ContentTypeService.UowRefreshedEntity += OnContentTypeRefreshedEntity;
|
||||
MediaTypeService.UowRefreshedEntity += OnMediaTypeRefreshedEntity;
|
||||
MemberTypeService.UowRefreshedEntity += OnMemberTypeRefreshedEntity;
|
||||
ContentTypeService.ScopedRefreshedEntity += OnContentTypeRefreshedEntity;
|
||||
MediaTypeService.ScopedRefreshedEntity += OnMediaTypeRefreshedEntity;
|
||||
MemberTypeService.ScopedRefreshedEntity += OnMemberTypeRefreshedEntity;
|
||||
|
||||
_withRepositoryEvents = true;
|
||||
}
|
||||
@@ -213,9 +213,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
MemberRepository.ScopeVersionRemove -= OnMemberRemovingVersion;
|
||||
MemberRepository.ScopedEntityRefresh -= OnMemberRefreshedEntity;
|
||||
|
||||
ContentTypeService.UowRefreshedEntity -= OnContentTypeRefreshedEntity;
|
||||
MediaTypeService.UowRefreshedEntity -= OnMediaTypeRefreshedEntity;
|
||||
MemberTypeService.UowRefreshedEntity -= OnMemberTypeRefreshedEntity;
|
||||
ContentTypeService.ScopedRefreshedEntity -= OnContentTypeRefreshedEntity;
|
||||
MediaTypeService.ScopedRefreshedEntity -= OnMediaTypeRefreshedEntity;
|
||||
MemberTypeService.ScopedRefreshedEntity -= OnMemberTypeRefreshedEntity;
|
||||
|
||||
_withRepositoryEvents = false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user