Monday, December 11, 2017

Schedule publishing with #Sitecore Workflow





“Workflows provide a flexible and controllable way of content creation, maintenance, and review. Sitecore's workflow facilities make it possible to quickly define sophisticated workflows, often without writing any code at all.”
Workflow is well known feature in the Sitecore, you can extend/customize it accordingly to the business need, states of the workflow can be configurable and extend as per the requirement.

Every workflow has the last state called final state, where Item is ready for publish, Sitecore also have the default publish state called Approved, with Auto Publish Action, which means once the item is in Approved State, it can be Auto Publish.

If you’re not familiar with the Sitecore workflow, you can refer the below posts:





Problem statement

We had the requirement “capability to scheduled publishing during the last state of the workflow”, that’s called “Publisher”.

Publisher can able to publish the content immediately or can scheduled for publishing for the future date, for example publisher want to launch the campaign in the mid night, so they can do the schedule publishing with the future date.


Solution:

Below Is the high-level solution to achieve the above requirement:

  1. Provide 2 features to the publisher one is Publish Now and other is Schedule Publish, So the publisher can decide what to do?
  2. If publisher click on Schedule Publish, it will prompt the dialog  to  get the input as “DateTime”
  3. Schedule the item for publish with provided “Datetime”
  4. Run the agent in the background, which will execute in the specific interval (30minutes) and check if scheduled Item is Ready for publishing or not, if yes it will publish the Item.


Below is the step by step implementation:

  • Create the state called Publish in the workflow
  • Create 2 command called Publish now and Schedule Publish in the Publish state as below:

  • Publish Now command simple called the next state called published, which have the default action Auto-publish, which means if the publisher click on Publish now, content will be published directly

  • “Schedule publish” command has the field called “comment Template”, Thanks @Sitecore to provide the Comment Template field, where we can configure the different type of field along with comment field

  • I created the custom Comment template called schedule which has 2 fields comments and “Schedule DateTime “

  • Now, the “schedule publish” command will prompt the comment template fields which we just configured here, in the below image, we can see the 2 command which we have configured in the publish workflow state “Publish Now” and “schedule Publish”



  • This dialog box prompt the schedule datetime, which we can use as the parameter later, so now publisher can have 2 option, publish now and schedule publish and on the click on schedule publish, the above dialog will prompt for schedule datetime
  • Now, if we consider the workflow state called “Publish” and look into the “schedule publish” command, it will call the action named “Schedule” under the next stage called “scheduled”, which mean if the publisher click on the schedule publish button, it prompt the dialog box(comment Template) and call the “Schedule” action.

  • I have configured the custom class function(Sitecore.Foundation.Publishing.ScheduleAction, Sitecore.Foundation.Publishing) in the field called “Type string” in  Schedule” action Item, as highlighted in the above image.
Below Is the code of the “ScheduleAction” function, which create the queue in the sitecore using the sitecore Item in the specific folder.


using Sitecore.Data.Items;
using Sitecore.Foundation.SiteSetting.Repositories;
using Sitecore.Workflows.Simple;
using System;
using System.Collections.Generic;
using Sitecore.Tasks;
using System.Linq;
using System.Web;
using Sitecore.Data.Fields;

namespace Sitecore.Foundation.Publishing
{
    public class ScheduleAction
    {
        public void Process(WorkflowPipelineArgs args)
        {
            PushItemToQueue(args);
        }

        public void PushItemToQueue(WorkflowPipelineArgs args)
        {
            if (args.DataItem == null)
                return;

            //Read the comments template field
            if (args.CommentFields["Schedule DateTime"] != null && string.IsNullOrEmpty(args.CommentFields["Schedule DateTime"]) == false)
            {
                var sourceItem = args.DataItem;
                string scheduleTime = args.CommentFields["Schedule DateTime"];

                //create the item Name
                string ItemName = ItemUtil.ProposeValidItemName(string.Format("{0}SchedulePublishQueue", sourceItem.ID.ToString()));

                using (new Sitecore.SecurityModel.SecurityDisabler())
                {
                    // Get the master database
                   Sitecore.Data.Database master = Sitecore.Data.Database.GetDatabase("master");
                    // Get the template for which you need to create item
                    Sitecore.Data.Items.TemplateItem template = master.GetItem(new Data.ID(Constants.schedulePublishQueueTemplateId)); //  ("/sitecore/templates/Sample/Sample Item");
                    // Get parent Item
                    Item parentItem = master.GetItem(new Data.ID(Constants.schedulePublishQueueParentFolderId));
                    // Add the item to the site tree
                    Item publishQueueItem = parentItem.Add(ItemName, template);
                    publishQueueItem.Editing.BeginEdit();
                    try
                    {
                        // Assign values to the fields of the new item
                        publishQueueItem.Fields["Publish Item Id"].Value = sourceItem.ID.ToString();
                        publishQueueItem.Fields["Publish Item Path"].Value = sourceItem.Paths.Path;
                        publishQueueItem.Fields["Publish Schedule Date"].Value = scheduleTime;
                        publishQueueItem.Editing.EndEdit();
                    }
                    catch (System.Exception ex)
                    {
                        // Log the message on any failure to sitecore log
                        Sitecore.Diagnostics.Log.Error("Could not update item " + publishQueueItem.Paths.FullPath + ": " + ex.Message, this);
                        publishQueueItem.Editing.CancelEdit();
                    }
                }


              
            }
        }
    }
}




