Monday, October 5, 2009

ContentTypeUsage Solution

I just finished a solution with which it would be easy to determine where ContentTypes are in use. The solution should work for WSS3 and Moss2007.

In the Solution there is a Site Collection Feature which registers two Custom Actions.

Navigate to a ContentType:





The "View usage" CustomAction should be visible:


The urls will be displayed of the Lists where the ContentType is in use:

This solution is not extensivly tested yet.
https://MRCL.svn.codeplex.com/svn/Wss3/ContentTypeUsageSolution/ContentTypeUsageSolution.wsp

WSPBuilder + Visual Studio 2008

Tuesday, September 15, 2009

GoogleAnalytics Solution

Just finished a WSS3/Moss2007 Solution to provide GoogleAnalytics page-tracking.


The Solution consists of two seperate SiteCollection-features:




This is a snapshot of the configuration page which is accessible via SiteSettings:





The GoogleAnalytics-script will be injected via a DelegateControl which must be present in the head of the MasterPage.


The sourcecode and package is available for download from Codeplex Subversion.

Over here you'll be able to find a blog of Wesley Baker which also has a Google Analytics Solution. I reused some of his ideas for my implementation.

Monday, September 14, 2009

GoogleAJAXLibrariesAPI

Since over a year Google Code is hosting a set of clientside libraries under the name Google AJAX Libraries API. For easy usage in Sharepoint (WSS3 / Moss2007) i´ve wrapped the API's into seperate SiteCollection Features.



For the features to work, the Masterpages must be prepped by adding the following DelegateControl to the head:



When all Features of this Solution are activated, the following script will be rendered in the head:


The sourcecode and the package of the Solution can be downloaded from here. The solution is built using Visual Studio 2008 and WSPBuilder.
Because the scripts are loaded from the Google-servers, websites running under HTTPS might display clientside warnings to the visitor.
At this moment i've selected versions by a general string, as this would enable minor updates of the libraries to automatically be used. Selecting a specific version of a library is easy by changing the version-string in the elements.xml-files.

FieldContentPanel

The FieldContentPanel is a simple derivative of the standard System.Web.UI.WebControls.Panel. The control is extended with the functionality to suppress itself and it's contents depending on the presence of a value for the given field from the current page/listitem.

This control is based on this posting of Andrew Connell. The sourcecode and the ready-to-use WSP is available for download from the MRCL Codeplex Subversion repository.

This is an example of typical usage of the control:


The FieldName-attribute/property defines which field of the current listitem/page which will be checked for an empty value.

The AlwaysShowInEditMode-attribute/property defines if the panel needs to be displayed in edit-mode even if the fieldvalue would be empty. This setting is important when the FieldContentPanel checks the same field as one of it´s children FieldControl(s). Without this setting one would be unable to change the value of a Field after it has been set to empty.

The solution is built using Visual Studio 2008 and WSPBuilder. This version of the control is usable in WSS3 and Moss2007.

The control can be easily be extended to support custom field-types which have specific empty values.

Tuesday, March 10, 2009

Sharepoint Extended Lookup Field

When designing an Sharepoint implementation I started out with OOTB Sharepoint behaviour. I added a LookupField to the list and pointed it to another list. But OOTB all listitems from the referenced list would show up in the dropdown as options. I wanted to be able to control which options would be shown.

I've found an open source fieldcontrol at http://extendedlookup.codeplex.com/ which would be able to do just that by using a view. Unfortunatly it didn't work right-away for me, probably due to some updates from SP-hotfixes. After making some minor changes, i was able to get it working again.

When fiddling with the control I noticed that when a selected item wasn't included in the view it would revert to the first/default-value. Once again i made a minor change, which would make it able to add the selected-item as an option.

Update: just noticed the archived comment at http://extendedlookup.codeplex.com/:
See other projects it looks similar: http://www.codeplex.com/CustomFieldControls http://www.codeplex.com/iLoveSharePoint/Release/ProjectReleases.aspx?ReleaseId=15633

Monday, March 9, 2009

Web Part Audience Targeting RoleProvider-roles through SharePoint Groups

