Custom Forms for editing the content,

Topics: Customizing Orchard
Nov 21, 2012 at 2:27 AM
Edited Nov 24, 2012 at 3:21 PM

Can somebody help me with how to edit a content item using Custom Forms?

At this point, when I add a Custom form widget for any content type, it adds a form which can add new items. I would like users to edit their own content items without being redirected to admin dashboard (they are not admin).

Also, any suggestions on deleting own content items (by users) directly from list display?

Note: it's not a new module and I'm using content feature to build content type and Custom forms for form.

Thanks.

Nov 21, 2012 at 5:44 PM

Appreciate any responses to my post.

Developer
Nov 21, 2012 at 6:22 PM

It looks like that isn't supported out of the box, so that would mean you have to write it yourself. Should be easy enough if you follow the examples of the CustomForms module and the core Contents module. Essentially you have to write your own controller to support listing and editing.

Nov 21, 2012 at 6:59 PM
Edited Nov 24, 2012 at 3:24 PM

Thanks Sipke for clarifying!!

How about delete...should that be also part of the same custom module or is there any easier way?

 

Nov 21, 2012 at 7:04 PM

On an alternate thought, and for benefit of community, how about writing a custom module which extend functionality of "Projector" or "Query" features and add "Edit" and "Delete" links to listed content item based on content permissions?

"Edit" link uses extension to custom form and uses custom form to populate the content item to support edit.

If I have to do it anyway, would like to do it to everybody's benefit and share the module. Let me know your vote / feedback on this.

Feb 5, 2013 at 3:45 AM
I'm voting for it.

We could definitely use something like this if Orchard is to replace web applications.
Coordinator
Feb 6, 2013 at 5:46 PM
In order to delete the content item from the front end, you can call the admin action, and give a return url, the user won't even go to the admin.
Feb 9, 2013 at 2:14 AM
Hi tippingpoint,

I have coded this out already with security handled via roles. I had to alter the Custom Forms module to accomplish what I need. Because it is now part of core and not it's own project, I am not sure of the best approach for branching so here is the code changes I made:
  • Controllers/ItemController (new section)
        public ActionResult Edit(int id) {
            var form = _contentManager.Get(id);

            if (!Services.Authorizer.Authorize(Permissions.CreateEditPermission(form.ContentType), form, T("Cannot edit content")))
                return new HttpUnauthorizedResult();

            if (form == null) {
                return HttpNotFound();
            }

            dynamic model = _contentManager.BuildEditor(form);

            model.ContenItem(form)
                .ReturnUrl(Url.RouteUrl(_contentManager.GetItemMetadata(form).DisplayRouteValues));

            // Casting to avoid invalid (under medium trust) reflection over the protected View method and force a static invocation.
            return View((object)model);
        }

        [HttpPost]
        public ActionResult Edit(int id, string returnUrl) {
            var form = _contentManager.Get(id);

            if (!Services.Authorizer.Authorize(Permissions.CreateEditPermission(form.ContentType), form, T("Cannot edit content")))
                return new HttpUnauthorizedResult();

            dynamic model = _contentManager.UpdateEditor(form, this);

            if (!ModelState.IsValid) {
                _transactionManager.Cancel();

                // if custom form is inside a widget, we display the form itself
                if (form.ContentType == "CustomFormWidget") { }

                // Casting to avoid invalid (under medium trust) reflection over the protected View method and force a static invocation.
                return View((object)model);
            }

            return this.RedirectLocal(returnUrl, () => Redirect(Request.RawUrl));
        }

        public ActionResult Remove(int id, string returnUrl) {
            var form = _contentManager.Get(id);

            if (!Services.Authorizer.Authorize(Permissions.CreateDeletePermission(form.ContentType), form, T("Cannot delete content"))) {
                return this.RedirectLocal(returnUrl, () => Redirect(Request.RawUrl));
                //return new HttpUnauthorizedResult();
            }

            if (form == null) {
                return HttpNotFound();
            }

            Services.ContentManager.Unpublish(form);

            return this.RedirectLocal(returnUrl, () => Redirect(Request.RawUrl));
        }
  • Security/AuthorizationEventHandler.cs (complete file)
using JetBrains.Annotations;
using Orchard.ContentManagement;
using Orchard.CustomForms.Models;
using Orchard.Security;
using Orchard.ContentManagement.Aspects;
using Orchard.Security.Permissions;

