As I was going through my upgraded and moved sites I discovered that the “Grouped Listings” web parts were not working on my webs that I’d migrated from a subsite to a site collection or moved from one site collection to another. I didn’t even notice they weren’t working at first because when viewing a published page they were simply not showing anything (no title, or message about no results being returned or anything). When I went to edit a page I saw that the Grouped Listings web part was on the page but there was a message stating that the query returned no items. When I went to edit the web part instead of getting the nice toolbar I got taken to an error page which stated that the list did not exist.

After digging further I discovered that the web part was using the list ID (GUID) of the previously migrated list and not the new list that was created as a result of the move. The problem though was that the browser wouldn’t let me retarget the web part to the correct list. So I had one of three options to solve the problem: delete the web part and replace it with something different; export the web part, change the GUID in the exported XML, import the changed web part and add it to the page, then delete the original web part; or programmatically manipulate the existing web part to point it to the correct list.

If you’ve read anything on this blog then you’ll know which route I took. Programmatically fixing the web part was the most obvious choice to me because I could not only make the code stand alone so that it could be used to retarget any list but I could also re-use the code in my gl-moveweb and gl-convertsubsitetositecollection commands so that those commands would automatically adjust any detected Grouped Listings web parts.

It’s important to note that the “Grouped Listings” web part that I’m referring to here is actually a ListingSummary web part which inherits from the ContentByQueryWebPart. This web part is created during an upgrade – in SPS 2003 there’s a built-in list called Listings which is consumed by the Grouped Listings web part. During the upgrade this built-in list is converted to a standard list of the same name. But now it’s not built-in so it can be edited like any other list. There’s also a new content type added called “Listing” and the Listings list inherits from this content type.