I have created the template called “SchedulePublishQueue” which will be used at the time of  Sitecore item creation when someone click on “schedule publish” button(for temporary queuing purpose)




Below is the output of the above function:


The above function creates the Sitecore items using the template “SchedulePublishQueue” and read the fields from the comment parameter template and set the value in the SchedulePublishQueue template field for example the “Publish schedule Date” Field.

Now, in this way, we can create the publishing schedule queue within the sitecore itself, which is easy to maintain.


Note: we can also create this queue in the external database as well.




Now everything done, Items has been scheduled, what Next? 
I Need some Job/ Agent/ service which can run in the background, and can configure in the Sitecore.

The moment I thought the sitecore background Job, I remembered the Akshay sura module named “SiteCron”, Yes “SiteCron is the Sitecore module for your everyday scheduling needs” you can find the more details from here,



But I just used the sitecore default agent, As I was in the hurry to implement this POC, so below is the agent that I have configured which will execute in the background with the interval of 1 hour.

<agent type="Sitecore.Foundation.Publishing.Tasks.PublishScheduleCommand, Sitecore.Foundation.Publishing"             method="Run" name ="Auto Publish Schedule" interval="00:01:00">      </agent>

Basically, this agent will check the schedule publish queue which we just created above, and validate the “Publish schedule Date” time with the current time, If time is less than and equals to the current datetime, it will publish the item and then recycle it, so it will be removed from the scheduled publish queue, so evertime, queue would be clean, below is the code of the agent :



using Sitecore.Data;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.SecurityModel;
using Sitecore.Tasks;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Sitecore.Foundation.Publishing.Tasks
{
    public class PublishScheduleCommand
    {
        Database webdb = Sitecore.Configuration.Factory.GetDatabase("web");
        Database masterdb = Sitecore.Configuration.Factory.GetDatabase("master");

        public void Run()
        {
            Item rootFolder = masterdb.GetItem(new Data.ID(Constants.schedulePublishQueueParentFolderId));
            IEnumerable<Item> unPublishedItem = rootFolder.Axes.GetDescendants().Where
                ( (x => !string.IsNullOrEmpty(x["Publish Item Id"]) && !string.IsNullOrEmpty(x["Publish Schedule Date"]) &&  GetDateTimeFieldValue(x.ID.ToString(), "Publish Schedule Date") <= DateTime.UtcNow)
                );

            if (unPublishedItem != null && unPublishedItem.Count() > 0)
            {
                foreach (var ScheduleQueueItem in unPublishedItem)
                {
                    //Get Schedule publish Item
                    Item scheduleItem = masterdb.GetItem(new Data.ID(ScheduleQueueItem["Publish Item Id"]));
                    PublishScheduleItem(scheduleItem, ScheduleQueueItem);
                }
            }
           
        }
        /// <summary>
        /// by: Ashish Bansal
        /// For: Publish the schedule sitecore Item, Recyle the schedule queue and change the workflow state
        /// </summary>
        /// <param name="actualPublishItem"></param>
        /// <param name="ScheduleQueueItem"></param>
        private void PublishScheduleItem(Item actualPublishItem, Item ScheduleQueueItem)
        {

            try
            {
                using (new Sitecore.SecurityModel.SecurityDisabler())
                {
                    // target databases
                    Database[] databases = new Database[1] { webdb };
                    Sitecore.Handle publishHandle = Sitecore.Publishing.PublishManager.PublishItem(actualPublishItem, databases, webdb.Languages, false, false);
                   
                    // change the workflow state
                    actualPublishItem.Editing.BeginEdit();
                    actualPublishItem["__Workflow state"] = Constants.publishedWorkflowStateID;
                    actualPublishItem.Editing.EndEdit();

                    ScheduleQueueItem.Editing.BeginEdit();
                    ScheduleQueueItem["Published DateTime"] = DateUtil.ToIsoDate(DateTime.Now);
                    ScheduleQueueItem["Is Published"] = "true";
                    ScheduleQueueItem.Editing.EndEdit();

                    //remove from the Publishing Queue
                    ScheduleQueueItem.Recycle();


                }
            }
            catch (Exception ex)
            {
                Sitecore.Diagnostics.Log.Error("Sitecore.Foundation.Publishing.Tasks.PublishScheduleCommand.PublishScheduleItem" + " Error in" +   ex.Message, this);
            }
          
          
        }
        private DateTime GetDateTimeFieldValue(string currentItemID, string fieldName)
        {
            Sitecore.Data.Database master = Sitecore.Data.Database.GetDatabase("master");
            Item currentItem = master.GetItem(new Data.ID(currentItemID));
            DateField fieldValue = currentItem.Fields[fieldName];
            DateTime publishDate = DateTime.MinValue;
            if ((fieldValue != null) && (fieldValue.InnerField.Value != null))
                publishDate = DateUtil.IsoDateToDateTime(currentItem.Fields[fieldName].Value);
            return publishDate;
        }
    }
}