namespace Orchard.CustomForms.Security {
    /// <summary>
    /// Alters the Edit permission requested by the Contents module before editing a form. Returns a Submit permission instead.
    /// </summary>
    [UsedImplicitly]
    public class AuthorizationEventHandler : IAuthorizationServiceEventHandler {
        public void Checking(CheckAccessContext context) { }
        public void Complete(CheckAccessContext context) { }

        public void Adjust(CheckAccessContext context) {
            if (!context.Granted
                && context.Permission.Name == Orchard.Core.Contents.Permissions.EditContent.Name
                && context.Content != null
                && context.Content.ContentItem.ContentType == "CustomForm") {

                context.Adjusted = true;
                context.Permission = Permissions.CreateSubmitPermission(context.Content.ContentItem.As<CustomFormPart>().ContentType);
            }

            // ADDED BY EMS - start (based on BlogAuthorizationEventHandler.cs)
            if (!context.Granted &&
                context.Content.Is<ICommonPart>()) {

                if (OwnerVariationExists(context.Permission, context.Content.ContentItem.ContentType) &&
                    HasOwnership(context.User, context.Content)) {

                    context.Adjusted = true;
                    context.Permission = GetOwnerVariation(context.Permission, context.Content.ContentItem.ContentType);

                }
            }

            // ADDED BY EMS - end
        }

        // ADDED BY EMS - start

        private static bool HasOwnership(IUser user, IContent content) {
            if (user == null || content == null)
                return false;

            var common = content.As<ICommonPart>();
            if (common == null || common.Owner == null)
                return false;

            return user.Id == common.Owner.Id;
        }

        private static bool OwnerVariationExists(Permission permission, string contentType) {
            return GetOwnerVariation(permission, contentType) != null;
        }

        private static Permission GetOwnerVariation(Permission permission, string contentType) {
            if (permission.Name == string.Format(string.Concat(Permissions.EditFormText, "{0}"), contentType)) {
                return Permissions.CreateEditOwnPermission(contentType);
            }
            if (permission.Name == string.Format(string.Concat(Permissions.DeleteFormText, "{0}"), contentType)) {
                return Permissions.CreateDeleteOwnPermission(contentType);
            }
            return null;
        }

        // ADDED BY EMS - end

    }
}
  • Views/Item/Edit.cshtml (complete file)
@using Orchard.ContentManagement
@{
    ContentItem customForm = Model.ContentItem;
    string returnUrl = Model.ReturnUrl;

    // remove default Save/Publish buttons
    Model.Zones["Sidebar"].Items.Clear();
}

@using (Html.BeginFormAntiForgeryPost()) {
    @Html.ValidationSummary()
    // Model is a Shape, calling Display() so that it is rendered using the most specific template for its Shape type
    @Display(Model)

    <fieldset class="submit-button">
        <button type="submit" name="submit.Save" value="submit.Save">@T("Submit")</button>
    </fieldset>
}
  • Permissions.cs (next post as this one is too long)
Feb 9, 2013 at 2:15 AM
Sorry for the length but hope it helps:
  • Permissions.cs (complete file)
using System;
using System.Linq;
using System.Collections.Generic;
using Orchard.ContentManagement;
using Orchard.ContentManagement.MetaData;
using Orchard.ContentManagement.MetaData.Models;
using Orchard.CustomForms.Models;
using Orchard.Environment.Extensions.Models;
using Orchard.Security.Permissions;

namespace Orchard.CustomForms {
    public class Permissions : IPermissionProvider {
        private static readonly Permission SubmitAnyForm = new Permission { Description = "Submit any forms", Name = "Submit" };
        private static readonly Permission SubmitForm = new Permission { Description = "Submit {0} forms", Name = "Submit_{0}", ImpliedBy = new[] { SubmitAnyForm } };

        // EMS CHANGE - start 
        // - Edit own content
        // - Edit content for others
        // - Delete own content
        // - Delete content for others
        public static readonly string EditFormText = "EditForm_";
        public static readonly string DeleteFormText = "DeleteForm_";
        public static readonly Permission EditAnyForm = new Permission { Description = "Edit any forms", Name = "EditAnyForm" };
        public static readonly Permission EditForm = new Permission { Description = "Edit {0} forms", Name = string.Concat(EditFormText, "{0}"), ImpliedBy = new[] { EditAnyForm } };
        public static readonly Permission EditAnyOwnForm = new Permission { Description = "Edit own forms", Name = "EditAnyOwnForm" };
        public static readonly Permission EditOwnForm = new Permission { Description = "Edit own {0} forms", Name = "EditOwnForm_{0}", ImpliedBy = new[] { EditAnyForm, EditAnyOwnForm } };

