Attachment A: Complete Code for the Class: CloneAndAssignAction using System; using System.Collections.Generic; using System.ComponentModel; using System.Text; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using Microsoft.SharePoint; using Microsoft.SharePoint.Utilities; using Microsoft.SharePoint.WebControls; using Microsoft.SharePoint.WebPartPages; using Microsoft.SharePoint.Publishing; using Microsoft.SharePoint.Publishing.WebControls.EditingMenuActions; using Microsoft.SharePoint.Publishing.WebControls; namespace SamplePublishingActions { [Microsoft.SharePoint.Security.SharePointPermission(System.Security.Permissions.SecurityAction.LinkDemand, ObjectModel = true)] [System.Web.AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = System.Web.AspNetHostingPermissionLevel.Minimal)] public sealed class CloneAndAssignAction : ConsoleAction { private const string PostBackEventArgument = "cloneAndAssign"; private const string HiddenFieldName = "cloneAndAssignInfo"; private const string TitleField = "Title"; internal const char SeparatorCharacter = ':'; public CloneAndAssignAction() : base() { this.DisplayText = "Clone and Assign"; } #region public overridden properties public override AuthoringStates RequiredStates { get { return AuthoringStates.IsDocLibListItemTrue | AuthoringStates.InWebPartDesignModeFalse | AuthoringStates.IsPublishingPageTrue | AuthoringStates.SaveConflictExistsFalse; } } [Microsoft.SharePoint.Security.SharePointPermission(System.Security.Permissions.SecurityAction.Demand, ObjectModel = true)] public override void RaisePostBackEvent(string eventArgument) { if (eventArgument == CloneAndAssignAction.PostBackEventArgument) { string formFieldValue = System.Web.HttpContext.Current.Request.Form.Get(HiddenFieldName); string[] dialogValues = formFieldValue.Split(SeparatorCharacter); string name = dialogValues[0] + ".aspx"; string assignTo = dialogValues[1]; //string name = "newpage" + DateTime.Now.Ticks.ToString() + ".aspx"; PublishingPage thisPage = PublishingPage.GetPublishingPage(SPContext.Current.ListItem); PageLayout layout = null; if (thisPage != null) { layout = thisPage.Layout; } // create a new list item using the page name supplied in the dialog // our new page will be in this current web SPWeb currentWeb = SPContext.Current.Web; PublishingWeb web = PublishingWeb.GetPublishingWeb(currentWeb); PublishingPage newPage = null; try { PublishingPageCollection subset = web.GetPublishingPages(1); newPage = subset.Add(name, layout); SPListItem currentItem = thisPage.ListItem; SPListItem newItem = newPage.ListItem; // copy everything from this current item into the new item foreach (SPField field in currentItem.Fields) { if (newItem.Fields[field.Id] == null) { newItem.Fields.Add(field); } } foreach (object key in currentItem.Properties.Keys) { if (newItem.Properties[key] == null) { newItem.Properties.Add(key, currentItem.Properties[key]); } else { newItem.Properties[key] = currentItem.Properties[key]; } } foreach (object obj in SPContext.Current.FormContext.FieldControlCollection) { BaseFieldControl fc = obj as BaseFieldControl; if (fc != null) { newItem[fc.FieldName] = currentItem[fc.FieldName]; } } SPLimitedWebPartManager newItemManager = newItem.File.GetLimitedWebPartManager(System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared); SPLimitedWebPartManager currentItemManager = currentItem.File.GetLimitedWebPartManager(System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared); foreach (WebPart part in currentItemManager.WebParts) { newItemManager.AddWebPart(part, part.ZoneID, part.ZoneIndex); } newItem[TitleField] = "Copy of " + currentItem[TitleField]; newPage.Update(); newPage.ListItem.File.CheckIn(String.Empty); } catch (SPException e) { this.ShowError(e, new ConsoleError(e.Message)); } SPSite currentSite = currentWeb.Site; // send mail to the email address specified in the dialog if (currentSite.WebApplication.OutboundMailServiceInstance != null) { string fullUrl = currentSite.MakeFullUrl(currentWeb.GetFile(newPage.Url).ServerRelativeUrl); SPUtility.SendEmail(currentWeb, false, false, assignTo, "Page " + name + " has been assigned to you.", "A new page, " + name + ", has been created by " + currentWeb.CurrentUser.Name + " and assigned to you. You can view this page at " + fullUrl + "."); } else { this.ShowError(new SPException(), new ConsoleError("The new file was created, but email was not sent because outgoing email is not configured for this server.")); return; } } } public override SPBasePermissions UserRights { get { return SPBasePermissions.AddListItems; } } public override string NavigateUrl { get { // we'll pop up a small dialog box that allows the user to input a page // name and the email address of the person to assign the page to. Page.ClientScript.RegisterHiddenField(HiddenFieldName, String.Empty); string cloneUrl = SPContext.Current.Web.ServerRelativeUrl.TrimEnd('/') + "/_layouts/CloneAssignDialog.aspx"; Page.ClientScript.RegisterClientScriptBlock(typeof(CloneAndAssignAction), "cloneAssignScript", @" var cloneAssignReturnValue = null; function GetCloneAndAssignCallback(ret){cloneAssignReturnValue = ret;} function GetCloneAndAssignInfo(){ window.commonShowModalDialog('" + cloneUrl + @"', 'dialogHeight: 230px; status: no; scroll: no; help: no', GetCloneAndAssignCallback); document.getElementById('" + HiddenFieldName + @"').value = cloneAssignReturnValue; return (cloneAssignReturnValue != null); }", true); return "javascript:" + "if (GetCloneAndAssignInfo()) {" + Page.ClientScript.GetPostBackEventReference(this, CloneAndAssignAction.PostBackEventArgument) + "} else { return false; }"; } } #endregion } [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name="FullTrust")] public class CloneAndAssignPage : Microsoft.SharePoint.WebControls.UnsecuredLayoutsPageBase { protected Microsoft.SharePoint.WebControls.PeopleEditor emailPicker; protected System.Web.UI.WebControls.TextBox namePicker; protected override void OnLoad(EventArgs e) { base.OnLoad(e); Page.ClientScript.RegisterClientScriptBlock(typeof(CloneAndAssignAction), "validateAndReturn", @" var sepChar = '" + CloneAndAssignAction.SeparatorCharacter + @"'; function ValidateAndReturn() {" // make sure there are no instances of the separation // character in either field, then pack them together // and return them to the calling window. + @" var pageNameForVal = document.getElementById('pageName').value; var emailAddressForVal = document.getElementById('emailAddress').value; if (pageNameForVal.indexOf(sepChar, 0) == -1 && emailAddressForVal.indexOf(sepChar, 0) == -1) { window.returnValue = document.getElementById('pageName').value + sepChar + document.getElementById('emailAddress').value; window.close(); } else { alert('The character \'' + sepChar + '\' can not appear in the page name or email address.'); } } ", true); //((DialogMaster)Page.Master).OkButton.OnClientClick = "ValidateAndReturn()"; ((DialogMaster)Page.Master).OkButton.Focus(); } protected override void OnPreRender(EventArgs e) { string emailName = String.Empty; string pageName = String.Empty; if (this.emailPicker.IsValid) { if (emailPicker.ResolvedEntities.Count > 0) { PickerEntity entity = emailPicker.ResolvedEntities[0] as PickerEntity; if (entity != null) { object address = entity.EntityData["Email"]; emailName = address != null ? address.ToString() : String.Empty; Page.ClientScript.RegisterClientScriptBlock(typeof(CloneAndAssignAction), "invokeReturn", "ValidateAndReturn()", true); } } } Page.ClientScript.RegisterHiddenField("pageName", this.namePicker.Text); Page.ClientScript.RegisterHiddenField("emailAddress", emailName); base.OnPreRender(e); } } }  Attachment B: Complete Code for Class: RecentlyEditedItemsNode using System; using System.Collections.Generic; using System.ComponentModel; using System.Text; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using Microsoft.SharePoint; using Microsoft.SharePoint.Utilities; using Microsoft.SharePoint.Publishing; using Microsoft.SharePoint.Publishing.WebControls.EditingMenuActions; using Microsoft.SharePoint.Publishing.WebControls; namespace SamplePublishingActions { [Microsoft.SharePoint.Security.SharePointPermission(System.Security.Permissions.SecurityAction.LinkDemand, ObjectModel = true)] public sealed class RecentlyEditedItemsNode : ConsoleNode { private const string CookieName = "RecentlyEditedItemsCookie"; private const char UrlSeparator = ':'; // this will never be in a URL returned by ServerRelativeUrl private const int MaxRecentUrls = 5; public RecentlyEditedItemsNode() : base() { this.initNode(); } public RecentlyEditedItemsNode(ConsoleNode node) : base(node) { this.initNode(); } private void initNode() { if (SPContext.Current.FormContext.FormMode == Microsoft.SharePoint.WebControls.SPControlMode.Edit) { // We're in the process of editing this current page. Let's add its URL to the list of // recently edited pages. System.Web.HttpContext currentContext = System.Web.HttpContext.Current; string[] recentPageUrls = RecentPageUrls(currentContext); // start out our list of recently visited URLs with the current one string outputRecentPages = SPContext.Current.File.ServerRelativeUrl; // then add the remaining recent URLs (that don't match the current one) int count = 1; foreach (string url in recentPageUrls) { if (count < MaxRecentUrls) { if (url != SPContext.Current.File.ServerRelativeUrl) { outputRecentPages += UrlSeparator.ToString() + url; count++; } } else { break; } } currentContext.Response.Cookies.Add(new System.Web.HttpCookie(CookieName, outputRecentPages)); currentContext.Response.Cookies[CookieName].Expires = DateTime.Now.AddYears(1); currentContext.Items[CookieName] = outputRecentPages; } } public override AuthoringStates RequiredStates { get { return AuthoringStates.EmptyMask; // we want this item always to appear in the console } } private void EnsureChildNodes() { base.ChildConsoleNodes.Clear(); System.Web.HttpContext currentContext = System.Web.HttpContext.Current; string[] recentPageUrls = RecentPageUrls(currentContext); // In order for this list to display correctly, what we do is add each recent URL // as a child node of this node. In the browser, they will appear as a submenu // with this node as the root. foreach (string url in recentPageUrls) { if (!String.IsNullOrEmpty(url)) { this.AddNewNode(url, url); } } } private void AddNewNode(string name, string url) { ConsoleNode recentEditNode = new ConsoleNode(this); recentEditNode.Text = name; recentEditNode.NavigateUrl = url; base.ChildConsoleNodes.Add(recentEditNode); } private string[] RecentPageUrls(HttpContext currentContext) { string recentPages = String.Empty; System.Web.HttpCookie requestCookie = currentContext.Request.Cookies[CookieName]; if (requestCookie != null) { recentPages = requestCookie.Value; } return recentPages.Split(UrlSeparator); } public override ConsoleNodeCollection ChildConsoleNodes { get { this.EnsureChildNodes(); return base.ChildConsoleNodes; } } // If there are no recent URLs to display, then this node will be disabled public override bool IsCurrentlyEnabled(AuthoringStates currentState) { this.EnsureChildNodes(); return (this.HasChildNodes && base.IsCurrentlyEnabled(currentState)); } } } Attachment C: Complete ASPX code listing for the custom Dialog. <%@ Assembly Name="RecentlyEditedItemsAction, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d3bfa9e538ba8cdc"%> <%@ Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%> <%@ Page Inherits="SamplePublishingActions.CloneAndAssignPage" Language="C#" MasterPageFile="~/_layouts/dialog.master" %> <%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> Clone Current Page Clone Current Page
Please provide the following information:
Page Name: .aspx
Assign to:
Attachment D: Complete listing of the CustomEditingMenu.xml file