If you’ve read my recent post, Fun with Variations, then you know that I’ve been doing a lot of work with variations lately. One of the things that I had to figure out a solution to was how to prevent the hidden Relationships List from getting out of sync with the pages in our publishing site when we migrated content from our authoring farm to our publishing farm. For specifics on what the Relationships List is see the aforementioned post which also touches upon some issues with it.
Breaking this list is extremely easy – let’s say that you have an authoring farm where your content authors create your initial pages and then you would like to migrate those pages to your public publishing farm using the content deployment API (so you could use my gl-exportlistitem
and gl-importlistitem
commands which use the API or write your own or use Central Admin’s content deployment jobs). In most cases the page will appear to have migrated just fine but you may notice a couple things that aren’t quite right – first, the Variation Label Menu is no longer working – you think to yourself, “well that’s just a navigation thing – maybe if I use the editing page toolbar to update the variations then the navigation will ‘fix itself’?”. So you go into the toolbar and click “Tools -> Update Variation…”. And suddenly you are presented with this very unhelpful error stating that a page already exists at the target location:
So what do you do now? Well, you could delete the page in the other variations but then you’ll lose all history and any translations that you want to keep so that’s not a practical solution. The other option is to try and understand what the failure really is and then fix that. In this case it’s the linking between the Relationships List, the imported page, and the matching pages throughout all the variations. Thus comes the creation of my new command called gl-fixvariationrelationships
.
What this command does is for each page in the source variations Pages library it loops through all variations and makes sure that the GroupID field matches and then it looks for an entry in the Relationships List matching the URL of the page and if it doesn’t find an entry then it creates one and if it does find one then it makes sure that the values are correct. It’s important to note that if your setup has page variations that are named differently from one variation to another then you’ll have to fix the issues manually as there’s no way for me to handle this scenario (I’d recommend keeping page names consistent regardless of this issue though – it’s just easier to follow what page goes with what).
I probably should have done a better job abstracting this code into separate method calls to make it easier to read but time has been kind of constrained lately and I just needed to get it done. Anyway, here’s the code:
1/// <summary>
2/// Processes the specified site.
3/// </summary>
4/// <param name="site">The site.</param>
5/// <param name="verbose">if set to <c>true</c> [verbose].</param>
6/// <param name="pageName">Name of the page.</param>
7public static void Process(SPSite site, bool verbose, string pageName)
8{
9 m_verbose = verbose;
10
11 using (SPWeb rootWeb = site.RootWeb)
12 {
13 Log(string.Format("Begin processing site collection '{0}'.", site.ServerRelativeUrl));
14
15 SPList relationshipList = rootWeb.Lists["Relationships List"];
16 SPList variationLabelsList = rootWeb.Lists["Variation Labels"];
17 PublishingWeb sourceLabelWeb = null;
18 Dictionary<PublishingWeb, bool> labelWebs = new Dictionary<PublishingWeb, bool>();
19 foreach (SPListItem item in variationLabelsList.Items)
20 {
21 Log(string.Format("Getting variation web '{0}'.", item["Label"]));
22
23 SPWeb web = site.OpenWeb(item["Label"].ToString());
24 if (!PublishingWeb.IsPublishingWeb(web))
25 continue;
26
27 if ((bool) item["Is Source"])
28 sourceLabelWeb = PublishingWeb.GetPublishingWeb(web);
29
30 labelWebs.Add(PublishingWeb.GetPublishingWeb(web), (bool) item["Is Source"]);
31 }
32 if (sourceLabelWeb == null)
33 throw new SPException("Unable to identify source label web.");
34
35 Dictionary<PublishingPage, Guid> sourcePages = new Dictionary<PublishingPage, Guid>();
36
37 Log(string.Format("Begin resetting GroupIDs."));
38
39 // First - make sure that all the matching pages have the same group ID
40 foreach (PublishingPage page in sourceLabelWeb.GetPublishingPages())
41 {
42 if (!(pageName == null || pageName.ToLowerInvariant() == page.Name.ToLowerInvariant()))
43 continue;
44
45 Log(string.Format("Procesing page '{0}'.", page.Url));
46
47 Guid groupID = Guid.Empty;
48 if (page.Fields.ContainsField("PublishingVariationGroupID"))
49 {
50 if (page.ListItem["PublishingVariationGroupID"] + "" != "" &&
51 page.ListItem["PublishingVariationGroupID"] + "" != Guid.Empty.ToString())
52 {
53 groupID = new Guid(page.ListItem["PublishingVariationGroupID"] + "");
54 Log(
55 string.Format("GroupID '{0}' found in variation source '{1}'.", groupID,
56 sourceLabelWeb.Url));
57 }
58 }
59 else
60 {
61 Log(string.Format("Unable to locate PublishingVariationGroupID field for page '{0}'", pageName));
62 }
63 if (groupID == Guid.Empty)
64 {
65 Log(string.Format("GroupID not found in source - begin searching variations."));
66 // See if we can find a group ID in matching pages within the other variations
67 foreach (PublishingWeb varWeb in labelWebs.Keys)
68 {
69 if (labelWebs[varWeb])
70 continue; // Don't consider the source as we've already done that
71 try
72 {
73 // Get the matching page
74 PublishingPage varPage = varWeb.GetPublishingPages()[page.Url];
75 if (varPage == null)
76 continue;
77 if (varPage.Fields.ContainsField("PublishingVariationGroupID"))
78 {
79 // If the matching page has a group ID then use that
80 if (varPage.ListItem["PublishingVariationGroupID"] + "" != "")
81 groupID = new Guid(varPage.ListItem["PublishingVariationGroupID"] + "");
82 }
83 }
84 catch (ArgumentException)
85 {
86 }
87 if (groupID.ToString() != Guid.Empty.ToString())
88 {
89 Log(string.Format("GroupID '{0}' found in variation '{1}'.", groupID, varWeb.Url));
90 break;
91 }
92 }
93 }
94 if (groupID == Guid.Empty)
95 {
96 groupID = Guid.NewGuid();
97 Log(string.Format("GroupID not found - new GroupID created: '{0}'.", groupID));
98 }
99
100 // Now that we have a groupID reset all pages to use that same groupID
101 Log(string.Format("Begin resetting variations to use new GroupID."));
102 foreach (PublishingWeb varWeb in labelWebs.Keys)
103 {
104 try
105 {
106 // Get the matching page
107 PublishingPage varPage = varWeb.GetPublishingPages()[page.Url];
108 if (varPage == null)
109 continue;
110 if (varPage.Fields.ContainsField("PublishingVariationGroupID"))
111 {
112 // Set the groupID if it doesn't match what we have
113 if ((varPage.ListItem["PublishingVariationGroupID"] + "").ToLower() !=
114 groupID.ToString().ToLower())
115 {
116 Log(
117 string.Format("Assigning GroupID to page '{0}' in variation '{1}'.",
118 varPage.Url, varWeb.Url));
119
120 varPage.ListItem["PublishingVariationGroupID"] = groupID.ToString();
121 varPage.ListItem.SystemUpdate();
122 }
123 }
124 }
125 catch (ArgumentException)
126 {
127 }
128 }
129 Log(string.Format("Finished resetting variations to use new GroupID."));
130
131 sourcePages.Add(page, groupID);
132 }
133 // Now that all the pages have been reset to use the same group ID for each variation we can now deal with the relationships list
134 Log(string.Format("Finished resetting GroupIDs.\r\n"));
135 Log(string.Format("Begin processing of Relationships List."));
136
137 foreach (PublishingPage page in sourcePages.Keys)
138 {
139 foreach (PublishingWeb varWeb in labelWebs.Keys)
140 {
141 Log(string.Format("Processing page '{0}' on variation '{1}'", page.Url, varWeb.Url));
142
143 SPFieldUrlValue objectID = new SPFieldUrlValue();
144 objectID.Description = varWeb.Web.ServerRelativeUrl + "/" + page.Url;
145 objectID.Url = site.MakeFullUrl(objectID.Description);
146 string groupID = sourcePages[page].ToString();
147
148 SPListItem relationshipItem = null;
149 foreach (SPListItem item in relationshipList.Items)
150 {
151 if (item["ObjectID"].ToString().ToLower() == objectID.ToString().ToLower())
152 {
153 Log(
154 string.Format("Found item in relationships list matching ObjectID '{0}'.",
155 objectID.Description));
156
157 relationshipItem = item;
158 relationshipItem["Deleted"] = varWeb.GetPublishingPages()[page.Url] == null;
159 break;
160 }
161 }
162 if (relationshipItem == null)
163 {
164 Log(
165 string.Format(
166 "Unable to locate item in Relationships List for ObjectID '{0}' - creating a new item.",
167 objectID.Description));
168 // We couldn't find a matching item for the variation so we have to create one
169 relationshipItem =
170 relationshipList.Items.Add(relationshipList.RootFolder.ServerRelativeUrl,
171 SPFileSystemObjectType.File);
172 relationshipItem["ObjectID"] = objectID.ToString();
173 relationshipItem["Deleted"] = varWeb.GetPublishingPages()[page.Url] == null;
174 }
175 relationshipItem["GroupID"] = groupID;
176 relationshipItem.Update();
177
178 Log(
179 string.Format("Relationships List item updated - assigning link to page '{0}'.",
180 page.Url));
181
182 // Now that the relationship list item is set the way we need it we have to link the corresponding page to the item.
183 try
184 {
185 PublishingPage varPage = varWeb.GetPublishingPages()[page.Url];
186 if (varPage == null)
187 continue;
188 string newLinkID = "/" +
189 (rootWeb.ServerRelativeUrl + "/" + relationshipItem.Url).Trim('/');
190 newLinkID = site.MakeFullUrl(newLinkID.Replace(" ", "%20")) + ", " + newLinkID;
191
192 varPage.ListItem["PublishingVariationRelationshipLinkFieldID"] = newLinkID;
193 varPage.ListItem.SystemUpdate();
194 }
195 catch (ArgumentException)
196 {
197 }
198 }
199 }
200 Log(string.Format("Finished processing of Relationships List."));
201
202 foreach (PublishingWeb web in labelWebs.Keys)
203 {
204 web.Web.Dispose();
205 }
206 Log(string.Format("Finished processing site collection '{0}'.", site.ServerRelativeUrl));
207 }
208}
Using the command is very simple – you just pass in the URL of your site collection and then an optional page name if you only wish to fix a specific page. You can also pass in an optional verbose parameter so that you can see exactly what the command is doing:
C:\>stsadm -help gl-fixvariationrelationships
stsadm -o gl-fixvariationrelationships
Links publishing pages using information in the hidden Relationship List list.
Parameters:
-url <url>
[-pagename <name of page to fix (example: "default.aspx")>]
[-verbose]
Here’s a simple example of running this command against a variation site collection:
stsadm -o gl-fixvariationrelationships -url http://portal -verbose
If you only want to affect one page you could use the following:
stsadm -o gl-fixvariationrelationships -url http://portal -verbose -pagename "default.aspx"
Update 9/2/2008: Tim Dobrinski has a great tool that he’s put together for fixing many issues with the relationships list (including addressing sub-sites which I’m not currently dealing with). You can find details about the tool here: http://www.thesug.org/blogs/lsuslinky/Lists/Posts/Post.aspx?List=ee6ea231%2D5770%2D4c2d%2Da99c%2Dc7c6e5fec1a7&ID=21