        public static readonly Permission DeleteAnyForm = new Permission { Description = "Delete any forms", Name = "DeleteAnyForm" };
        public static readonly Permission DeleteForm = new Permission { Description = "Delete {0} forms", Name = string.Concat(DeleteFormText, "{0}"), ImpliedBy = new[] { DeleteAnyForm } };
        public static readonly Permission DeleteAnyOwnForm = new Permission { Description = "Delete own forms", Name = "DeleteOwnForm" };
        public static readonly Permission DeleteOwnForm = new Permission { Description = "Delete own {0} forms", Name = "DeleteOwnForm_{0}", ImpliedBy = new[] { DeleteAnyForm, DeleteAnyOwnForm } };

        // EMS CHANGE - end

        private readonly IContentDefinitionManager _contentDefinitionManager;
        private readonly IContentManager _contentManager;

        public virtual Feature Feature { get; set; }

        public Permissions(IContentDefinitionManager contentDefinitionManager, IContentManager contentManager) {
            _contentDefinitionManager = contentDefinitionManager;
            _contentManager = contentManager;
        }

        public IEnumerable<Permission> GetPermissions() {
            // manage rights only for Creatable types
            var formTypes = _contentManager.Query<CustomFormPart>().List().Select(x => x.ContentType).Distinct();

            foreach (var contentType in formTypes) {
                var typeDefinition = _contentDefinitionManager.GetTypeDefinition(contentType);
                if (typeDefinition == null) {
                    continue;
                }

                yield return CreateSubmitPermission(typeDefinition);
                yield return CreateEditPermission(typeDefinition);
                yield return CreateEditOwnPermission(typeDefinition);
                yield return CreateDeletePermission(typeDefinition);
                yield return CreateDeleteOwnPermission(typeDefinition);
            }

            yield return SubmitAnyForm;
            yield return EditAnyForm;
            yield return EditAnyOwnForm;
            yield return DeleteAnyForm;
            yield return DeleteAnyOwnForm;
        }

        public IEnumerable<PermissionStereotype> GetDefaultStereotypes() {
            return new[] {
                new PermissionStereotype {
                    Name = "Administrator",
                    Permissions = new[] { SubmitAnyForm, EditAnyForm, DeleteAnyForm }
                },
                new PermissionStereotype {
                    Name = "Editor",
                    Permissions = new[] { SubmitAnyForm }
                },
                new PermissionStereotype {
                    Name = "Moderator",
                    Permissions = new[] { SubmitAnyForm }
                },
                new PermissionStereotype {
                    Name = "Author",
                    Permissions = new[] { SubmitAnyForm }
                },
                new PermissionStereotype {
                    Name = "Contributor",
                    Permissions = new[] { SubmitAnyForm }
                }
            };
        }

        /// <summary>
        /// Generates a permission dynamically for a content type
        /// </summary>
        public static Permission CreateSubmitPermission(ContentTypeDefinition typeDefinition) {
            return new Permission {
                Name = String.Format(SubmitForm.Name, typeDefinition.Name),
                Description = String.Format(SubmitForm.Description, typeDefinition.DisplayName),
                Category = "Custom Forms",
                ImpliedBy = new[] { SubmitForm }
            };
        }

        // EMS CHANGE - start 

        /// <summary>
        /// Generates a permission dynamically for a content type
        /// </summary>
        public static Permission CreateEditPermission(ContentTypeDefinition typeDefinition) {
            return new Permission {
                Name = String.Format(EditForm.Name, typeDefinition.Name),
                Description = String.Format(EditForm.Description, typeDefinition.DisplayName),
                Category = "Custom Forms",
                ImpliedBy = new[] { EditForm }
            };
        }

        /// <summary>
        /// Generates a permission dynamically for a content type
        /// </summary>
        public static Permission CreateEditOwnPermission(ContentTypeDefinition typeDefinition) {
            return new Permission {
                Name = String.Format(EditOwnForm.Name, typeDefinition.Name),
                Description = String.Format(EditOwnForm.Description, typeDefinition.DisplayName),
                Category = "Custom Forms",
                ImpliedBy = new[] { EditOwnForm }
            };
        }

