Skip to content
This repository has been archived by the owner on Jan 19, 2021. It is now read-only.

Commit

Permalink
Fixes to tokenparser when provisioning and extracting clientsidepages
Browse files Browse the repository at this point in the history
  • Loading branch information
erwinvanhunen committed Jun 27, 2020
1 parent ce109bb commit 9688ead
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 18 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).

## Changed

- Added an {fqdn} token to the provisioning engine which resolves to yourtenant.sharepoint.com (full qualified domain name) without scheme (unlike {hosturl} which does include the scheme) [erwinvanhunen - Erwin van Hunen]
- Updated the token parser in the provisioning engine: you can now use {pageuniqueid:/path/topage.aspx} tokens to reference to pages that are not in the template. If the token at application time cannot be resolved it will not be replaced with "" but kept in place as is. [erwinvanhunen - Erwin van Hunen]
- Fixed issue when extract a template with a page from the rootsite when choosing to extract assets [erwinvanhunen - Erwin van Hunen]
- Adding authentication cookies for SPO Admin domain #2687 [koenzomers - Koen Zomers]
- Call ParseString for webSettings.SearchCenterUrl #2686 [cebud - Martin Dubec]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Microsoft.SharePoint.Client;
using OfficeDevPnP.Core.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace OfficeDevPnP.Core.Framework.Provisioning.ObjectHandlers.TokenDefinitions
{
[TokenDefinitionDescription(
Token = "{fqdn}",
Description = "Returns a full url of the current host",
Example = "{fqdn}",
Returns = "mycompany.sharepoint.com")]
public class FqdnToken: TokenDefinition
{
public FqdnToken(Web web): base(web, "{fqdn}")
{
}

public override string GetReplaceValue()
{
if (CacheValue == null)
{
TokenContext.Web.EnsureProperty(w => w.Url);
var uri = new Uri(TokenContext.Web.Url);
CacheValue = $"{uri.DnsSafeHost.ToLower().Replace("-admin","")}";
}
return CacheValue;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ public TokenParser(Tenant tenant, Model.ProvisioningHierarchy hierarchy, Provisi
_tokens.Add(new AuthenticationRealmToken(web));
#endif
_tokens.Add(new HostUrlToken(web));
_tokens.Add(new FqdnToken(web));
AddResourceTokens(web, hierarchy.Localizations, hierarchy.Connector);
_initializedFromHierarchy = true;
}
Expand Down Expand Up @@ -220,6 +221,8 @@ public TokenParser(Web web, ProvisioningTemplate template, ProvisioningTemplateA
#endif
if (tokenIds.Contains("hosturl"))
_tokens.Add(new HostUrlToken(web));
if (tokenIds.Contains("fqdn"))
_tokens.Add(new FqdnToken(web));
#if !ONPREMISES
if (tokenIds.Contains("sitecollectionconnectedoffice365groupid"))
_tokens.Add(new SiteCollectionConnectedOffice365GroupId(web));
Expand Down Expand Up @@ -276,6 +279,8 @@ public TokenParser(Web web, ProvisioningTemplate template, ProvisioningTemplateA
if (tokenIds.Contains("apppackageid"))
AddAppPackagesTokens(web);
#endif
if (tokenIds.Contains("pageuniqueid"))
AddPageUniqueIdTokens(web, applyingInformation);

// TermStore related tokens
AddTermStoreTokens(web, tokenIds);
Expand Down Expand Up @@ -609,6 +614,29 @@ private void AddSiteDesignTokens(Web web, ProvisioningTemplateApplyingInformatio
}
}

private void AddPageUniqueIdTokens(Web web, ProvisioningTemplateApplyingInformation applyingInformation)
{

var pagesList = web.GetListByUrl("SitePages", p => p.RootFolder);

var query = new CamlQuery()
{
ViewXml = $"<View><ViewFields><FieldRef Name='UniqueId'/><FieldRef Name='FileLeafRef' /></ViewFields></View><RowLimit Paging='TRUE'>100</RowLimit>"
};
do
{
var items = pagesList.GetItems(query);
web.Context.Load(items);
web.Context.ExecuteQueryRetry();
foreach (var item in items)
{
_tokens.Add(new PageUniqueIdToken(web, $"SitePages/{item["FileLeafRef"]}", Guid.Parse(item["UniqueId"].ToString())));
_tokens.Add(new PageUniqueIdEncodedToken(web, $"SitePages/{item["FileLeafRef"]}", Guid.Parse(item["UniqueId"].ToString())));
}
query.ListItemCollectionPosition = items.ListItemCollectionPosition;
} while (query.ListItemCollectionPosition != null);
}

private void AddSiteScriptTokens(Web web, ProvisioningTemplateApplyingInformation applyingInformation)
{
try
Expand Down Expand Up @@ -687,7 +715,7 @@ internal void RebuildListTokens(Web web)
{
_tokens.Add(new ListViewIdToken(web, list.Title, view.Title, view.Id));
}

foreach (var contentType in list.ContentTypes)
{
_tokens.Add(new ListContentTypeIdToken(web, list.Title, contentType.Name, contentType.Id));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public void ExtractClientSidePage(Web web, ProvisioningTemplate template, Provis
public void ExtractClientSidePage(Web web, ProvisioningTemplate template, ProvisioningTemplateCreationInformation creationInfo, PnPMonitoredScope scope, PageToExport page)
{
bool excludeAuthorInformation = false;
if(creationInfo.ExtractConfiguration != null && creationInfo.ExtractConfiguration.Pages != null)
if (creationInfo.ExtractConfiguration != null && creationInfo.ExtractConfiguration.Pages != null)
{
excludeAuthorInformation = creationInfo.ExtractConfiguration.Pages.ExcludeAuthorInformation;
}
Expand All @@ -71,7 +71,7 @@ public void ExtractClientSidePage(Web web, ProvisioningTemplate template, Provis
}

int promotedState = 0;
if(pageToExtract.PageListItem[OfficeDevPnP.Core.Pages.ClientSidePage.PromotedStateField]!=null)
if (pageToExtract.PageListItem[OfficeDevPnP.Core.Pages.ClientSidePage.PromotedStateField] != null)
{
int.TryParse(pageToExtract.PageListItem[OfficeDevPnP.Core.Pages.ClientSidePage.PromotedStateField].ToString(), out promotedState);
}
Expand Down Expand Up @@ -107,7 +107,7 @@ public void ExtractClientSidePage(Web web, ProvisioningTemplate template, Provis
#endif
)
{

var extractedHeader = new ClientSidePageHeader()
{
Type = (ClientSidePageHeaderType)Enum.Parse(typeof(Pages.ClientSidePageHeaderType), pageToExtract.PageHeader.Type.ToString()),
Expand Down Expand Up @@ -146,14 +146,17 @@ public void ExtractClientSidePage(Web web, ProvisioningTemplate template, Provis
string guidPatternNoDashes = "[a-fA-F0-9]{32}";
Regex regexGuidPatternNoDashes = new Regex(guidPatternNoDashes, RegexOptions.Compiled);

string guidPatternOptionalBrackets = "(?<Bracket>\\{)?[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}(?(Bracket)\\}|)";
Regex regexGuidPatternOptionalBrackets = new Regex(guidPatternOptionalBrackets, RegexOptions.Compiled);

string siteAssetUrlsPattern = "(?:\")(?<AssetUrl>[\\w|\\.|\\/|:|-]*\\/SiteAssets\\/SitePages\\/[\\w|\\.|\\/|:|-]*)(?:\")";
// OLD RegEx with Catastrophic Backtracking: @".*""(.*?/SiteAssets/SitePages/.+?)"".*";
Regex regexSiteAssetUrls = new Regex(siteAssetUrlsPattern, RegexOptions.Compiled);

if (creationInfo.PersistBrandingFiles && !string.IsNullOrEmpty(extractedPageInstance.ThumbnailUrl))
{
var thumbnailFileIds = new List<Guid>();
CollectImageFilesFromGenericGuids(regexGuidPatternNoDashes, null, extractedPageInstance.ThumbnailUrl, thumbnailFileIds);
CollectImageFilesFromGenericGuids(regexGuidPatternNoDashes, null, regexGuidPatternOptionalBrackets, extractedPageInstance.ThumbnailUrl, thumbnailFileIds);
if (thumbnailFileIds.Count == 1)
{
var file = web.GetFileById(thumbnailFileIds[0]);
Expand Down Expand Up @@ -407,7 +410,7 @@ public void ExtractClientSidePage(Web web, ProvisioningTemplate template, Provis
var untokenizedJsonControlData = controlInstance.JsonControlData;
// Tokenize the JsonControlData
controlInstance.JsonControlData = TokenizeJsonControlData(web, controlInstance.JsonControlData);
TokenizeBeforeExport(web, template, creationInfo, scope, errorneousOrNonImageFileGuids, regexGuidPattern, regexGuidPatternEncoded, regexSiteAssetUrls, controlInstance, untokenizedJsonControlData);
TokenizeBeforeExport(web, template, creationInfo, scope, errorneousOrNonImageFileGuids, regexGuidPattern, regexGuidPatternEncoded, regexGuidPatternOptionalBrackets, regexSiteAssetUrls, controlInstance, untokenizedJsonControlData);
}
// add control to section
sectionInstance.Controls.Add(controlInstance);
Expand Down Expand Up @@ -474,7 +477,7 @@ public void ExtractClientSidePage(Web web, ProvisioningTemplate template, Provis
var untokenizedJsonControlData = controlInstance.JsonControlData;
// Tokenize the JsonControlData
controlInstance.JsonControlData = TokenizeJsonControlData(web, controlInstance.JsonControlData);
TokenizeBeforeExport(web, template, creationInfo, scope, errorneousOrNonImageFileGuids, regexGuidPattern, regexGuidPatternEncoded, regexSiteAssetUrls, controlInstance, untokenizedJsonControlData);
TokenizeBeforeExport(web, template, creationInfo, scope, errorneousOrNonImageFileGuids, regexGuidPattern, regexGuidPatternEncoded, regexGuidPatternOptionalBrackets, regexSiteAssetUrls, controlInstance, untokenizedJsonControlData);
// add control to section
sectionInstance.Controls.Add(controlInstance);
}
Expand Down Expand Up @@ -536,8 +539,8 @@ public void ExtractClientSidePage(Web web, ProvisioningTemplate template, Provis
}
}

#region Helper methods
private void TokenizeBeforeExport(Web web, ProvisioningTemplate template, ProvisioningTemplateCreationInformation creationInfo, PnPMonitoredScope scope, List<string> errorneousOrNonImageFileGuids, Regex regexGuidPattern, Regex regexGuidPatternEncoded, Regex regexSiteAssetUrls, CanvasControl controlInstance, string untokenizedJsonControlData)
#region Helper methods
private void TokenizeBeforeExport(Web web, ProvisioningTemplate template, ProvisioningTemplateCreationInformation creationInfo, PnPMonitoredScope scope, List<string> errorneousOrNonImageFileGuids, Regex regexGuidPattern, Regex regexGuidPatternEncoded, Regex regexGuidPatternOptionalBrackets, Regex regexSiteAssetUrls, CanvasControl controlInstance, string untokenizedJsonControlData)
{
// Export relevant files if this flag is set
if (creationInfo.PersistBrandingFiles)
Expand All @@ -547,7 +550,7 @@ private void TokenizeBeforeExport(Web web, ProvisioningTemplate template, Provis
Dictionary<string, string> exportedPages = new Dictionary<string, string>();

CollectSiteAssetImageFiles(regexSiteAssetUrls, web, untokenizedJsonControlData, fileGuids);
CollectImageFilesFromGenericGuids(regexGuidPattern, regexGuidPatternEncoded, untokenizedJsonControlData, fileGuids);
CollectImageFilesFromGenericGuids(regexGuidPattern, regexGuidPatternEncoded, regexGuidPatternOptionalBrackets, untokenizedJsonControlData, fileGuids);

// Iterate over the found guids to see if they're exportable files
foreach (var uniqueId in fileGuids)
Expand Down Expand Up @@ -602,11 +605,12 @@ private void TokenizeBeforeExport(Web web, ProvisioningTemplate template, Provis
{
controlInstance.JsonControlData = Regex.Replace(controlInstance.JsonControlData, exportedPage.Key.Replace("-", "%2D"), $"{{pageuniqueidencoded:{exportedPage.Value}}}", RegexOptions.IgnoreCase);
controlInstance.JsonControlData = Regex.Replace(controlInstance.JsonControlData, exportedPage.Key, $"{{pageuniqueid:{exportedPage.Value}}}", RegexOptions.IgnoreCase);
controlInstance.JsonControlData = Regex.Replace(controlInstance.JsonControlData, exportedPage.Key.Replace("-", ""), $"{{pageuniqueid:{exportedPage.Value}}}", RegexOptions.IgnoreCase);
}
}
}

private static void CollectImageFilesFromGenericGuids(Regex regexGuidPattern, Regex regexGuidPatternEncoded, string jsonControlData, List<Guid> fileGuids)
private static void CollectImageFilesFromGenericGuids(Regex regexGuidPattern, Regex regexGuidPatternEncoded, Regex regexGuidPatternOptionalBrackets, string jsonControlData, List<Guid> fileGuids)
{
// grab all the guids in the already tokenized json and check try to get them as a file
if (regexGuidPattern != null)
Expand Down Expand Up @@ -638,6 +642,23 @@ private static void CollectImageFilesFromGenericGuids(Regex regexGuidPattern, Re
}
}
}
if(regexGuidPatternOptionalBrackets != null)
{
if(regexGuidPatternOptionalBrackets.IsMatch(jsonControlData))
{
foreach(Match guidMatch in regexGuidPatternOptionalBrackets.Matches(jsonControlData))
{
Guid uniqueId;
if(Guid.TryParse(guidMatch.Value, out uniqueId))
{
if (!fileGuids.Contains(uniqueId))
{
fileGuids.Add(uniqueId);
}
}
}
}
}
}

private void IncludePageHeaderImageInExport(Web web, string imageServerRelativeUrl, ProvisioningTemplate template, ProvisioningTemplateCreationInformation creationInfo, PnPMonitoredScope scope)
Expand Down Expand Up @@ -838,20 +859,43 @@ private string TokenizeJsonControlData(Web web, string json)

// HostUrl token replacement
var uri = new Uri(web.Url);
json = Regex.Replace(json, $"{uri.Scheme}://{uri.DnsSafeHost}:{uri.Port}", "{hosturl}", RegexOptions.IgnoreCase);
json = Regex.Replace(json, $"{uri.Scheme}://{uri.DnsSafeHost}", "{hosturl}", RegexOptions.IgnoreCase);

if (web.ServerRelativeUrl != "/")
{
json = Regex.Replace(json, $"{uri.Scheme}://{uri.DnsSafeHost}:{uri.Port}", $"{uri.Scheme}://{{fqdn}}", RegexOptions.IgnoreCase);
json = Regex.Replace(json, $"{uri.Scheme}://{uri.DnsSafeHost}", $"{uri.Scheme}://{{fqdn}}", RegexOptions.IgnoreCase);
json = Regex.Replace(json, $"{uri.DnsSafeHost}", "{fqdn}");
}
else
{
json = Regex.Replace(json, $"{uri.Scheme}://{uri.DnsSafeHost}:{uri.Port}", $"{uri.Scheme}://{{fqdn}}{{site}}", RegexOptions.IgnoreCase);
json = Regex.Replace(json, $"{uri.Scheme}://{uri.DnsSafeHost}", $"{uri.Scheme}://{{fqdn}}{{site}}", RegexOptions.IgnoreCase);
json = Regex.Replace(json, $"{uri.DnsSafeHost}", $"{{fqdn}}", RegexOptions.IgnoreCase);

}
// Site token replacement, also replace "encoded" guids
json = Regex.Replace(json, site.Id.ToString(), "{sitecollectionid}", RegexOptions.IgnoreCase);
json = Regex.Replace(json, site.Id.ToString().Replace("-", "%2D"), "{sitecollectionidencoded}", RegexOptions.IgnoreCase);
json = Regex.Replace(json, site.Id.ToString("N"), "{sitecollectionid}", RegexOptions.IgnoreCase);
json = Regex.Replace(json, web.Id.ToString(), "{siteid}", RegexOptions.IgnoreCase);
json = Regex.Replace(json, web.Id.ToString().Replace("-", "%2D"), "{siteidencoded}", RegexOptions.IgnoreCase);
json = Regex.Replace(json, web.Id.ToString("N"), "{siteid}", RegexOptions.IgnoreCase);
json = Regex.Replace(json, "(\"" + web.ServerRelativeUrl + ")(?!&)", "\"{site}", RegexOptions.IgnoreCase);
json = Regex.Replace(json, "'" + web.ServerRelativeUrl, "'{site}", RegexOptions.IgnoreCase);
json = Regex.Replace(json, ">" + web.ServerRelativeUrl, ">{site}", RegexOptions.IgnoreCase);
json = Regex.Replace(json, web.ServerRelativeUrl, "{site}", RegexOptions.IgnoreCase);
if (web.ServerRelativeUrl != "/")
{
// Normal site collection
json = Regex.Replace(json, "(\"" + web.ServerRelativeUrl + ")(?!&)", "\"{site}", RegexOptions.IgnoreCase);
json = Regex.Replace(json, "'" + web.ServerRelativeUrl, "'{site}", RegexOptions.IgnoreCase);
json = Regex.Replace(json, ">" + web.ServerRelativeUrl, ">{site}", RegexOptions.IgnoreCase);
json = Regex.Replace(json, web.ServerRelativeUrl, "{site}", RegexOptions.IgnoreCase);
}
else
{
// Root site collection
json = Regex.Replace(json, "(\"" + web.ServerRelativeUrl + ")(?!&)", "\"{site}/", RegexOptions.IgnoreCase);
json = Regex.Replace(json, "'" + web.ServerRelativeUrl, "'{site}/", RegexOptions.IgnoreCase);
json = Regex.Replace(json, ">" + web.ServerRelativeUrl, ">{site}/", RegexOptions.IgnoreCase);

}

// Connected Office 365 group tokenization
if (site.GroupId != null && !site.GroupId.Equals(Guid.Empty))
Expand All @@ -871,7 +915,7 @@ private string TokenizeJsonTextData(Web web, string json)

return json;
}
#endregion
#endregion
}
#endif
}
1 change: 1 addition & 0 deletions Core/OfficeDevPnP.Core/OfficeDevPnP.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,7 @@
<Compile Include="Framework\Provisioning\ObjectHandlers\TokenDefinitions\EveryoneButExternalUsersToken.cs" />
<Compile Include="Framework\Provisioning\ObjectHandlers\TokenDefinitions\FieldIdToken.cs" />
<Compile Include="Framework\Provisioning\ObjectHandlers\TokenDefinitions\GroupSiteTitleToken.cs" />
<Compile Include="Framework\Provisioning\ObjectHandlers\TokenDefinitions\FqdnToken.cs" />
<Compile Include="Framework\Provisioning\ObjectHandlers\TokenDefinitions\ListContentTypeIdToken.cs" />
<Compile Include="Framework\Provisioning\ObjectHandlers\TokenDefinitions\O365GroupIdToken.cs" />
<Compile Include="Framework\Provisioning\ObjectHandlers\TokenDefinitions\SequenceSiteGroupIdToken.cs" />
Expand Down

0 comments on commit 9688ead

Please sign in to comment.