Filter content items, blog posts, everything based on user profile value or role

Topics: Customizing Orchard, General, Writing modules, Writing themes
Aug 18, 2011 at 1:17 PM

Hi,

I've read a few posts about filtering content, but none seem to quite do what I'm after.

I'm looking for some way to filter every content item added to the site based on either a user profile property (i have added the Profile module) or a role, maybe both.

On each content item I have setup the correct meta data property (i would also like to use tags as part of the filter).

Every user must be logged in on my site.  I have done this with a custom filter in my theme implementing IAuthorizationFilter.  That works fine.  So I know I will always have CurrentUser and I can get the field values/roles from that no probs.

SO...if I user someone creates a blog post and tags it with a certain tag, I want to match that tag against a profile property and if it doesnt match...dont show it.  Like wise if a field on the content item has a certain value I want to show it...otherwise no.

What it seems to me is I need some way to hook into every item of content being rendered (including RecentBlogPosts) and only render it if a tag or property matches.

I don't know how to start this...whether its a Filter or a Module I don't know - I've only been working with Orchard a little while.

Any ideas to get this or close?

Thx

Coordinator
Aug 18, 2011 at 6:25 PM

First whatever code you write it needs to be in a module or a theme. A module would make more sense, but if working in the theme is fine for you, let's stick with it.

You need to prevent something from being displayed, and the best way I see for it is to intercept content items on a per shape basis. So you have to know what kind of shape will be displayed on your site, like a Blog Post Summary, a Blog Post Detail, ... then each time a shape is displayed, check for this shape type, look for the corresponding Blog Post, then specific properties, and finally decided wether or not to render something.

To intercept it, just create a class inheriting from Orchard.DisplayManagement.Implementation.ShapeDisplayEvents, and override the Displaying method. There you can override the shape.ChildContent value, which will be rendered by Orchard for a shape.

Aug 19, 2011 at 11:18 AM
Edited Aug 19, 2011 at 11:20 AM

Wow, thx.  Thats great.  However!!!

I'm in the displaying method,  checking the context.ShapeMetaData.Type to get RecentBlogPosts, but I can't see how I can access the actual post that is being rendered in part and whether I need to display it or not?

I've got context.Shape.ContentItem which is RecentBlogPosts.

I can't see iterate the actual blog posts to check the tags and remove it if I need to?  For the blog specifically I guess I'm looking for a list of BlogPostPart so Ican access the TagsPart ?  Just cant see where

context.ChildContent is always null?

Thx for your help.

Steve

 

Aug 19, 2011 at 1:30 PM

If all you want to do is override the Search form widget's template, and it sounds like you've already done that, why not just use the admin UI to put the widget in whatever zone you want? You can define a Zone in Layout.cshtml right where you intend to put it, define that Zone name in your Theme.txt file, then you can use the widget UI to place it there. The advantage you get with this is that you can also use a Layer rule to control _when_ it shows up, not just _where_ it shows up (and if you don't care when, then just use the Default layer).

If you don't care about when it shows up and really just want to hard code it in your layout like you're describing, you can probably do it by just asking it to display that shape. See Betrand's two blog posts here for some ideas on doing that:

http://weblogs.asp.net/bleroy/archive/2011/06/30/creating-shapes-on-the-fly.aspx
http://weblogs.asp.net/bleroy/archive/2011/06/30/so-what-are-zones-really.aspx

Aug 19, 2011 at 1:47 PM

Sorry, ignore this. I accidentally posted this to the wrong thread. :-)

Aug 19, 2011 at 2:24 PM

I'll try to at least help a little bit on this one too if I can. :-)

I'm not exactly sure how to do this, but I might be able to at least start you down the right path. If not, hopefully Sebastien will jump in and correct me. If you look at the RecentBlogPostsPartDriver, you'll see that when it's displayed it returns this shape:

return ContentShape(shapeHelper.Parts_Blogs_RecentBlogPosts(ContentItem: part.ContentItem, ContentItems: blogPostList));

So as you've discovered, context.Shape.ContentItem is the RecentBlogPosts content item itself. Then it returns the blog post list in another property called ContentItems. So what does that look like? Well the previous line is this:

