diff --git a/src/Umbraco.Core/Models/ContentCultureInfos.cs b/src/Umbraco.Core/Models/ContentCultureInfos.cs
index bcf1dbb1b1..f51e3a275a 100644
--- a/src/Umbraco.Core/Models/ContentCultureInfos.cs
+++ b/src/Umbraco.Core/Models/ContentCultureInfos.cs
@@ -28,11 +28,11 @@ namespace Umbraco.Core.Models
/// Initializes a new instance of the class.
///
/// Used for cloning, without change tracking.
- private ContentCultureInfos(string culture, string name, DateTime date)
- : this(culture)
+ internal ContentCultureInfos(ContentCultureInfos other)
+ : this(other.Culture)
{
- _name = name;
- _date = date;
+ _name = other.Name;
+ _date = other.Date;
}
///
@@ -61,7 +61,7 @@ namespace Umbraco.Core.Models
///
public object DeepClone()
{
- return new ContentCultureInfos(Culture, Name, Date);
+ return new ContentCultureInfos(this);
}
///
diff --git a/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs b/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs
index 5238e65631..82b0ba6475 100644
--- a/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs
+++ b/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs
@@ -24,8 +24,12 @@ namespace Umbraco.Core.Models
public ContentCultureInfosCollection(IEnumerable items)
: base(x => x.Culture, StringComparer.InvariantCultureIgnoreCase)
{
+ // make sure to add *copies* and not the original items,
+ // as items can be modified by AddOrUpdate, and therefore
+ // the new collection would be impacted by changes made
+ // to the old collection
foreach (var item in items)
- Add(item);
+ Add(new ContentCultureInfos(item));
}
///
diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs
index 64877e393e..7371686c7c 100644
--- a/src/Umbraco.Core/Services/IContentService.cs
+++ b/src/Umbraco.Core/Services/IContentService.cs
@@ -368,6 +368,11 @@ namespace Umbraco.Core.Services
/// A publishing document is a document with values that are being published, i.e.
/// that have been published or cleared via and
/// .
+ /// When one needs to publish or unpublish a single culture, or all cultures, using
+ /// and is the way to go. But if one needs to, say, publish two cultures and unpublish a third
+ /// one, in one go, then one needs to invoke and
+ /// on the content itself - this prepares the content, but does not commit anything - and then, invoke
+ /// to actually commit the changes to the database.
/// The document is *always* saved, even when publishing fails.
///
PublishResult SavePublishing(IContent content, int userId = 0, bool raiseEvents = true);
@@ -375,11 +380,30 @@ namespace Umbraco.Core.Services
///
/// Saves and publishes a document branch.
///
+ ///
+ /// Unless specified, all cultures are re-published. Otherwise, one culture can be specified. To act on more
+ /// that one culture, see the other overload of this method.
+ /// The parameter determines which documents are published. When false,
+ /// only those documents that are already published, are republished. When true, all documents are
+ /// published.
+ ///
IEnumerable SaveAndPublishBranch(IContent content, bool force, string culture = "*", int userId = 0);
///
/// Saves and publishes a document branch.
///
+ ///
+ /// The parameter determines which documents are published. When false,
+ /// only those documents that are already published, are republished. When true, all documents are
+ /// published.
+ /// The parameter is a function which determines whether a document has
+ /// values to publish (else there is no need to publish it). If one wants to publish only a selection of
+ /// cultures, one may want to check that only properties for these cultures have changed. Otherwise, other
+ /// cultures may trigger an unwanted republish.
+ /// The parameter is a function to execute to publish cultures, on
+ /// each document. It can publish all, one, or a selection of cultures. It returns a boolean indicating
+ /// whether the cultures could be published.
+ ///
IEnumerable SaveAndPublishBranch(IContent content, bool force, Func editing, Func publishCultures, int userId = 0);
///
diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs
index 200c5af096..f14747cda3 100644
--- a/src/Umbraco.Core/Services/Implement/ContentService.cs
+++ b/src/Umbraco.Core/Services/Implement/ContentService.cs
@@ -1272,12 +1272,49 @@ namespace Umbraco.Core.Services.Implement
bool IsEditing(IContent c, string l)
=> c.PublishName != c.Name ||
- c.PublishedCultures.Any(x => c.GetCultureName(x) != c.GetPublishName(x)) ||
- c.Properties.Any(x => x.Values.Where(y => culture == "*" || y.Culture == l).Any(y => !y.EditedValue.Equals(y.PublishedValue)));
+ c.PublishedCultures.Where(x => x.InvariantEquals(l)).Any(x => c.GetCultureName(x) != c.GetPublishName(x)) ||
+ c.Properties.Any(x => x.Values.Where(y => culture == "*" || y.Culture.InvariantEquals(l)).Any(y => !y.EditedValue.Equals(y.PublishedValue)));
return SaveAndPublishBranch(content, force, document => IsEditing(document, culture), document => document.PublishCulture(culture), userId);
}
+ // fixme - make this public once we know it works + document
+ private IEnumerable SaveAndPublishBranch(IContent content, bool force, string[] cultures, int userId = 0)
+ {
+ // note: EditedValue and PublishedValue are objects here, so it is important to .Equals()
+ // and not to == them, else we would be comparing references, and that is a bad thing
+
+ cultures = cultures ?? Array.Empty();
+
+ // determines whether the document is edited, and thus needs to be published,
+ // for the specified cultures (it may be edited for other cultures and that
+ // should not trigger a publish).
+ bool IsEdited(IContent c)
+ {
+ if (cultures.Length == 0)
+ {
+ // nothing = everything
+ return c.PublishName != c.Name ||
+ c.PublishedCultures.Any(x => c.GetCultureName(x) != c.GetPublishName(x)) ||
+ c.Properties.Any(x => x.Values.Any(y => !y.EditedValue.Equals(y.PublishedValue)));
+ }
+
+ return c.PublishName != c.Name ||
+ c.PublishedCultures.Where(x => cultures.Contains(x, StringComparer.InvariantCultureIgnoreCase)).Any(x => c.GetCultureName(x) != c.GetPublishName(x)) ||
+ c.Properties.Any(x => x.Values.Where(y => cultures.Contains(y.Culture, StringComparer.InvariantCultureIgnoreCase)).Any(y => !y.EditedValue.Equals(y.PublishedValue)));
+ }
+
+ // publish the specified cultures
+ bool PublishCultures(IContent c)
+ {
+ return cultures.Length == 0
+ ? c.PublishCulture() // nothing = everything
+ : cultures.All(c.PublishCulture);
+ }
+
+ return SaveAndPublishBranch(content, force, IsEdited, PublishCultures, userId);
+ }
+
///
public IEnumerable SaveAndPublishBranch(IContent document, bool force,
Func editing, Func publishCultures, int userId = 0)
diff --git a/src/Umbraco.Tests/Composing/ActionCollectionTests.cs b/src/Umbraco.Tests/Composing/ActionCollectionTests.cs
index 04bd0a2e1e..46e4eee765 100644
--- a/src/Umbraco.Tests/Composing/ActionCollectionTests.cs
+++ b/src/Umbraco.Tests/Composing/ActionCollectionTests.cs
@@ -49,6 +49,8 @@ namespace Umbraco.Tests.Composing
public bool ShowInNotifier => false;
public bool CanBePermissionAssigned => true;
+
+ public bool OpensDialog => true;
}
public class NonSingletonAction : IAction
@@ -66,6 +68,8 @@ namespace Umbraco.Tests.Composing
public bool ShowInNotifier => false;
public bool CanBePermissionAssigned => true;
+
+ public bool OpensDialog => true;
}
#endregion
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-actions.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-actions.less
index f52258333d..15296a6aaa 100644
--- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-actions.less
+++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-actions.less
@@ -51,6 +51,15 @@
text-decoration: none;
}
+.umb-action {
+ &.-opens-dialog {
+ .menu-label:after {
+ // adds an ellipsis (...) after the menu label for actions that open a dialog
+ content: '\2026';
+ }
+ }
+}
+
.umb-actions-child {
.umb-action {
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertcodesnippet/insertcodesnippet.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertcodesnippet/insertcodesnippet.html
index 58b422ceb2..2ccbf11cc1 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertcodesnippet/insertcodesnippet.html
+++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertcodesnippet/insertcodesnippet.html
@@ -16,24 +16,23 @@