Sitecore custom publish agent from specific node and at a specific time

Hi all,

As you all know, scheduled publishing will not run automatically unless you enable PublishAgent task in Sitecore config.

By default, all items that are in publish queue will get published when this task triggers. This includes all items in the final workflow state and all items that don't have workflows. Sometimes this is not what you want. Maybe you don't enable workflow on media items and you don't want the items to go live when they are not ready.

The workaround is to create your own PublishAgent task. Looking at the one provided, we can easily extend the code to insert the RootItem to the PublishOptions (this only works with Full or Smart publish mode however):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
    public class CustomContentPublishAgent
    {
        /// <summary>The languages.</summary>
        private readonly List<language> _languages;
        /// <summary>The publish mode.</summary>
        private readonly PublishMode _mode;
        /// <summary>The source database.</summary>
        private readonly string _sourceDatabase;
        /// <summary>The target database.</summary>
        private readonly string _targetDatabase;
        private readonly string _rootItemPath;
        private readonly string _publishingTime;
 
        /// <summary>Gets the language.</summary>
        /// <value>The language.</value>
        [Obsolete("Deprecated - Use Languages instead.")]
        public Language Language
        {
            get
            {
                return this._languages[0];
            }
        }
 
        /// <summary>Gets the languages.</summary>
        /// <value>The languages.</value>
        public List<language> Languages
        {
            get
            {
                return this._languages;
            }
        }
 
        /// <summary>Gets the publish mode.</summary>
        /// <value>The publish mode.</value>
        public PublishMode Mode
        {
            get
            {
                return this._mode;
            }
        }
 
        /// <summary>Gets the source database.</summary>
        /// <value>The source database.</value>
        public string SourceDatabase
        {
            get
            {
                return this._sourceDatabase;
            }
        }
 
        /// <summary>Gets the target database.</summary>
        /// <value>The target database.</value>
        public string TargetDatabase
        {
            get
            {
                return this._targetDatabase;
            }
        }
 
        public string RootItemPath
        {
            get { return this._rootItemPath; }
        }
 
        public TimeSpan PublishingTime
        {
            get
            {
                return TimeSpan.ParseExact(this._publishingTime, "hh\\:mm\\:ss", System.Globalization.CultureInfo.InvariantCulture);
            }
        }
 
        public bool HasTaskExecuted { get; set; }
 
        /// <summary>
        /// Initializes a new instance of the <see cref="T:Sitecore.Tasks.PublishAgent" /> class.
        /// </summary>
        /// <param name="sourceDatabase">The source database.</param>
/// <param name="targetDatabase">The target database.</param>
/// <param name="mode">The publish mode string.</param>
/// <param name="languages">The languages string.</param>
/// <param name="rootItemPath">The languages string.</param>
public CustomContentPublishAgent(string sourceDatabase, string targetDatabase, string mode, string languages, string rootItemPath, string publishingTime)
        {
            Assert.ArgumentNotNullOrEmpty(sourceDatabase, "sourceDatabase");
            Assert.ArgumentNotNullOrEmpty(targetDatabase, "targetDatabase");
            Assert.ArgumentNotNullOrEmpty(mode, "mode");
            Assert.ArgumentNotNullOrEmpty(languages, "languages");
            Assert.ArgumentNotNullOrEmpty(rootItemPath, "rootItemPath");
            Assert.ArgumentNotNullOrEmpty(publishingTime, "publishingTime");
            this._sourceDatabase = sourceDatabase;
            this._targetDatabase = targetDatabase;
            this._languages = ParseLanguages(languages);
            this._mode = ParseMode(mode);
            this._rootItemPath = rootItemPath;
            this._publishingTime = publishingTime;
            Assert.IsTrue(this._languages.Count > 0, "No languages specified in PublishAgent constructor.");
        }
 
        /// <summary>Runs this instance.</summary>
        public void Run()
        {
            if (this.IsPublishingTime())
            {
                this.StartPublish((IEnumerable<language>)this._languages);
                this.HasTaskExecuted = true;
            }
        }
 
        /// <summary>Parses the languages.</summary>
        /// <param name="languages">The languages string.</param>
/// <returns>The languages.</returns>
        private static List<language> ParseLanguages(string languages)
        {
            List<language> languageList = new List<language>();
            string str1 = languages;
            char[] chArray = new char[1] { ',' };
            foreach (string str2 in str1.Split(chArray))
            {
                if (str2.Length > 0)
                    languageList.Add(Language.Parse(str2.Trim()));
            }
            return languageList;
        }
 
        /// <summary>Parses the publish mode.</summary>
        /// <param name="mode">The publish mode string.</param>
/// <returns>The publish mode.</returns>
        private static PublishMode ParseMode(string mode)
        {
            if (mode.Equals("Full", StringComparison.InvariantCultureIgnoreCase))
                return PublishMode.Full;
            if (mode.Equals("Incremental", StringComparison.InvariantCultureIgnoreCase))
                return PublishMode.Incremental;
            return mode.Equals("Smart", StringComparison.InvariantCultureIgnoreCase) ? PublishMode.Smart : PublishMode.Unknown;
        }
 
        /// <summary>Starts the publishing.</summary>
        /// <param name="languages">The languages.</param>
private void StartPublish(IEnumerable<language> languages)
        {
            Assert.ArgumentNotNull((object)languages, "languages");
            Assert.IsTrue(languages.Any<language>(), "languages count should be more than zero");
            string str = string.Join("|", languages.Select<Language, string>((Func<Language, string>)(l => l.Name)));
            PublishingLog.Info("PublishAgent started (source: {0}, target: {1}, mode: {2}, languages: {3}, root item path: {4})".FormatWith((object)this._sourceDatabase, (object)this._targetDatabase, (object)this._mode, (object)str, (object)this._rootItemPath), (Exception)null);
            Database database1 = Factory.GetDatabase(this._sourceDatabase);
            Database database2 = Factory.GetDatabase(this._targetDatabase);
            Assert.IsNotNull((object)database1, "Unknown database: {0}", (object)this._sourceDatabase);
            Assert.IsNotNull((object)database2, "Unknown database: {0}", (object)this._targetDatabase);
 
            //Get content node
            var contentItem = database1.GetItem(this.RootItemPath);
 
            PublishOptions options = new PublishOptions(database1, database2, this.Mode, languages.FirstOrDefault<language>(), DateTime.Now)
            {
                RootItem = contentItem,
                Deep = this.Mode != PublishMode.Incremental
            };
            if (this.Mode == PublishMode.Full)
                options.CompareRevisions = false;
            Publisher publisher = new Publisher(options, languages);
            bool willBeQueued = publisher.WillBeQueued;
            publisher.PublishAsync();
            PublishingLog.Info("Asynchronous publishing {0}. Job name: {1}. Languages: {2}".FormatWith(willBeQueued ? (object)"queued" : (object)"started", (object)publisher.GetJobName(), (object)str), (Exception)null);
            TaskCounters.Publishings.Increment();
        }
 
        //Check if publishing time is near the set value
        private bool IsPublishingTime()
        {
            // this.HasTaskExecuted prevents publishing more than once.
            bool isTime = !this.HasTaskExecuted;
            TimeSpan timeDiff = DateTime.Now.TimeOfDay.Subtract(this.PublishingTime);
            // only return true if the time diff is less than 30 minutes
            if (isTime)
                isTime = timeDiff.Hours == 0 && timeDiff.Minutes >= 0 && timeDiff.Minutes <= 30;
            // reset the setting to false after 30 minutes
            else {
                this.HasTaskExecuted = timeDiff.Minutes < 30;
            }
            return isTime;
        }
    }
You will notice that on the Run method I check whether the server time is around the time I set in the parameter (thanks to this post for the code). This will enable us to run the task near the specific time we set (e.g. midnight).

In the config patch, just add this to enable the custom agent:

1
2
3
4
5
6
7
8
9
10
<scheduling>
  <agent type="YourNameSpace.CustomContentPublishAgent, YourAssembly" method="Run" interval="00:30:00">
    <param desc="source database">master</param>
    <param desc="target database">web</param>
    <param desc="mode (full or incremental)">smart</param>
    <param desc="languages">en</param>
    <param desc="rootItemPath">/sitecore/content</param>
    <param desc="publishingTime">23:30:00</param>
  </agent>
</scheduling>
There you go. You can monitor your Publishing log file to see if the task is properly triggered. Don't forget to disable the task in the SwitchMasterToWeb config as well.

HTH,
Andreas

Comments

Popular posts from this blog

SharePoint 2013 anonymous access add attachments to list item

CRM Plugin - Parent and Child Pipeline