User Contributions Workflow Go to next step after Save

Charles Matvchuk asked on April 15, 2015 17:45

I am not having any luck resolving a scenario. Looking for some assistance.

I have a user contribution web part that has a basic workflow, with one additional step of approval. I only allow a website user on the live site to ADD. Once save is clicked I need to advance automatically to the next workflow step. Right now it just stays in the Edit (first work flow step).

I found a previous thread, without a complete solution. Basic WorkFlow Problem

So I am having difficulty making it work. Does anyone have a solution or any ideas ?

Correct Answer

Charles Matvchuk answered on April 16, 2015 04:45

A lot good information and responses. Thank you. I have come up with the solution. Let me elaborate.

The requirement: All registered users (Who are not editors or Administrators) whose roles allow them to add a page via the user contribution, should be able to do so. They will not have the ability to edit once the page is saved. Then, under pending documents when an editor logs in they can see all the pages that are pending approval and either publish them or reject them. If it is rejected please send the user who posted the page an email explaining why.

This was accomplished by cloning and modifying the users contribution list web part so that after the user clicks save the contribution web part edit form closes and either goes back to just the Add button or to the list depending on how it is configured.

The modification for the cloned web part. ~/CMSWebparts/UserContributions/ContributionsList.ascx

private void CMSForm_OnAfterSave(object sender, EventArgs e)
    {
        if (!StandAlone)
        {

            // Reload data after saving the document
            PagePlaceholder.ClearCache();
            PagePlaceholder.ReloadData();

            if(CloseFormAfterSave)
            {
                URLHelper.Redirect("~/" + DocumentContext.CurrentDocument.NodeAliasPath);
            }
        }
    }

Here you also see "CloseFormAfterSave", this is a Boolean web part setting I added to the cloned web part and I am picking it up from the properties above. This allows me to configure the web part to either the standard behavior which is to stay open after save or to close. Not elegant but works.

Next create a workflow for your page types. Add a step between edit and publish. I call mine "Pending", configure the scope and the security so that the individuals or roles will be able to see them and approve/reject them.

Finally create a class file in your App_Code folder somewhere and call it what you like. Below is the code. It is taking advantage of global events. Notice the comments. It is configured for my needs but if you are so inspired modify it for your own purposes. My complete class is below.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using CMS.Base;
using CMS.DocumentEngine;
using CMS.SiteProvider;
using CMS.Membership;
using CMS.WorkflowEngine;


using CMS.Base;
using CMS.DocumentEngine;
using CMS.PortalEngine;

[CustomDocumentEvents]
public partial class CMSModuleLoader
{
    /// <summary>
    /// Attribute class that ensures the loading of custom handlers.
    /// </summary>
    private class CustomDocumentEventsAttribute : CMSLoaderAttribute
    {
        /// <summary>
        /// The system executes the Init method of the CMSModuleLoader attributes when the application starts.
        /// </summary>
        public override void Init()
        {
            // Assigns custom handlers to events
            DocumentEvents.Update.After += Document_Update_After;
            DocumentEvents.Insert.After += Document_Insert_After;

            DocumentEvents.InsertLink.Before += Document_InsertLink_Before;
        }

        private void Document_Insert_After(object sender, DocumentEventArgs e)
        {

            //Add custom actions here

        }
        private void Document_InsertLink_Before(object sender, DocumentEventArgs e)
        {
            // Add custom actions here
        }
        private void Document_Update_After(object sender, DocumentEventArgs e)
        {
            //Check is site is in Live Site view mode.  In Backend this will not fire.  Manual workflow processing will be done.
            bool isLiveSite = PortalContext.ViewMode.IsLiveSite();

            if (isLiveSite)
            {
                SetWorkFlowToPending(e.Node.NodeID);
            }

        }