Because the Grouped Listings web part is a bit “special” I decided to add some extra code into the command to help “repair” a content query web part that is pointed to a Listings list. That way if you delete the “Grouped Listings” web part but want to get something similar back you can add a new Content Query web part and point it to the Listings list and then run this command to set the properties that you simply cannot set via the browser. Other than this “special” code that I added the command itself basically just allows you to point a content query web part to a different list or site within the site collection. The core code is shown below:

  1/// <summary>
  2/// Adjusts the web part.
  3/// </summary>
  4/// <param name="web">The web.</param>
  5/// <param name="wp">The web part.</param>
  6/// <param name="manager">The web part manager.</param>
  7/// <param name="listUrl">The list URL.</param>
  8/// <param name="listType">Type of the list (list template).</param>
  9/// <param name="siteUrl">The site URL.</param>
 10internal static void AdjustWebPart(SPWeb web, WebPart wp, SPLimitedWebPartManager manager, string listUrl, string listType, string siteUrl)
 11{
 12    ContentByQueryWebPart cqwp = wp as ContentByQueryWebPart;
 13    if (cqwp == null)
 14        throw new SPException("Web part is not a Content Query web part.");
 15
 16    if (listUrl != null)
 17    {
 18        SPList list = Utilities.GetListFromViewUrl(web, listUrl);
 19        if (list == null)
 20        throw new SPException("Specified List was not found.");
 21
 22        if (list.ContentTypes["Listing"] != null)
 23        {
 24            // The list is a special list - it was upgraded from v2 and corresponds 
 25            // to the grouped listings web part so we need to set some additional
 26            // properties that cannot be set via the browser.
 27
 28            ApplyListTypeChanges(web, cqwp, "Links");
 29
 30            cqwp.AdditionalGroupAndSortFields = "Modified,Modified;Created,Created";
 31            cqwp.DataColumnRenames = "SummaryTitle,Title;Comments,Description;URL,LinkUrl;SummaryImage,ImageUrl";
 32            cqwp.SortByFieldType = "Number";
 33            cqwp.ChromeType = PartChromeType.None;
 34            cqwp.CommonViewFields = "SummaryTitle,Text;Comments,Note;URL,URL;SummaryImage,URL;SummaryIcon,URL;SummaryType,Integer;_TargetItemID,Note";
 35            cqwp.FilterType1 = "ModStat";
 36            cqwp.Xsl =  "<xsl:stylesheet xmlns:x=\"http://www.w3.org/2001/XMLSchema\" version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\" xmlns:cmswrt=\"http://schemas.microsoft.com/WebPart/v3/Publishing/runtime\" exclude-result-prefixes=\"xsl cmswrt x\" > <xsl:import href=\"/Style Library/XSL Style Sheets/Header.xsl\" /> <xsl:import href=\"/Style Library/XSL Style Sheets/ItemStyle.xsl\" /> <xsl:import href=\"/Style Library/XSL Style Sheets/ContentQueryMain.xsl\" /> </xsl:stylesheet>";
 37            cqwp.FilterValue1 = "Approved";
 38            cqwp.ShowUntargetedItems = false;
 39            cqwp.FilterField1 = list.Fields["Approval Status"].Id.ToString();
 40            cqwp.Filter1ChainingOperator = ContentByQueryWebPart.FilterChainingOperator.And;
 41            cqwp.ItemLimit = -1;
 42            cqwp.SortBy = list.Fields["Order"].Id.ToString();
 43            cqwp.SortByDirection = ContentByQueryWebPart.SortDirection.Asc;
 44            cqwp.GroupByDirection = ContentByQueryWebPart.SortDirection.Asc;
 45            cqwp.Description = "Show listings in the portal.";
 46            cqwp.GroupBy = "SummaryGroup";
 47            cqwp.Hidden = false;
 48        }
 49        cqwp.WebUrl = list.ParentWeb.ServerRelativeUrl;
 50        cqwp.ListGuid = list.ID.ToString();
 51        cqwp.ListName = list.Title;
 52    }
 53    else if (siteUrl != null)
 54    {
 55        try
 56        {
 57            SPWeb web2 = web.Site.AllWebs[Utilities.GetServerRelUrlFromFullUrl(siteUrl)];
 58            if (!web2.Exists)
 59            throw new SPException();
 60        }
 61        catch (ArgumentException)
 62        {
 63            throw new SPException(siteUrl + " either does not exist is is invalid or does not belong to the web part's container site collection.");
 64        }
 65        catch (SPException)
 66        {
 67            throw new SPException(siteUrl + " either does not exist is is invalid or does not belong to the web part's container site collection.");
 68        }
 69        catch (FileNotFoundException)
 70        {
 71            throw new SPException(siteUrl + " either does not exist is is invalid or does not belong to the web part's container site collection.");
 72        }
 73
 74        cqwp.WebUrl = siteUrl;
 75        cqwp.ListGuid = string.Empty;
 76        cqwp.ListName = string.Empty;
 77    }
 78    else
 79    {
 80        cqwp.WebUrl = string.Empty;
 81        cqwp.ListGuid = string.Empty;
 82    }
 83
 84    if (listType != null)
 85        ApplyListTypeChanges(web, cqwp, listType);
 86
 87    manager.SaveChanges(cqwp);
 88}
 89 
 90/// <summary>
 91/// Applies the list type changes.
 92/// </summary>
 93/// <param name="web">The web.</param>
 94/// <param name="cqwp">The content query web part.</param>
 95/// <param name="listType">Type of the list.</param>
 96private static void ApplyListTypeChanges(SPWeb web, ContentByQueryWebPart cqwp, string listType)
 97{
 98    if (listType == null)
 99        return;
100
101    SPListTemplateCollection listTemplates = web.Site.RootWeb.ListTemplates;
102
103    SPListTemplate template = listTemplates[listType];
104    if (template == null)
105        throw new SPException("List template (type) not found.");
106
107    cqwp.BaseType = string.Empty;
108    cqwp.ServerTemplate = Convert.ToString((int)template.Type, CultureInfo.InvariantCulture);
109
110    bool isGenericList = template.BaseType == SPBaseType.GenericList;
111    bool isIssueList = template.BaseType == SPBaseType.Issue;
112    bool isLinkList = template.Type == SPListTemplateType.Links;
113    cqwp.UseCopyUtil = !isGenericList ? isIssueList : (isLinkList ? false : true);
114}

The syntax of the command I created can be seen below.

C:\>stsadm -help gl-retargetcontentquerywebpart

stsadm -o gl-retargetcontentquerywebpart

Retargets a Content Query web part (do not provide list or site if you wish to show items from all sites in the containing site collection).

Parameters:
        -url <web part page URL>
        {-id <web part ID> |
         -title <web part title>}
        [-list <list view URL>]
        [-listtype <list type (template) to show items from>]
        [-site <show items from this site and all subsites>]
        [-allmatching (if title specified and more than one match found, adjustall matches)
        [-publish]

Here’s an example of how to retarget all web parts titled “Grouped Listings”:

stsadm -o gl-retargetcontentquerywebpart -url "http://intranet/hr/pages/default.aspx" -title "Grouped Listings" -list "http://intranet/hr/lists/summary links/allitems.aspx" -allmatching

Note that you can specify a list type in the event that you are trying to piont the web part to a completely different type of list (this may not work smoothly if your list has completely different content types and fields as the web part will still attempt to display the fields that it was originally setup to display).

I’ve updated the gl-moveweb and gl-repairsitecollectionimportedfromsubsite commands (used by gl-convertsubsitetositecollection) so that they will attempt to repair the Grouped Listings web parts.