Monday, July 27, 2015

Create default associated groups for Sharepoint site collection via client object model (CSOM)

When you create site collection via CSOM default Sharepoint groups (Visitors, Members and Owners) are not created by default. So when you will go to Site settings > People and groups most probably you will only see Everyone and Excel Services Viewers:

image

In order to create these groups you need to write additional code by yourself. Here it is:

   1: using (var ctx = new ClientContext("http://example.com"))
   2: {
   3:     ctx.Load(ctx.Web);
   4:     ctx.ExecuteQuery();
   5:     CreateDefaultAssociatedGroups(ctx);
   6: }
   7:  
   8: private static void CreateDefaultAssociatedGroups(ClientContext ctx)
   9: {
  10:     EnsureDefaultAssociatedGroup(ctx, GetGroupName(ctx, "Visitors"),
  11:         String.Format("Use this group to grant people read permissions to the SharePoint site: {0}",
  12:             ctx.Web.Title));
  13:     EnsureDefaultAssociatedGroup(ctx, GetGroupName(ctx, "Members"),
  14:         String.Format("Use this group to grant people contribute permissions to the SharePoint site: {0}",
  15:             ctx.Web.Title));
  16:     EnsureDefaultAssociatedGroup(ctx, GetGroupName(ctx, "Owners"),
  17:         String.Format("Use this group to grant people full control permissions to the SharePoint site: {0}",
  18:             ctx.Web.Title));
  19: }
  20:  
  21: private static void EnsureDefaultAssociatedGroup(ClientContext ctx, string groupName,
  22:     string description)
  23: {
  24:     if (string.IsNullOrEmpty(groupName))
  25:     {
  26:         return;
  27:     }
  28:  
  29:     if (GroupExist(ctx, groupName))
  30:     {
  31:         return;
  32:     }
  33:     var ci = new GroupCreationInformation
  34:     {
  35:         Title = groupName,
  36:         Description = description
  37:     };
  38:     var group = ctx.Web.SiteGroups.Add(ci);
  39:     var roleType = RoleType.None;
  40:     if (groupName.EndsWith("Visitors"))
  41:     {
  42:         ctx.Web.AssociatedVisitorGroup = group;
  43:         ctx.Web.AssociatedVisitorGroup.Update();
  44:         roleType = RoleType.Reader;
  45:     }
  46:     else if (groupName.EndsWith("Members"))
  47:     {
  48:         ctx.Web.AssociatedMemberGroup = group;
  49:         ctx.Web.AssociatedMemberGroup.Update();
  50:         roleType = RoleType.Contributor;
  51:     }
  52:     else if (groupName.EndsWith("Owners"))
  53:     {
  54:         ctx.Web.AssociatedOwnerGroup = group;
  55:         ctx.Web.AssociatedOwnerGroup.Update();
  56:         roleType = RoleType.Administrator;
  57:     }
  58:  
  59:     ctx.Web.Update();
  60:     ctx.ExecuteQuery();
  61:  
  62:     if (roleType != RoleType.None)
  63:     {
  64:         var roleDefinition = ctx.Web.RoleDefinitions.GetByType(roleType);
  65:         ctx.Load(roleDefinition);
  66:         ctx.ExecuteQuery();
  67:  
  68:         var roleDefinitions = new RoleDefinitionBindingCollection(ctx);
  69:         roleDefinitions.Add(roleDefinition);
  70:         ctx.Web.RoleAssignments.Add(group, roleDefinitions);
  71:         ctx.ExecuteQuery();
  72:     }
  73: }
  74:  
  75: private static string GetGroupName(ClientContext ctx, string suffix)
  76: {
  77:     return String.Format("{0} {1}", ctx.Web.Title, suffix);
  78: }
  79:  
  80: private static bool GroupExist(ClientContext ctx, string groupName)
  81: {
  82:     var groups = ctx.Web.SiteGroups;
  83:     ctx.Load(groups);
  84:     ctx.ExecuteQuery();
  85:     foreach (var g in groups)
  86:     {
  87:         if (g.Title == groupName)
  88:         {
  89:             return true;
  90:         }
  91:     }
  92:     return false;
  93: }

After executing this code you will see 3 additional groups in Site settings > People and groups:

image

If you will go to Site settings > Site permissions you will find that Visitors group has Read permissions on the site, Members – Contribute and Owners – Full control:

image

Hope that this information will help you.