        //This will set the worflow on the document to the next status of pending so it will be
        //reviewable in pending documents by the configured users/roles in the workflow's settings.
        private void SetWorkFlowToPending(int nodeID)
        {

            //Get Tree for current user.
            TreeProvider tree = new TreeProvider(MembershipContext.AuthenticatedUser);

            // Get the document
            CMS.DocumentEngine.TreeNode node = tree.SelectSingleNode(nodeID);

            if (node != null)
            {

                //Get Workflow Manager Instance
                WorkflowManager workflowManager = WorkflowManager.GetInstance(tree);

                //Get Workflow for node.
                WorkflowInfo workflow = workflowManager.GetNodeWorkflow(node);

                // Check if the document uses workflow
                if (workflow != null)
                {

                    // Check if the workflow doesn't use automatic publishing, otherwise, documents can't change workflow steps.
                    if (!workflow.WorkflowAutoPublishChanges)
                    {
                        //If node is checked out, this will check it in so workflow can be updated.
                        if (node.IsCheckedOut)
                        {
                            VersionManager versionmanager = VersionManager.GetInstance(tree);

                            // Check in the document if it is checked out depending on settings and workflow.
                            versionmanager.CheckIn(node, null, "Automatic from Live Site Insert.");
                        }

                        // Move the document to the next step
                        workflowManager.MoveToNextStep(node, "Automatic from Live Site Insert.");

                        //Comment out above and uncomment below if you want to check if user has proper permissions.
                        // Check if the current user can move the document to the next step
                        //if (workflowManager.CheckStepPermissions(node, WorkflowActionEnum.Approve))
                        //{
                        //    // Move the document to the next step
                        //    workflowManager.MoveToNextStep(node, null);
                        //}
                    }

                }
            }
        }
    }

}

In the end what I ended up with is a way for registered users of the website allowed to insert pages (Documents) that will be put in a pending workflow status, reviewed, approved or rejected and then published. The solution used a cloned contribution web part. A Workflow. A Global Event Handler.

0 votesVote for this answer Unmark Correct answer

Recent Answers


Charles Matvchuk answered on April 15, 2015 20:15

I believe I am getting closer. How would I get the newly inserted ID in the CMSForm_OnAfterSave event of a contribution web part? I guess I could query the database for last inserted ID but that is not a 100% if multiple inserts are done simultaneously.

Any Thoughts ?

0 votesVote for this answer Mark as a Correct answer

Brenden Kehren answered on April 15, 2015 21:11

Within a custom table, I did this on v7, see similar code below:

CustomTableForm ctForm = (CustomTableForm)sender;
// get the primary key column
IDataClass idc = DataClassFactory.NewDataClass(CustomTableName);
// get the id
lastInsertedId = ValidationHelper.GetInteger(ctForm.BasicForm.Data[idc.IDColumn], -1);
0 votesVote for this answer Mark as a Correct answer

Charles Matvchuk answered on April 15, 2015 21:28

Thanks for the input. I saw that in one of the previous threads. I was unable to get the new document class there. Also it looks like you are just getting the last inserted ID. If multiple inserts happen, and there are a great deal of users, then it wouldn't be 100%. Unless I am missing something in your code.

I am trying to use a global event handler Document_Insert_After() and stumbling around a bit without a solution as of yet.

0 votesVote for this answer Mark as a Correct answer

Joshua Adams answered on April 15, 2015 21:54

Override the data form control and insert your own item in there where you store it in session or something...so you can reference it in your own code.

Here is an example of overriding the dataform control and storing the value in session

public class BPDataForm : DataForm
{
    protected override bool SaveDataInternal()
    {
    //inside of the normal code from source code, find where it is inserted or updated
    if (Mode == FormModeEnum.Insert)
                {
                    // Insert a new content
                    content.Insert();
                    SessionHelper.SetValue("BPEventID", content.ID);
                }
                else
                {
                    // Update existing content
                    content.Update();
                    SessionHelper.SetValue("BPEventID", content.ID);
                }
    }

Hope that at least gets you close to what you were looking for...

0 votesVote for this answer Mark as a Correct answer

Brenden Kehren answered on April 16, 2015 00:36 (last edited on April 16, 2015 00:37)

ctForm is the current object that was inserted so it will be the ID based on that form click event for that user. I'm getting the ID column name dynamically so you can use it across multiple page types. It will be the right ID, no worries.

I should mention that code I shared is in the OnAfterSave event of the form.

0 votesVote for this answer Mark as a Correct answer

   Please, sign in to be able to submit a new answer.