I have a site built in Drupal 6 that requires a number of blocks that are only visible to certain roles or on specific pages. Normally, I would use the block configuration settings to control page and role visibility. However I recently ran across the context module that effectively splits the site into "sections" or "groups" of related attributes.
My question is: When is it appropriate to control block visibility from context rather than the block configuration settings? Should I only use one or the other or a combination of both?
these are different approaches meant for different use cases, but they are not mutually exclusive.
context indeed splits the site into different contexts (and there is also the possibility of context types). and that is effectively its use case: context-based block showing/positioning.
however context won't check for user roles nor multilingual settings (i18n). block supports these cases (and you can combine both context and block to show something on a context depending on user role).
block also allows users to choose whether they want to see the box or not.
my final answer is: combine instead of choosing.
There is no hard and fast rule for when to use core block placement, and when to use context. In general though, if you have a lot of blocks, with different and complex visibiity logic, context will make things more manageable. Additionally, contexts can be exported to code, which allows one to more easily track block configurations in version control. When storing contexts in code, this also allows changes to blocks to be deployed to a production site by simply updating the code, rather than needing to log in and manually move things around via the UI.
Related
I am building a site that targets several audiences, or groups if you will. Nodes can be associated with one or more of these groups. It is important that when one of these groups is accessed from the main nav, the context persists until another group is selected. The group will need to be exposed to panels so that I can pass the value to various included views. Each group also has certain styling associated.
What I have attempted so far is to use Organic Groups and Context modules. When you visit one of the group nodes, there is a context rule for each group which sets a class on the body. I can probably determine the group context in panels as well (haven't quite figured that out yet). This works mostly: I can access the same node from different group nodes and the body class changes accordingly.
The problem is this only works for authenticated users, which is useless to me as all visitors to the site will be anonymous. I feel like I am probably over complicating this and welcome any suggestions!
In essence, what I need it to be able to set an 'environment' globally using links in the main nav, which I can then access in panels and pass to the included views in order to filter content.
I ended up solving this by approaching the problem in a different way. I am now simply setting a session variable whenever the user hits one of the group term pages and then using it as a contextual filter in views. This creates a few more issues in my case but nothing that I don't know how to solve.
I am using a Repeater web part in Kentico to pick out pages from the content tree, to generate nicely repeatable snippets of structured HTML, based on an ASCX transformation. (No surprises here - its been working great!).
However, a new requirement landed whereby alongside the existing HTML structure mentioned above, each repeated item must also have an area where we can add any amount of additional content; based on other web parts.
I have previously written a few "layout" type web parts; implementing CMSAbstractLayoutWebPart, as described here, which has allowed me to generate a repeating amount of web part zones, so I feel like I'm half way there. The issue with that though is that as it stands, I don't seem to be able to make use of the great power and flexibility of the transformations on the page type (which I really think I need to do, and seems like it should be possible..).
I thought I may be able to specify the WebPartZone control in the transformation markup directly, like in the following:
<%# Register Src="~/CMSInlineControls/WebPartZone.ascx" TagName="CMSWebPartZone" TagPrefix="cms" %>
<cms:CMSWebPartZone ZoneID="ZoneIDHere" runat="server" />
<div>
<h3><%# Eval("Heading") %></h3>
<p><%# Eval("Summary") %></p>
</div>
But the design view doesn't seem to pick up the web part zone; so I'm assuming the page lifecycle may not allow me to do this as I'd hoped.
So what I would like to know is:
Is it possible to include WebPartZone control in a transformation such that I can then bring in new web parts in Design view?
If not, what is the recommended way to go about this? (If a custom web part is the way to go, I'd like to clone the Repeater web part in the first instance, as many of its existing properties will be needed - but presumably this must still inherit from CMSAbstractLayoutWebPart?
Thanks!
Update
Good point about the editor's experience; I would definitely like to keep this as consistent as possible. The issue for me is that the requirements that drive my data structures are not always fully understood - and are certainly subject to change. Also, they are liable to vary (albeit subtly) across different products. So I've been trying to keep templates and page types more or less the same across the board, and push out the differences into page properties that drive web part config through macros. So given that the transformation approach won't work, I expect a custom web part is the right fit for me.
I shall post my findings!
I think adding a web part zone into transformation is not a right direction as web part zone should be a part of page template (not transformation) in order to utilize it.
I'd probably try to organize my content so each item you currently showing in the repeater has any number of child pages (potentially of a different type) and use something like hierarchical viewer in order to present all of them on the page. It allows using different transformation based on either page type or node level. Another advantage of this approach is that you keep editors experience consistent.
In the end, I was able to use transformation markup to specify the generation of web part zones. I went down the route of creating a custom web part that inherits from CMSAbstractLayoutWebPart, rather than using CMSRepeater web part or similar...
Here's a breakdown of things I needed to do this:
Gave the custom layout-type web part some properties with which to query the content tree, and supply them to a TreeProvider.SelectNodes() method in the web part code once it has initialised (by overriding the OnInit() method)
Gave the web part a TransformationName property so that the raw markup can be retrieved using TransformationInfoProvider.GetTransformation(this.TransformationName)
Used the markup above and resolved macros within it using each node from the node query
Example of macro resolution code (HTML transformations with macros)
protected virtual string ResolveNode(TreeNode node)
{
var resolver = this.ContextResolver.CreateChild();
resolver.AddAnonymousSourceData(node);
return resolver.ResolveMacros(rawTransformationMarkup);
}
Then I go looking for placeholder text in the transformation markup and use the methods available in the CMSAbstractLayoutWebPart parent class(es), as detailed here, to Append() the resolved markup and also call AddZone() as necessary to tap into the response string builder
Summary: The great functionality of the API allowed me to completely avoid the use of any repeater controls. I could generate web part zones as part of the layout web part usual layout generation process.
It would be nice if I could figure out how to resolve the expressions in SCRIPT tags in ASCX transformations to complete the story, but by using HTML transformations I can use the above to accomplish what I need.
Lets say you are developing an web app that requires that you are able to Add/Edit items. The item form contains several input control. Would you separate the add/edit pages or use the page for add/edit and control via querystring (i.e. ItemAddEdit.aspx?isEdit=1)
The advantage I see in separating is that it is easier for the (non-technical) user to type the page and to determine whether it is add or edit. Also, when there would be specific changes to each page (if ever), it would be easier to change.
For the single page, well, you reuse code which eliminates some duplicate code and avoid possible problems.
And no, I can't use routing.
This is generally something which could be a subjective thing, because there's as many ways of doing things as there are coders, and a lot of it can be depending on how your system is set up generally.
But, if I were to recommend, I'd say the way you should do it if working with asp.net web-forms is to make two web pages (add/ edit) and then you use a user-control on those to group up the shared logic between the two pages. After all - that's why we have user controls.
In this way you can have both of your situations, by keeping logic in one file/class, but still have two entry points.
This would also mirror more how MVC does it, which could be considered a plus.
That being said - if your administration functionality is behind login etc, there's nothing to hinder for actually doing it in one and separate with the query string approach, and then just load the data if editing or display "empty"/base data when creating.
You shouldn't have the user type the addresses anyway, but click through the links to follow your flow, so the query string should be a minimal issue.
But for the sake of keeping your functionality clean and divided, I'd personally recommend going for two page / usercontrol approach.
My site is to have a section for normal users, a section for managers, and a section for use only by anonymous visitors. Each section of the site requires changes to Drupal settings for using a different theme, changing the Primary & Secondary links, changes which blocks are used, etc. In other words, the user experience changes significantly from section to section.
I could probably accomplish what I need by using Drupal's multi-sites, a shared database, and using settings.php to override the variables I need to (ie: menu_primary_links_source). However, to make things more manageable from an operational point of view, and to buy flexibility, I'm considering using the PURL API (purl.module) to prefix the URLs for certain site sections, and having my theme and custom modules react according to the current PURL prefix.
Before I get started, I want to ensure I'm not discounting Spaces.module. Spaces uses PURL, Features, and Context (which I'm also currently using for my site). I don't entirely understand how exactly Spaces fits into the picture. Would it help me make different site sections, each with specific configuration & behavior? Or am I better off depending directly on the PURL API?
The Spaces-PURL-Context conundrum. Fun. I've been meaning to write this up long-style to finish wrapping my head around it.
What is Spaces?
Spaces is a module that creates containers of overridden configuration for your site. It's not specifically about features, it's about any number of configuration values that are able to work with Spaces, including whether a Feature is active or not. (Active does not mean the module is disabled, just that a number of Feature-oriented things are whisked away, such as content types and Spaces-aware Views.
When using Spaces, you need to decide what type of "buckets" you want to use. Open Atrium uses OG and User-shaped buckets, what you need is a new sort of bucket based on user role. For the sake of sanity, you might even need to create a separate module just to define user roles as a more concrete thing in Drupal, kind of like how Spaces OG needs to lean on Organic Groups for a number of concepts.
What is Context?
Context is ultimately a page decorating mechanism. You tell it some stuff about the page, it modifies the page accordingly. Context cannot modify the URL, it's the other way around. Features define Contexts to tell the site how to render a given page uniquely for that Feature, there is no direct connection between Context and Spaces or Context and PURL.
What is PURL?
PURL is a method of sticking things in the URL and keeping them there until you are done with them.
How this Glues Together
Spaces with PURL integration are triggered based on one of two things: The URL or something about the content in the page. To explain this, I'll use Spaces OG as an example.
You click a link. The link was prebuilt with a PURL component that Spaces OG is watching for clues. If that piece of the URL makes sense to spaces, the Space is triggered.
All links except those that opt-out of the PURL modification persist the PURL URL element, meaning the Space is happy, and re-triggers with each page load.
Spaces OG knows to check nodes for their group affiliations. If Spaces can crack open a node and find a group, it will trigger that node's Space, using PURL's modified version of drupal_goto() to redirect the whole page for URL consistency. This will trump any existing URL structure.
If there is no URL component, and the node has no group affiliation, no Space is triggered.
Once the Space is triggered, all of that Spaces configuration values are pulled into play. This will mean the Space's preset defaults (you can have multiple default Space configurations for every Space type) overlay Drupal's defaults, which in turn are overridden by any configuration saved specifically for the Space. In the case of Open Atrium, this includes such nice things as group color, blocks on the dashboard, and enabled Features.
If the user goes to visit something provided by a Feature--a Node, a View, etc, any Contexts related to that node, that view, that URL that any module provides might just be triggered, and start doing things with blocks and theming to tailor the page for the Feature's content.
Next Steps
As I mention above, it sounds to me as though your first step is to try looking at Spaces OG, and rewriting it to be centered around the User Role instead of Organic Groups. You shouldn't have to do much with PURL directly besides a little copy and paste from Spaces OG. You might want to post in the Spaces issue queue to float this idea where the maintainers might see it and give pointers.
The way I understand the spaces module is this:
It provides a way for the features module (and your "features" created from this) to integrate with and be available within defined areas of your site. Out of the box this includes: Organic Groups, Taxonomy, and Users. There is an API to define more "spaces" than this.
So for example you could create a "feature" (with the features module) of an image gallery. Using spaces with organic groups, you would be able to have each group have the ability to enable and disable this feature and it would only be available within that "space" (group in this case).
From the organic groups page:
Groups get their own theme, language, taxonomy, and so on. Integrates well and depends upon Views module
So in your situation, you could think of spaces as a way to make organic groups more flexible. As NoParrots said, OpenAtrium (http://openatrium.com/) relies on the features/spaces/context modules heavily, so that might be a good place to review how these modules work together.
EDIT:
I found a great video that might explain things more clearly: http://www.archive.org/details/TheHeartOfOpenAtriumContextPurlAndSpaces_782. Around 16:00 he starts talking about PURL.
From this page (below the video) there is also an explaination of PURL/Context/Spaces which I think is pretty good:
Context is a module for triggering reactive behaviors within a page load.
Controlling block visibility, menu
trails, page classes, and page
template layouts are examples of
things that fall into its
jurisdiction.
PURL is a library for capturing and abstracting request handling that goes
beyond what the Drupal core menu
system provides ($_GET['q']).
Detection of request components, like
subdomain, path prefix, user agent, or
file extension, and sustaining their
presence is its primary role.
Spaces is a generalized configuration override framework. In
theory it allows you to "customize
everything, for anything." In practice
it allows things like custom group
colors and features, per-user
dashboards, and multisite-like usage
of a single Drupal install.
I would suggest using Spaces or Organic Groups. Spaces was used considerably in Open Atrium... a Development Seed out-of-the-box intranet package. Intranets really require the concept of access control and feature visibility depending on which department or role you have so I'm confident that Spaces will be very good for you.
Of course there is the venerable Organic Groups also. Spaces is a "higher" level concept than PURL. Spaces uses the context and PURL modules BTW. My gut instinct is for you to use Spaces or Organic groups.
There are a couple of videos on the net that talk about Spaces. Check them out.
Is there any way to show local tasks to user if they doesn't have necessary permissions? Right now it seems like Drupal just excludes them from page code. I want to show them, but with different CSS class.
Version of Drupal is 5.20
Even though there are some differences concerning the local task building between Drupal 5 and 6, Mac is right that the logic to ignore entries not accessible by the current user is pretty deeply embedded in the menu.inc functions. If you want to look for yourself, start with theme_menu_local_tasks() and follow the function calls from there.
If I had to implement the feature you're looking for, I'd rather avoid Macs suggestion of messing around with the menu access settings directly. Instead, I'd override theme_menu_local_tasks() with a custom version and duplicate the entry retrieval logic in there. The first run would fetch the primary and secondary links as before, and the second would do the same while impersonating another user (probably user 1 in this case). That way, I'd get two versions of the local task markup which I'd then needed to diff somehow in order to find the ones not allowed for the current user, thus needing the extra CSS class.
Note that this would still be somewhat ugly to do, as menu_primary_local_tasks() and menu_secondary_local_tasks() return already themed lists, so the comparison would need to work on the markup, probably parsing out the li tags somehow. So it might be worth spending some time trying to do the same thing (fetching the local tasks as two different users), but using lower level functions to get the entries before theming.
Note: Should you end up using the user impersonation logic, make sure to use the safe, second version that disables session saving during impersonation.
I know the D6 version of hook_menu much better than D5's. AFAIK - however - you can't override that behaviour as it is hardcoded in menu.inc.
If I am right with what above, a workaround (rather inelegant, I must admit) could be:
Remove the access control from the menu item, so that all menu items are visible to all user.
Put access control in the callback directly (you will make the tab non-clickable in a moment, but if the user insert the URL directly, this will prevent access to pages they must not see).
In the page displaying the tabs, load a different js file according to what roles the user has. The js file for users with limited access will select tabs by mean of their text content (at least in D6 tabs do not get any "individual" class: they only get a common "tab" one), it will remove the link to the tabs the user has no permission to visit and it will add a custom class to those tabs that should be displayed differently.
Add CSS theming for your custom class.
As stated before, I do not know D5 much, so it might also turn out that you can actually achieve what you want in a much cleaner way!