var blogPostList = shapeHelper.Parts_Blogs_BlogPost_List(ContentPart: part, ContentItems: list);

So blogPostList is itself another Shape which has two properties, one of which is again named ContentItems with the value coming from the list variable. Again, the previous code is:

var list = shapeHelper.List();
list.AddRange(blogPosts.Select(bp => _contentManager.BuildDisplay(bp, "Summary")));
So, the list is a dynamic list of BlogPost shapes. So, theoretically, if you access context.Shape.ContentItems.ContentItems you'll have a list of BlogPost shapes. From there I believe you'll be able to get to the tags. I haven't actually tried this though, so no warranty implied. :-)

Aug 19, 2011 at 2:34 PM

Okay, I had to try it for myself. :-)

I have a page with the RecentBlogPosts widget on it. So I added a class derived from ShapeDisplayEvents and overrode the Displaying method as Sebastien described. Here's the code I wrote:

public override void Displaying(ShapeDisplayingContext context) {
    if (context.ShapeMetadata.Type == "Parts_Blogs_RecentBlogPosts") {
        var posts = context.Shape.ContentItems.ContentItems;
        foreach (var post in posts) {
            var tags = post.ContentItem.TagsPart.CurrentTags;
        }
    }
}
That seems to work. I can inspect the tags for each post. Note that inside the foreach, post.ContentItem gets me the actual BlogPost content item so from there I can access the different parts. Hope this helps.

Aug 19, 2011 at 5:11 PM

Ahh, excellent.  Thanks very much!!

I wasn't a million miles away, but I was doing casting all over the place!!

.ContentItems.ContentItems!  Never got that one.

Also didn't know you could simply access the parts of a ContentItem dynamically in that way.

--

What I have just tried to do though is filter on this part (Parts_Blogs_BlogPost_List).  THat doesnt come up on the blog home page. (even though it shows in the Designer inspector thing.  Each post comes through as a Content part - any ideas on that one?  Seems like it will be tricker to hide the post when all I have is the post, not the list of posts?

For the recent blog posts widget -  i am doing this...

context.Shape.ContentItems.ContentItems = filteredPosts;

where filteredPosts is a list of posts after I've removed certain ones.

I'd rather keep it together, but perhaps the BlogPost_List view is worth a look? mmm

thx again for your help.

Aug 19, 2011 at 5:33 PM

Hmm, well for the blog itself it gets rendered as a ShapeResult from the Item action on the BlogController. The actual list of blog posts comes from this code:

var list = Shape.List();
list.AddRange(blogPosts);
blog.Content.Add(Shape.Parts_Blogs_BlogPost_List(ContentItems: list), "5");
So you're right, there is a Parts_Blogs_BlogPost_List shape that you should be able to hook into, and it has a ContentItems property which is the list of post shapes. From there I would think you can get to the actual blog post parts in the same way as before.

Aug 19, 2011 at 8:20 PM
Well I thought the same, but stepping through the blog home page the BlogPost_List doesn't come up.

Well not that I can see. It's late perhaps I'm missing something else!

On 19 Aug 2011, at 18:37, "kevink" <notifications@codeplex.com> wrote:

From: kevink

Hmm, well for the blog itself it gets rendered as a ShapeResult from the Item action on the BlogController. The actual list of blog posts comes from this code:

var list = Shape.List();
list.AddRange(blogPosts);
blog.Content.Add(Shape.Parts_Blogs_BlogPost_List(ContentItems: list), "5");
So you're right, there is a Parts_Blogs_BlogPost_List shape that you should be able to hook into, and it has a ContentItems property which is the list of post shapes. From there I would think you can get to the actual blog post parts in the same way as before.

Aug 19, 2011 at 8:35 PM

Okay, we may have to hope Sebastien can chime in. Looking at the Item action in the BlogController again I see this code:

// primary action run for a home paged item shall not pass
if (!RouteData.DataTokens.ContainsKey("ParentActionViewContext")
    && blogPart.Id == _routableHomePageProvider.GetHomePageId(_workContextAccessor.GetContext().CurrentSite.HomePage)) {
    return HttpNotFound();
}
This seems to be saying that if the blog is made the home page, this action will return a 404. So making the blog the homepage must result in the data coming from somewhere else, meaning that there's probably a different shape being rendered. But I don't know where that is. :-)