Fix problem with empty page layouts when edit page in Sharepoint

When you edit publishing page in Sharepoint and click Page layouts button on the ribbon you may face with the following problem: list with available page layouts won’t be opened. Instead the only thing which you will see is popup message “Apply a new layout to this page”.

One of possible reason may be the following. If you will open browser console at developer tools and click on the ribbon button again, the following error will be shown:

Unable to get property nodeName of undefined or null reference

In this case go to Site settings > Page layouts and site templates. Here in Page layouts section instead of “Pages inherit preferred layouts from parent site” select “Pages in this site can only use the following layouts”. In the left list of layouts select all page layouts except those which start with “(Invalid Associated Content Type)”. Also check that in the right list of selected layouts there are no such page layouts. After that click Save. If you now go to the publishing page, refresh it and edit you will find that list of available page layouts is now shown.

Another solution which may be useful if you need to allow using of all page layouts is to check all page layouts which have “(Invalid Associated Content Type)” prefix, then check their urls (it can be done by inspecting left and right lists of page layouts in Site settings > Page layouts and site templates in developer tools: values of options elements of these lists contain urls of the pages in /_catalogs/masterpage so you will easily find them), then go to Site settings > Master pages and page layouts, find appropriate page layouts and change their content type to the correct ones.

Wednesday, July 22, 2015

Problem with X-Frame-Options: SAMEORIGIN in Yammer embedded feed

When you add Yammer embed feed to your Sharepoint Online site you may have the following problem: if user didn’t complete Yammer signup then instead of the feed he or she will see empty box inside iframe. To be more precise you may face with this problem if your site is running on any other platform or technology, but as I faced with it on Sharepoint Online, I decided to mentioned it here.

Let’s see it in more details. Embedded feed is loaded using the following code:

   1: <script src="https://c64.assets-yammer.com/assets/platform_embed.js"
   2: type="text/javascript"></script>
   1:  
   2: <div id="embedded-feed" style="height:800px;"></div>
   3: <script type="text/javascript">
   4:     yam.connect.embedFeed({ container: '#embedded-feed', network: 'example.com'});
</script>

When user has active Yammer profile it shows the Yammer feed in iframe which is added into div with specified identifier. However when user doesn’t have profile yet, empty page will be shown. The reason of this problem is the following: when user didn’t complete Yammer signup and iframe loads feed page which URL looks like this:

https://www.yammer.com/platform_embed/feed?container=%23embedded-feed&network=example.com&network_permalink=example.com&bust=…

(instead of example.com you will have your domain here and bust will have identifier which corresponds to the current user), it redirects user to the following page:

https://www.yammer.com/example.com/signup/complete_signup?activation_code=…&user_id=…

One of the HTTP headers returned in response for this page is the following:

X-Frame-Options: SAMEORIGIN

This header tells to the browser that remote site can’t be loaded in iframe if parent page belongs to different domain (different from yammer.com).

In order to tell user about that he or she should complete signup first the following script may be used:

   1:  
   2: var g_YammerCompleteSignup_CompletePolling = false;
   3: function YammerCompleteSignup(){
   4:     if (jQuery("iframe#embed-feed").length) {
   5:         try {
   6:             var url = jQuery("iframe#embed-feed").attr("src");
   7: jQuery("#embedded-feed").prepend("<span style='background: url(/_layouts/images/info16by16.gif) " +
   8:     "no-repeat; display: inline-block; background-size: 16px 16px; height: 16px; " +
   9:     "padding-left: 18px;'><span style='font-size: 12px; height: 16px; display: table-cell; " +
  10:     "vertical-align: middle;'>Yammer feed will be empty if you don't have Yammer account yet. " +
  11:     "Click <a href='" + url + "' target='_blank'>here</a> in order to complete signup and then " +
  12:     "refresh this page.</span></span>");
  13:         } finally {
  14:             g_YammerCompleteSignup_CompletePolling= true;
  15:         }
  16:     }
  17:     
  18:     if (g_YammerCompleteSignup_CompletePolling) {
  19:         return;
  20:     }
  21:  
  22:     setTimeout(YammerCompleteSignup, 2000);
  23: }

It starts polling the DOM and when iframe is added by yam.connect.embedFeed() call, it reads its src and uses it when add informational notice above the iframe, which looks like this:

image

The drawback of this approach is that this text will be shown both for users which already completed signup and for users which didn’t do that.