        /// <summary>
        /// Generates a permission dynamically for a content type
        /// </summary>
        public static Permission CreateDeletePermission(ContentTypeDefinition typeDefinition) {
            return new Permission {
                Name = String.Format(DeleteForm.Name, typeDefinition.Name),
                Description = String.Format(DeleteForm.Description, typeDefinition.DisplayName),
                Category = "Custom Forms",
                ImpliedBy = new[] { DeleteForm }
            };
        }

        /// <summary>
        /// Generates a permission dynamically for a content type
        /// </summary>
        public static Permission CreateDeleteOwnPermission(ContentTypeDefinition typeDefinition) {
            return new Permission {
                Name = String.Format(DeleteOwnForm.Name, typeDefinition.Name),
                Description = String.Format(DeleteOwnForm.Description, typeDefinition.DisplayName),
                Category = "Custom Forms",
                ImpliedBy = new[] { DeleteOwnForm }
            };
        }

        // EMS CHANGE - end


        /// <summary>
        /// Generates a permission dynamically for a content type
        /// </summary>
        public static Permission CreateSubmitPermission(string contentType) {
            return new Permission {
                Name = String.Format(SubmitForm.Name, contentType),
                Description = String.Format(SubmitForm.Description, contentType),
                Category = "Custom Forms",
                ImpliedBy = new[] { SubmitForm }
            };
        }

        // EMS CHANGE - start

        /// <summary>
        /// Generates a permission dynamically for a content type
        /// </summary>
        public static Permission CreateEditPermission(string contentType) {
            return new Permission {
                Name = String.Format(EditForm.Name, contentType),
                Description = String.Format(EditForm.Description, contentType),
                Category = "Custom Forms",
                ImpliedBy = new[] { EditForm }
            };
        }

        public static Permission CreateEditOwnPermission(string contentType) {
            return new Permission {
                Name = String.Format(EditOwnForm.Name, contentType),
                Description = String.Format(EditOwnForm.Description, contentType),
                Category = "Custom Forms",
                ImpliedBy = new[] { EditOwnForm }
            };
        }

        /// <summary>
        /// Generates a permission dynamically for a content type
        /// </summary>
        public static Permission CreateDeletePermission(string contentType) {
            return new Permission {
                Name = String.Format(DeleteForm.Name, contentType),
                Description = String.Format(DeleteForm.Description, contentType),
                Category = "Custom Forms",
                ImpliedBy = new[] { DeleteForm }
            };
        }

        public static Permission CreateDeleteOwnPermission(string contentType) {
            return new Permission {
                Name = String.Format(DeleteOwnForm.Name, contentType),
                Description = String.Format(DeleteOwnForm.Description, contentType),
                Category = "Custom Forms",
                ImpliedBy = new[] { DeleteOwnForm }
            };
        }

        // EMS CHANGE - end
    }
}
That should do it, hope I didn't miss anything and if there is any way to get it back in core even better.
Mar 5 at 8:15 PM
Will this code still work with Version 1.7.2?
I'm trying to do something similar and I don't want the users who add/update a ContentItem to have to use the dashboard.

At this point I've basically copied the code from the "Edit | Delete" links from the Content List in the Dashboard onto an alternate for my projection results. The Delete calls the admin action and works fine, but I'd rather not give the user permission to access the Admin panel at all though.

So my question is really:
how do I get the Edit action to be called from the projection page? Is there a particular or custom html extension I should be using?

Sorry if I'm asking the obvious. I'm new to both Orchard and web development in general. Thanks for any help.
Mar 6 at 8:14 PM
Hi tcurry, I am still using the same basic concept today on 1.7.2 so you should be all set. To get the "Edit" called you would need to override the view and drop in an Edit button on your own. Just target the controller like normal to have it resolve the URL. Then, on the Edit page you just post back to this same controller. If you have any questions as you go along ping me.

If you are looking for some sophisticated stuff where the url's use autoroute (i.e. all friendly url's) I have some of that in the works. If you don't care about friendly url's the above process will work fine.
Apr 9 at 12:52 PM
For anyone who stumbles on this in the future, Richard Garside has a nice module that accomplishes the same functionality as my code overrides above with the huge advantage of not having to alter existing code in a module. The only subtle difference I saw was that when my code removed an item, I unpublished it where he chose to actually remove it. Here is the link to his module: https://bitbucket.org/NogginBox/custom-forms-edit-orchard-module/overview