Aug 20, 2011 at 12:12 PM
Edited Aug 20, 2011 at 12:13 PM

Okay, so I've made progress, effectively got what I'm after...but I'm not 100% sold on the solution!

I have been through the Orchard.Blog module and I could achieve what I'm after pretty easiely editing that code.  I'll come back to that!

I have this...

            string type = context.ShapeMetadata.Type;
            
            if (type == "Content")
            {
                var tags = context.Shape.ContentItem.TagsPart.CurrentTags;

                var item = context.Shape.ContentItem;

                bool nd = false;

                foreach (var tag in tags)
                {
                    if (!_utils.DoesUserHavePermission((Orchard.Tags.Models.TagRecord)tag))
                    {
                        nd = true;
                        context.Shape = null;
                        context.ChildContent = new HtmlString("&nbsp;");
                        context.ShapeMetadata.ChildContent = new HtmlString("&nbsp;");
                    }
                    
                }
                if (nd) return;
            }

It's a bit muddly, I'll tidy it up a bit later, but you can see my process...I tried setting the context.Shape to null...no luck.
Then I was just setting context.ChildContent, but that didn't do the trick...so i adding ShapreMetadata.ChildContent (after looking at the code for the display factory).

This also works in the admin section which is a nice touch!

I did consider doing in in the BlogList view...but I think I prefer this way!  Easier for other devs to pick up as well...

Back to the change in Orchard.Blogs module...as pointed out thats not a great plan.  How about creating a new blog module called Orchard.MyBlog or something more useful, copying the files from the orginal and editing what I need.  DIsable the normal blog module in the dashboard and enabling mine...is there somehting that could go horribly wrong doing that?

 

 

 

Aug 22, 2011 at 12:46 PM

Theoretically there's nothing wrong with doing that. The only issue is that you won't get any future updates to the blog functionality unless you manually merge them into your custom module.

Aug 25, 2011 at 8:46 PM
sebastienros wrote:

First whatever code you write it needs to be in a module or a theme. A module would make more sense, but if working in the theme is fine for you, let's stick with it.

You need to prevent something from being displayed, and the best way I see for it is to intercept content items on a per shape basis. So you have to know what kind of shape will be displayed on your site, like a Blog Post Summary, a Blog Post Detail, ... then each time a shape is displayed, check for this shape type, look for the corresponding Blog Post, then specific properties, and finally decided wether or not to render something.

To intercept it, just create a class inheriting from Orchard.DisplayManagement.Implementation.ShapeDisplayEvents, and override the Displaying method. There you can override the shape.ChildContent value, which will be rendered by Orchard for a shape.

I'm trying to work on this from the Module perspective. My goal is to get all the tags on a page/post, display the other content which is corresponding to those tags on the same page.

Please help

Thanks in advance

Coordinator
Aug 25, 2011 at 8:51 PM

Did you read this? http://orchard.codeplex.com/discussions/262677

Aug 26, 2011 at 2:48 PM
bertrandleroy wrote:

Did you read this? http://orchard.codeplex.com/discussions/262677

aha, thanks so much. It works like a charm :) Base on the solution on that discussion, a dependency has to be created between my module and the tag module. Initially, I'd like to eliminate all dependency and make my module as isolated as possible. That was why I tried to see that I can use this solution to see what components are loading. If one of those components is tag, then I want to see what tag is loading without using TagsPart class to get those like in the other solution. Is there a way to do that or I'm just too greedy to my desire implementation? 

Coordinator
Aug 26, 2011 at 6:02 PM

Frankly I don't see the point. If you are going to use the data from tags, you already *have* a dependency. Might as well do it in the clean way.

Aug 26, 2011 at 6:08 PM

Thanks. I'm just too greedy :D. Thanks for quick replying