I've managed to spend some days to understand the Audience Targeting functionality of SharePoint Server 2007 (MOSS). I wish to share my conclusions:

* Global Audiences (rule-based, SSP) is OOTB only possible when using SharePoint UserProfiles. I wanted to bind it to an ASP.NET Profile Provider, but that's not possible.

* The Global Audiences (rule-based) functionality is realized directly in-database via stored-procedures.

* It is possible to target (changing visibility) Web Parts to SharePoint Groups.

* If you target a Web Part to a "ASP.NET RoleProvider"-role by adding the role to a Sharepoint Group does not work. (Forms Based Authentication)

* It is possible to override the "TargetTo"-FieldType by replacing the 12\Template\xml\fldtypes_TargetTo.xml file with your own version. This way you are able to hook your own FieldControl into the Audience-functionality.

* Most code which is related to Targeting is hardwired to use "Microsoft.Office.Server.Audience.AudienceManager". It is thus not easy replaceable or extendable

* The ContentByQuery-WebPart is hardwired to use field with ID "{61cbb965-1e04-4273-b658-eedaa662f48d}" as Audience-field.

* The AuthorizationFilter.Editor of Web Parts is buggy. When removing Sharepoint Groups as Target, many strange bugs become apparent.

* It is not possible to manually AudienceFilter the ContentByQuery-WebPart by selecting the "Audience"-field as filter. It gave me an error because it was trying to compare an ntext- with an nvarchar-column.


I wrote the following class to be able to correctly use RoleProvider-roles as Sharepoint-groups for targeting Web Parts. You should register the class as ResourceFilter in the web.config of your SharePoint-site.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
 
using Microsoft.Office.Server.Audience;
using System.Collections;
using System.Globalization;
using Microsoft.SharePoint.WebPartPages;
using Microsoft.SharePoint;
 
namespace AudienceManagerUI
{
    /// <summary>
    /// Replace <RuntimeFilter Assembly="Microsoft.Office.Server, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" Class="Microsoft.Office.Server.Audience.AudienceManager" BuilderURL="audience_chooser.aspx" />
    /// With <RuntimeFilter Assembly="AudienceManagerUI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=6cb89fff80058439" Class="AudienceManagerUI.PatchedResourceManager" BuilderURL="audience_chooser.aspx" />
    /// </summary>
    public class PatchedResourceManager : Microsoft.SharePoint.WebPartPages.IRuntimeFilter2
    {
        private Microsoft.Office.Server.Audience.AudienceManager audienceManager = new Microsoft.Office.Server.Audience.AudienceManager();
 
        public ArrayList InitializeStrings(CultureInfo cultureInfO)
        {
            return audienceManager.InitializeStrings(cultureInfO);
        }
 
        public IToolPaneControl GetToolPaneControl()
        {
            return audienceManager.GetToolPaneControl();
        }
 
        public bool IsFilterActive()
        {
            return audienceManager.IsFilterActive();
        }
 
        /// <summary>
        /// Checks that the values in the IsIncludedFilter property are valid before they are saved
        /// </summary>
        /// <param name="PersistedString"></param>
        /// <returns></returns>
        public string ValidateIsIncludedFilter(string PersistedString)
        {
            return audienceManager.ValidateIsIncludedFilter(PersistedString);
        }
 
        public bool CheckRuntimeRender(string isIncludedFilter)
        {
            bool result = audienceManager.CheckRuntimeRender(isIncludedFilter);
            if (result) return result;
            string[] targets = isIncludedFilter.Split(new string[] {";;" }, StringSplitOptions.None);
            if (targets.Length == 3)
            {
                string[] groups = targets[2].Split(new char[] {';'}, StringSplitOptions.RemoveEmptyEntries);
                foreach (string target in groups)
                {
                    try
                    {
                        SPGroup group = SPContext.Current.Site.RootWeb.SiteGroups[target];
                        if (group.ContainsCurrentUser) return true;
                    }
                    catch (SPException)
                    {
                    }
                }
            }
            return false;
        }
    }
}