Note: There is a scope of optimization in the above solution, this is just for POC purpose, like I said, we can use “SiteCron” for the background Job, we can maintain the queue in the external system, we can provide more features, Code optimization( using Index search API to get the schedule queue Items) etc, etc..

I hope this article will help you for schedule publishing in the workflow:


 Happy Sitecoring 

Thursday, November 30, 2017

Connect with #Sitecore community through various channels







Now, Sitecore community is growing drastically, and I am proud to be a part of this wonderful community, yes this is our #Sitecore community.

There are various way to join this community through different channels, blogs, slack, stack exchange, Local Sitecore user group, market place, Sitecore documentation etc.. etc..

Here I have consolidated the different Sitecore networks website’s list which can help you to explore various channels, getting information, Sitecore documentation, Sitecore Market Place modules, MVP details, Sitecore user group information etc.



                         

Sitecore Community:
Get help from members of Sitecore community, including Sitecore employees, Certified Sitecore Developers, and MVPs. https://community.sitecore.net/

Sitecore documentation:
Access official Sitecore documentation for Sitecore Experience Platform and other Sitecore products.

Sitecore Market Place:
Discover and contribute to Sitecore community-driven modules, You can find different sitecore modules, If you have some common solution, you can create as a module and submit here.

Sitecore Slack Community Chat
Join the Sitecore slack channel, where you can find the quick response from the Sitecore members including Sitecore employees, MVPs.

How to join the channel:

Thanks, Akshay, Mike Reynolds and Johann Baziret for this amazing initiative.

Sitecore Stack Exchange
Sitecore Stack Exchange is a question and answer site for Sitecore users, aim to answering all the question regarding the Sitecore.

Thanks Mark Cassidy for such great initiative.

Sitecore User Group Site
Sitecore User Group Directory is dedicated to supporting the Sitecore communities wherever they exist across the globe.

Sitecore User Group Conference (SUGCON)
Best way to explore the different Sitecore world 😊 through this great Sitecore conference events which happens every year to discuss the latest Sitecore topics, we really making fun, learning, networking, discussing sitecore in this event.
Stay tune on http://www.sugcon.eu/

Sitecore Hackathon
Sitecore hackathon is a free online community driven event organized by Akshay Sura and supported by Sitecore.
You can participate in this event with team, find more details here:

Sitecore MVP Information
Find all the information related sitecore MVP, recommendation, nomination, list of existing sitecore MVP here

Sitecore Facebook Group:
Join the sitecore Facebook group, you can share the sitecore related blogs, Information, Event announcement etc.


Sitecore Support Help Desk:
Open a support ticket and get in touch with Sitecore Support representative. Available to Certified Sitecore Developers.

Sitecore User Voice:
Welcome to the Sitecore feedback and suggestions site! We love hearing from our customers. If you have suggestions for how we can improve any area of Sitecore, please share them with us here. While we can't respond to every suggestion, we are committed to reading every post.


Sitecore Habitat
Sitecore Habitat is a range of sites demonstrating the capabilities of the Sitecore, find the Sitecore habitat demo here:


Sitecore Profile
Find and create your profile here, here you can see all your contribution, certificate etc



I hope this strong #Sitecore networks will help you to explore the Sitecore community.