Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] add setting site logo support for group with applications perms #1519

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions docs/using-the-sdk/branding-intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,6 @@ await chrome.Header.ResetSiteLogoThumbnailAsync();

While above methods work for any modern SharePoint site the implementation is different for group connected sites (such as a Team site) as for those sites the logo is maintained via the connected group. For group connected sites the site logo and the site logo thumbnail are the same and as such is does not matter which set or reset method you use, both result in the same code being called. Another difference is on how the provided image is stored: for group connected sites the image is stored with the Microsoft 365 group, whereas for other sites the image is first uploaded to the site assets library. Consequently there's also a difference in reset behavior: for group connected sites a reset will try to load the `__siteIcon__.jpg` file from the site assets library and set that as group image, for other site types there's no dependency on an image in site assets library.

> [!Important]
> When you're using the `SetSiteLogo` and `SetSiteLogoThumbnail` methods on a Microsoft 365 group connected site while using application permissions then you'll get an error. Currently these methods require delegated permissions when used on a group connected sites, usage on other sites is not impacted.

### Set and clear the header background image

Whenever the site header layout is set to `Extended` you can optionally set a header background image via the `SetHeaderBackgroundImage` methods. Clearing the header background can be done using the `ClearHeaderBackgroundImage` methods.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ public async Task<T> AddAsync(byte[] fileBytes, string fileName, bool overwrite
var apiCall = new ApiCall($"_api/web/{Scope}appcatalog/Add(overwrite={overwrite.ToString().ToLower()},url='{fileName}')", ApiType.SPORest)
{
Interactive = true,
BinaryBody = fileBytes,
Content = new ByteArrayContent(fileBytes),
Headers = new Dictionary<string, string>()
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Threading.Tasks;

Expand Down Expand Up @@ -74,29 +75,63 @@ public async Task SetSiteLogoThumbnailAsync(string fileName, Stream content, boo
}
else
{
Dictionary<string, string> headers;
var byteContent = new ByteArrayContent(ToByteArray(content));

if (MimeTypeMap.TryGetMimeType(fileName, out string mimeType))
{
headers = new Dictionary<string, string>
{
{ "Content-Type", mimeType }
};
byteContent.Headers.ContentType = new MediaTypeHeaderValue(mimeType);
}
else
{
throw new ClientException(ErrorType.Unsupported, PnPCoreResources.Exception_Unsupported_NotAnImageMimeType);
throw new ClientException(ErrorType.Unsupported,
PnPCoreResources.Exception_Unsupported_NotAnImageMimeType);
}

// We're setting the group logo as that serves as the site logo thumbnail
var api = new ApiCall("_api/GroupService/SetGroupImage", ApiType.SPORest)
var apiCall = new ApiCall($"groups/{PnPContext.Site.GroupId}/photo/$value", ApiType.Graph)
{
Interactive = true,
BinaryBody = ToByteArray(content),
Headers = headers
Interactive = true, Content = byteContent
};

// Set the uploaded file as site logo
await (PnPContext.Web as Web).RawRequestAsync(api, HttpMethod.Post, "SetGroupImage").ConfigureAwait(false);
// Upload the image and set it as group logo
await (PnPContext.Web as Web).RawRequestAsync(apiCall, HttpMethod.Put).ConfigureAwait(false);

// get the site url and current used site logo url to check if it is set correctly
await PnPContext.Web.EnsurePropertiesAsync(x => x.SiteLogoUrl, x => x.Url).ConfigureAwait(false);

var correctSiteLogoUrl =
$"{PnPContext.Web.Url.PathAndQuery}/_api/GroupService/GetGroupImage?id='{PnPContext.Site.GroupId}'";

// Use StartSWith to avoid issues with the query string that can contains &hash=xxxx
if (string.IsNullOrEmpty(PnPContext.Web.SiteLogoUrl) ||
!PnPContext.Web.SiteLogoUrl.StartsWith(correctSiteLogoUrl))
{
PnPContext.Web.SiteLogoUrl = correctSiteLogoUrl;
await PnPContext.Web.UpdateAsync().ConfigureAwait(false);

const string cachedSiteIcon = "__siteIcon__.png";

try
{
// check if we have a file named "__siteIcon__.jpg" in the SiteAssets folder
var file = await PnPContext.Web
.GetFileByServerRelativeUrlAsync(
$"{PnPContext.Uri.AbsolutePath}/siteassets/{cachedSiteIcon}")
.ConfigureAwait(false);

// delete the cached file to ensure the new logo is used https://learn.microsoft.com/en-us/sharepoint/troubleshoot/sites/error-when-changing-o365-site-logo
await file.DeleteAsync().ConfigureAwait(false);
}
catch (SharePointRestServiceException ex)
{
var error = ex.Error as SharePointRestError;

// If the exception indicated a non existing file then ignore, else throw
if (!File.ErrorIndicatesFileDoesNotExists(error))
{
throw;
}
}
}
}
}

Expand Down Expand Up @@ -215,4 +250,4 @@ private static byte[] ToByteArray(Stream source)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ private static async Task<Attachment> FileUpload(Attachment newFile, Stream cont
var api = new ApiCall(fileCreateRequest, ApiType.SPORest)
{
Interactive = true,
BinaryBody = ToByteArray(content)
Content = new ByteArrayContent(ToByteArray(content))
};

await newFile.RequestAsync(api, HttpMethod.Post).ConfigureAwait(false);
return newFile;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public async Task UploadImageAsync(IListItem item, string name, Stream content)
var api = new ApiCall(fileCreateRequest, ApiType.SPORest)
{
Interactive = true,
BinaryBody = ToByteArray(content),
Content = new ByteArrayContent(ToByteArray(content))
};

var response = await (item as ListItem).RawRequestAsync(api, HttpMethod.Post).ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ private static async Task<File> FileUpload(File newFile, Stream content, bool ov
var api = new ApiCall(fileCreateRequest, ApiType.SPORest)
{
Interactive = true,
BinaryBody = ToByteArray(content)
Content = new ByteArrayContent(ToByteArray(content))
};
await newFile.RequestAsync(api, HttpMethod.Post).ConfigureAwait(false);
return newFile;
Expand Down Expand Up @@ -122,7 +122,7 @@ private static async Task<File> ChunkedFileUpload(File newFile, Stream content,
api = new ApiCall(endpointUrl, ApiType.SPORest)
{
Interactive = true,
BinaryBody = chunk
Content = new ByteArrayContent(chunk)
};
await newFile.RequestAsync(api, HttpMethod.Post).ConfigureAwait(false);
firstChunk = false;
Expand All @@ -134,7 +134,7 @@ private static async Task<File> ChunkedFileUpload(File newFile, Stream content,
var api = new ApiCall(endpointUrl, ApiType.SPORest)
{
Interactive = true,
BinaryBody = chunk
Content = new ByteArrayContent(chunk)
};
await newFile.RequestAsync(api, HttpMethod.Post).ConfigureAwait(false);

Expand All @@ -146,7 +146,7 @@ private static async Task<File> ChunkedFileUpload(File newFile, Stream content,
var api = new ApiCall(endpointUrl, ApiType.SPORest)
{
Interactive = true,
BinaryBody = chunk
Content = new ByteArrayContent(chunk)
};
await newFile.RequestAsync(api, HttpMethod.Post).ConfigureAwait(false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public async Task SetMyProfilePictureAsync(byte[] fileBytes)
var apiCall = new ApiCall(baseUrl, ApiType.SPORest)
{
Interactive = true,
BinaryBody = fileBytes
Content = new ByteArrayContent(fileBytes)
};

await RawRequestAsync(apiCall, HttpMethod.Post).ConfigureAwait(false);
Expand Down
14 changes: 8 additions & 6 deletions src/sdk/PnP.Core/Services/Core/ApiCall.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net.Http;

namespace PnP.Core.Services
{
Expand All @@ -9,7 +10,8 @@ namespace PnP.Core.Services
/// </summary>
internal struct ApiCall
{
internal ApiCall(string request, ApiType apiType, string jsonBody = null, string receivingProperty = null, bool loadPages = false)
internal ApiCall(string request, ApiType apiType, string jsonBody = null, string receivingProperty = null,
bool loadPages = false)
{
Type = apiType;
Request = request;
Expand All @@ -22,7 +24,7 @@ internal ApiCall(string request, ApiType apiType, string jsonBody = null, string
RawResultsHandler = null;
Commit = false;
Interactive = false;
BinaryBody = null;
Content = null;
ExpectBinaryResponse = false;
StreamResponse = false;
RemoveFromModel = false;
Expand All @@ -46,7 +48,7 @@ internal ApiCall(List<Core.CSOM.Requests.IRequest<object>> csomRequests, string
RawResultsHandler = null;
Commit = false;
Interactive = false;
BinaryBody = null;
Content = null;
ExpectBinaryResponse = false;
StreamResponse = false;
RemoveFromModel = false;
Expand Down Expand Up @@ -117,9 +119,9 @@ internal ApiCall(List<Core.CSOM.Requests.IRequest<object>> csomRequests, string
internal bool Interactive { get; set; }

/// <summary>
/// Binary content for this API call
/// Http Content to add Binary content or other content to this API call
/// </summary>
internal byte[] BinaryBody { get; set; }
internal HttpContent Content { get; set; }

/// <summary>
/// Indicates whether the call expects a binary response
Expand Down Expand Up @@ -161,4 +163,4 @@ internal ApiCall(List<Core.CSOM.Requests.IRequest<object>> csomRequests, string
/// </summary>
internal bool AddedViaBatchMethod { get; set; }
}
}
}
2 changes: 1 addition & 1 deletion src/sdk/PnP.Core/Services/Core/Batch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ internal Guid PrepareLastAddedRequestForBatchProcessing<T>(Action<string, ApiCal

lastBatchRequest.ApiCall = new ApiCall()
{
BinaryBody = sourceApiCall.BinaryBody,
Content = sourceApiCall.Content,
Commit = sourceApiCall.Commit,
CSOMRequests = sourceApiCall.CSOMRequests,
ExecuteRequestApiCall = sourceApiCall.ExecuteRequestApiCall,
Expand Down
10 changes: 4 additions & 6 deletions src/sdk/PnP.Core/Services/Core/BatchClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -995,10 +995,9 @@ private async Task ExecuteMicrosoftGraphInteractiveAsync(Batch batch)
content.Headers.Add($"Content-Type", $"application/json");
PnPContext.Logger.LogDebug(requestBody);
}
else if (graphRequest.ApiCall.BinaryBody != null)
else if (graphRequest.ApiCall.Content != null)
{
binaryContent = new ByteArrayContent(graphRequest.ApiCall.BinaryBody);
request.Content = binaryContent;
request.Content = graphRequest.ApiCall.Content;
}

// Add extra headers
Expand Down Expand Up @@ -1876,10 +1875,9 @@ private async Task ExecuteSharePointRestInteractiveAsync(Batch batch)
content.Headers.Add($"Content-Type", $"application/json;odata=verbose");
PnPContext.Logger.LogDebug(requestBody);
}
else if (restRequest.ApiCall.BinaryBody != null)
else if (restRequest.ApiCall.Content != null)
{
binaryContent = new ByteArrayContent(restRequest.ApiCall.BinaryBody);
request.Content = binaryContent;
request.Content = restRequest.ApiCall.Content;
}

if (restRequest.ApiCall.ExpectBinaryResponse)
Expand Down
Loading