A Web Part with this ID has already been added to this page.

Within a SharePoint site that we’ve upgraded, we have an error showing when users navigate to a page :

image

Using some PowerShell, I was able to list the specific webparts on the page :

$web = Get-SPWeb http://intranet/Services/Finance 
$list = $web.Lists["Pages"] 
  
#get the page (listitem) 
$item = $list.Items[0] 
 
#get the webpart manager 
$wpm = $item.File.GetLimitedWebPartManager('Shared') 
 
$wpm.WebParts | select id, title 

And somehow, there is a duplicate GUID for a certain WebPart :

image

The next step is to remove one of those webparts from the page – following on from the PowerShell shown above :

#get the webpart using #3
$wp = $wpm.WebParts[3] 

#check which one we've got
$wp | select id, title
 
#delete from the page - and update 
$wpm.DeleteWebPart($wp) 
$item.Update()

 

That outta do it – and your users will be able to browse to the page.

NB.  You might have to checkout the page also – and then verify which webpart you need to remove – be careful !

Advertisements

Quick connect to O365 using PowerShell

I’ve been doing lots of posts about connecting to Office365, and a bunch of scripts, so I thought I’d share my easy script – just gotta update the URL, and PASSWORD.

** NB.  You need a folder with the DLL’s – this is my set of files :

image

This is the basic script – to add a reference to the necessary DLL’s (above) – and then define the user/password – and make a connection – and then load the WEB object.

$path =  'C:\o365';

$urlSite = 'https://[tenant].sharepoint.com/sites/[SITECOLLECTION]'

$user = '[USER]@[TENANT].onmicrosoft.com';
$password = 'Password1234';

[Reflection.Assembly]::LoadFile('$path\Libraries\Microsoft.SharePoint.Client.dll')
[Reflection.Assembly]::LoadFile('$path\Libraries\Microsoft.SharePoint.Client.Runtime.dll')

add-type -Path $path'\Libraries\Microsoft.SharePoint.Client.dll'
add-type -Path $path'\Libraries\Microsoft.SharePoint.Client.Runtime.dll'

$passwordSecureString = ConvertTo-SecureString -string $password -AsPlainText -Force
$credential = New-Object -TypeName System.Management.Automation.PSCredential -argumentlist $user, $passwordSecureString

$spoCtx = New-Object Microsoft.SharePoint.Client.ClientContext($urlSite)
$spoCredentials = New-Object -TypeName Microsoft.SharePoint.Client.SharePointOnlineCredentials -argumentlist $user, $passwordSecureString
$spoCtx.Credentials = $spoCredentials
$spoCtx.RequestTimeout = '500000'

$web = $spoCtx.web
$spoCtx.Load($web)
$spoCtx.ExecuteQuery();

Please let me know if that’s helpful – feel free to copy+use – I’ve used this a LOT in quick testing scripts…

🙂

SetDefaultPageLayout CSOM

As part of my new Office 365 site collection provisioning (via PowerShell), I’d like to be able to set the ‘default page layout’ :

image

BUT – within the object model for SharePoint.Client.Publishing, there are only a handful of methods for the PublishingWebClass – and so I can’t use CSOM or POWERSHELL to set the default page layout.

Click here to see on MSDN :

image

But – after a quick poke into the DLL for SharePoint.Publishing.DLL (using ILSPY), I could see that the functionality is just to add a property – which is EASY via CSOM…>!

image

image

And – sure enough, if you look using the REST endpoint, there it is :

https://tenant.sharepoint.com/sites/D001N/_api/web/AllProperties

image

It looks like the value that I need – for the Page Layout of “ArticleLeft” is :

<layout guid=”8520f570-356b-462d-8976-1e58b936c65e” url=”_catalogs/masterpage/ArticleLeft.aspx” />

Unfortunately – that GUID is different, as you move to another site collection – even for the same page layout – doh !

Another look at ILSPY shows that the GUID is the ID of the listitem :

image

So – the PowerShell needs to determine the specific item for the page layout I want – and then construct the little XML chunk – and update the property for __DefaultPageLayout.

There’s a REST call that shows these guys :

https://tenant.sharepoint.com/sites/D001N/_api/Web/GetFolderByServerRelativeUrl(‘/sites/D001N/_catalogs/masterpage’)/Files

And – you can actually grab the specific file :

https://tenant.sharepoint.com/sites/D001N/_api/Web/GetFileByServerRelativeUrl(‘/sites/D001N/_catalogs/masterpage/ArticleLeft.aspx’)?$select=UniqueId

image

But – from a POWERSHELL command line, this is what you need – once you’ve established a context to the specific site collection :

$rootWeb = $spoCtx.web
$spoCtx.Load($rootWeb)
$spoCtx.ExecuteQuery();

$spoCtx.Load($rootWeb.AllProperties)
$spoCtx.ExecuteQuery(); 

$defaultPageLayout = '_catalogs/masterpage/ArticleLeft.aspx'

#get the page item - and grab the GUID of it
$urlRelative = $rootWeb.ServerRelativeUrl + "/" + $defaultPageLayout
$pageLayoutFile = $rootWeb.GetFileByServerRelativeUrl($urlRelative) 
$spoCtx.Load($pageLayoutFile)
$spoCtx.ExecuteQuery();

$pageGuid = $pageLayoutFile.UniqueId;

#set the xmlchunk for the property
$xmlPageLayout = "<layout guid='{0}' url='{1}' />"
$xmlPageLayout = $xmlPageLayout.Replace("{0}", $pageGuid);
$xmlPageLayout = $xmlPageLayout.Replace("{1}", $defaultPageLayout);

$rootWeb.AllProperties["__DefaultPageLayout"] = $xmlPageLayout;
$rootWeb.Update()
$spoCtx.ExecuteQuery(); 

That works nicely – phew !     🙂

Add SharePoint Group to Library (Break Permissions)

I’ve had some back and forth hiccups when getting some security provisioning for a SharePoint library.

The basic premise was to break-inheritance – and only allow the “OWNERS” group to have permission (full control).

Seems easy enough – but I was getting errors like :

The collection has not been initialized. It has not been requested or the request has not been executed. It may need to be explicitly requested.

Or this one, which was annoying, and tripping me up :

Cannot add a role assignment with empty role definition binding collection.

It turns out that I was actually doing “too many” of the ExecuteQuery statements – which I didn’t actually need.   

So – I had success, using this code – and through I’d share it in case you have the same problem – or for my own future reference :

// ==================================================================
// set the OWNERS group to have permissions to the [submitted work] library
// ==================================================================
List submittedWorkLib = clientCtx.Web.Lists.GetByTitle('Submitted Work');
clientCtx.Load(submittedWorkLib);
clientCtx.ExecuteQuery();

//break security/permissions for the library
submittedWorkLib.BreakRoleInheritance(false, false);
clientCtx.Load(submittedWorkLib);
clientCtx.ExecuteQuery();

//add the owners group
string workspaceOwnersGroup = web.Title + &quot; Owners&quot;;
LogHelper.WriteToLog(log, &quot;Add the workspace owners group to library : &quot; + workspaceOwnersGroup);

clientCtx.Load(web.RoleDefinitions);
clientCtx.ExecuteQuery();

var role = web.RoleDefinitions.FirstOrDefault(r =&gt; r.Name == &quot;Full Control&quot;);
clientCtx.Load(role);
clientCtx.ExecuteQuery();

clientCtx.Load(web.SiteGroups);
clientCtx.ExecuteQuery();

var group = web.SiteGroups.FirstOrDefault(g =&gt; g.Title == workspaceOwnersGroup);
clientCtx.Load(web.SiteGroups);
clientCtx.Load(group);
clientCtx.ExecuteQuery();

var roleDefBinding = new RoleDefinitionBindingCollection(clientCtx);
roleDefBinding.Add(role);
submittedWorkLib.RoleAssignments.Add(group, roleDefBinding);

clientCtx.ExecuteQuery();

Let me know if that helps – thanks !

Other references :

403 Forbidden when deleting SPWeb (JSOM) REST call Office365

When attempting to DELETE a SharePoint sub web within some included JavaScript (as opposed to a SharePoint App), you can simply call a REST endpoint, and pass the ‘DELETE’ verb as a HTTP header.

This is using the $.ajax method of jQuery.

You just need to use the URL of the subweb you want to delete – with the “_api/web” suffix.

var urlToDelete = 'http://tenant.sharepoint.com/sites/corp/web/subweb1/_api/web';

And – to then do a call to the REST endpoint, you just construct JavaScript like this :

$.ajax({
        url: urlToDelete,
        method: 'POST',
        headers: {
            'Accept': 'application/json; odata=verbose', 
            'X-HTTP-Method': 'DELETE'
        },
        success: function (data) {
            alert('success');
        },
        error: function (err) {
            alert('fail');
        }
    }); 

BUT – and this is what caught me out – you might get a “403 Forbidden” error.    If you check using F12 developer tools – you’ll see the error.

The trick is to include the content of the page (hidden variable) – and use as another header :

$('#__REQUESTDIGEST').val()

And so – when you add it all together – you get this – and it works !    and no 403 error…

$.ajax({
        url: urlToDelete,
        method: 'POST',
        headers: {
            'Accept': 'application/json; odata=verbose', 
            'X-HTTP-Method': 'DELETE',
            'X-RequestDigest': $('#__REQUESTDIGEST').val()
        },
        success: function (data) {
            alert('success');
        },
        error: function (err) {
            alert('fail');
        }
    });

If you get a 500 error – it maybe that there are “subwebs” – and you need to delete those first.

Get SharePoint Apps for current web using JavaScript CSOM (Office 365)

We have a requirement for a right hand pane set-of-links, which is essentially a set of shortcuts to the “APPS” that are within the SITE CONTENTS page.   This will be shown on the home page of a SharePoint SPWeb, in a short list, rather than the user viewing all the big tiles, etc.

The basic steps are :

  • Get the current SPContext for the web
  • Get the AppTiles for the current web
  • Cycle through, and get the TITLE and TARGET (URL) for each tile
  • Inject the HTML in using jQuery

I simply saved this as a file called “allApps.htm” and then added a Content Editor WebPart.   There are only a few lines of HTML – just a placeholder :

image

And then, there’s a stack of JavaScript – yay !

image

Here’s what it looks like when displayed in the CEWP, inside a Page – needs some CSS magic – but it works !

image

And – if you want to use the above technique – here’s the code :

var ctxCurrent;
var ctxWeb;

//get the SharePoint context
SP.SOD.executeFunc('sp.js', 'SP.ClientContext', loadContext);

function loadContext() {
    ctxCurrent = new SP.ClientContext.get_current();
    ctxWeb = ctxCurrent.get_web();
    loadAppTiles();
}

function loadAppTiles() {
    //get the appTiles
    var appTiles = ctxWeb.get_appTiles();
    ctxCurrent.load(appTiles);

    //go through them all - and find the app for 'class workspace provisioning';
    ctxCurrent.executeQueryAsync(
        function onQuerySucceeded(sender, args) {
            if (appTiles.get_count() &gt; 0) {
                //go through the appTiles - get the TITLE and TARGET - and jQuery them into the UL
                for (var i = 0; i &lt; appTiles.get_count() ; i++) {
                    //get the tile at the &quot;i&quot; number
                    appTile = appTiles.getItemAtIndex(i);

                    //grab the title and url
                    appTitle = appTile.get_title();
                    appUrl = appTile.get_target();

                    //append to the UL - using jQuery
                    var newLi = &quot;&lt;li&gt;&lt;a href='&quot; + appUrl + &quot;' &gt;&quot; + appTitle + &quot;&lt;/a&gt;&lt;/li&gt;&quot;;
                    $jq('#currentApps ul').append(newLi);
                }
            }
        },
        function onQueryFailed(sender, args) {
            console.error('getAppTile. Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
        }
    );
}

Let me know if this was helpful – cheers !

🙂

Office365 Master Page change via jQuery injection

With the SharePoint platform in Office365, there’s one school-of-thought to NOT change the Master Page.   This will allow for any changes downstream, from Microsoft – if/when new features are to be included.

We used to include a whole stack of changes to Master Pages within the SP2010 and SP2013 environment, but need to approach this in a different way – OR – miss out on the new changes/updates.

Anyway, we’re going with that approach – NO changes to Master Page.

And so, the requirement I have is to include a ‘footer’ on each page – which would traditionally be included in a Master Page.   

How do I do it ??

The approach I’m now taking is to use some ‘JavaScript+HTML’ injection – provisioned using remote PowerShell – and CSOM.

The main steps are :

  • Connect to SharePoint online – using credentials – and establish a ‘context’
  • Upload the files needed – jQuery and custom code JS (and CSS if needed)
  • Include a CustomAction that registers a ScriptBlock – in the Master Page (startup.js)
  • Within the startup.js, reference my other code libraries (JS files)

Connect to SharePoint Online

$urlAdmin = https://YOUR.TENANT-admin.sharepoint.com
$user = “administrator@YOUR.TENANT.onmicrosoft.com”
$password = “YOUR.TENANT.PASSWORD”
$urlSite =
https://YOUR.TENANT.sharepoint.com/sites/TestSiteCollection 

$passwordSecureString = ConvertTo-SecureString -string $password -AsPlainText -Force
$credential = New-Object -TypeName System.Management.Automation.PSCredential -argumentlist $user, $passwordSecureString

Connect-SPOService -Url $urlAdmin -Credential $credential

$spoCredentials = New-Object -TypeName Microsoft.SharePoint.Client.SharePointOnlineCredentials -argumentlist $user, $passwordSecureString

$spoCtx = New-Object Microsoft.SharePoint.Client.ClientContext($urlSite)
$spoCtx.Credentials = $spoCredentials
$spoCtx.RequestTimeout = “500000”

Upload the files

Ignoring the CSS for now, I have TWO files to upload to my SharePoint site – these will just land in the SiteAssets library – or you could use StyleLibrary – either is OK.

  • jquery-2.1.3.min.js     (could use a CDN URL I guess)
  • CLIENTNAME.O365.startup

(Not including the PowerShell to upload files – that’s a topic for another blog post)

Include a CustomAction

This next bit of PowerShell will register a <SCRIPTBLOCK> within the page, and thereby, inject my specific JavaScript/jQuery/etc.

$newAction = $spoCtx.Site.UserCustomActions.Add();
$newAction.Location = “ScriptLink”;
$newAction.scriptSrc = “~SiteCollection/SiteAssets/scripts/jquery-2.1.3.min.js“;
$newAction.Sequence = 1001;
$newAction.Update();
$spoCtx.ExecuteQuery();

$newAction = $spoCtx.Site.UserCustomActions.Add();
$newAction.Location = “ScriptLink”;
$newAction.scriptSrc = “~SiteCollection/SiteAssets/scripts/CLIENTNAME.O365.startup.js“;
$newAction.Sequence = 1002;
$newAction.Update();
$spoCtx.ExecuteQuery();

Here you can see that my JS’s are now referenced – and thus it’s worked (the above steps).

image

Reference other JS files

Within my ‘startup.js’, I can now call out to other JavaScript files – one in particular is to update a FOOTER on the site collection.   

I’ve ALSO chosen to centralize these files – so that I can change it ONCE and all site collections are updated – nice !

Switching over to JavaScript now, this is the code within the “startup.js” :

var $jq = jQuery.noConflict();

// load the SHARED scripts from central location
$jq.getScript(‘/siteassets/BRANDING/scripts/jquery-ui-1.11.4.min.js’);

$jq.getScript(‘/siteassets/BRANDING/scripts/CLIENTNAME.O365.footer.js’);

Update the footer – via JS

I can now do whatever I like with the HTML markup – and style accordingly via CSS.    I’m only showing part of the footer code here – it’s up to YOU what you need.

$jq(document).ready(function () {

    //load the footer contents
    var footerHtml = “<div id=’ClientFooter’ class=’ms-dialogHidden’>”;
    footerHtml = footerHtml + “<div class=’footerSection1′>”;

    footerHtml = footerHtml + “<div class=’text’>”;
    footerHtml = footerHtml + “<a href=’
http://www.homepage.com.au’><img alt=’Logo’ src=’/SiteAssets/BRANDING/Images/logo.png’ /></a>”;
    footerHtml = footerHtml + “</div>”;

    footerHtml = footerHtml + “<ul>”;
    footerHtml = footerHtml + “<li><a href=’
https://www.homepage.com.au/’>AAAAAA</a></li>”;
    footerHtml = footerHtml + “<li><a href=’www.homepage.com.au/pages/BBBBBB.aspx’>BBBBBB</a></li>”;
    footerHtml = footerHtml + “</ul>”;

    footerHtml = footerHtml + “<div class=’footerSection2′>”;
        footerHtml = footerHtml + “<div class=’text’>”;
            footerHtml = footerHtml + “<h3>Contact Us</h3>”;
            footerHtml = footerHtml + “<p>If you would like to email us :</p>”;
            footerHtml = footerHtml + “<ul>”;
            footerHtml = footerHtml + “<li><a id=’contact’ onclick=’loadEmailForm();’ href=’#’>Click here to send email</a></li>”;
            footerHtml = footerHtml + “</ul>”;
        footerHtml = footerHtml + “</div>”;
    footerHtml = footerHtml + “</div>”;

   $jq(‘#s4-workspace’).append(footerHtml);

});

It’s essentially just “build a string” – and then use jQuery to inject at the bottom of the page (#s4-workspace).

After doing a bunch of CSS and HTML – it can look really nice !

Conclusion

So – there you have it – a fairly simple approach to make ‘page changes’ without messing with the Master Page.

Unless Microsoft ditch the “s4-workspace” DIV, my code is fairly safe – and it works….

Let me know your thoughts on this approach – it’s been a interesting one to develop.

🙂

Content Organizer Rule (CSOM/O365)

Our client is using Office365, and we’re provisioning new sites using an Azure WebJob, which will do the necessary configuration.

One aspect they want is to do the “Content Organizer” rule.   This will mean people can upload to a document library (DropOff Lib), and it will be re-routed.  

This is for school students to submit assignment work.

After looking at CSOM code, and searching on Google for stuff like “EcmDocumentRouterRule”, I had a brain wave to just do it SIMPLE – for my fairly simple rule.

The Content Organizer from SiteSettings is just a SharePoint list – and ListItems are added.  So – looking at the REST API – there are simply a bunch of properties ;

https://TENANT.sharepoint.com/sites/SearchTest1/_api/web/lists/RoutingRules/Items(1)

image

So – it follows that I simply need to create a listitem – and it just works !

image

** NB.  PRIOR TO THIS, need to make sure you have turned ON the feature :

// Load the features
FeatureCollection webFeatures = clientCtx.Web.Features;
clientCtx.Load(webFeatures);
clientCtx.ExecuteQuery();

//turn on site feature for Content Organizer, will create drop off library
webFeatures.Add(new Guid(“7ad5272a-2694-4349-953e-ea5ef290e97c”), false, FeatureDefinitionScope.None);
clientCtx.Load(webFeatures);
clientCtx.ExecuteQuery();

Here’s the code, ready to copy+paste :

//only way to do within CSOM is to add the necessary listitem to “ROUTINGRULES” list  
List routingRulesList = clientCtx.Web.Lists.GetByTitle(“Content Organizer Rules”);
clientCtx.Load(routingRulesList);
clientCtx.ExecuteQuery();

//get the current url, to use when defining target for rule
var web = clientCtx.Web;
clientCtx.Load(web);
clientCtx.ExecuteQuery();

var currentUrl = web.Url;
string targetLibraryUrl = currentUrl + “/SubmittedWork”;

ListItemCreationInformation routingRuleInfo = new ListItemCreationInformation();
ListItem routingRule = routingRulesList.AddItem(routingRuleInfo);
routingRule[“Title”] = “Move to Submitted library”;
routingRule[“RoutingRuleName”] = “Move to Submitted library”;
routingRule[“RoutingPriority”] = 1;
routingRule[“RoutingEnabled”] = true;
routingRule[“RoutingContentType”] = “Document”;
routingRule[“RoutingConditionProperties”] = “Content Type”;
routingRule[“RoutingTargetLibrary”] = “Submitted Work”;
routingRule[“RoutingTargetPath”] = targetLibraryUrl;
routingRule[“RoutingRuleExternal”] = false;
routingRule[“RoutingAliases”] = null;
routingRule[“RoutingRuleDescription”] = null;
routingRule[“RoutingTargetFolder”] = null;
routingRule[“RoutingAutoFolderProp”] = null;
routingRule[“RoutingCustomRouter”] = null;

routingRule.Update();
clientCtx.ExecuteQuery();
 

Hopefully that helps with your Office365 configuration – good luck !

🙂

SharePoint page showing BLANK

I have an Office 365 site collection that I’ve been provisioning from within PowerShell – and automatically uploading webparts.   I’m getting some weird behaviour though :

image

When looking at VIEW SOURCE – it’s even stranger – that’s the ENTIRE page !

image

Even when looking at the page request/s via FIDDLER – nothing seemed off.   And nothing in the console or error log in the F12 developer tools.

I could view the page with the ?contents=1 appended to the URL – but still no answer.

image

I worked out that it’s actually a SEARCH webpart that is causing the issue – but I couldn’t work out WHY…

This was my PowerShell code to upload the webpart to the page – some other O365 guff around this – but this is the basic code (using CSOM) :

$wpm = $pubFile.GetLimitedWebPartManager([Microsoft.SharePoint.Client.WebParts.PersonalizationScope]::Shared);       
$wpcoll = $wpm.WebParts
$spoCtx.Load($wpcoll)
$spoCtx.ExecuteQuery();

$webPartFileContents = [System.IO.File]::ReadAllText($filepath);
$wpd = $wpm.ImportWebPart($webPartFileContents);

$wpdNew = $wpm.AddWebPart($wpd.WebPart, $xmlwp.ZoneID, $webPartCount);
$spoCtx.Load($wpdNew);
$spoCtx.ExecuteQuery();

Within the .WEBPART file, there’s the usual XML bits & pieces – and a bunch of properties.

Resolution

After lots of blog post hunting – off/on for a few days actually – and re-building the WebPart contents piece-by-piece, I noticed it was breaking on properties like this >

<property name=”RenderTemplateId” type=”string”>~sitecollection/_catalogs/masterpage/Display Templates/Search/Control_SearchResults.js</property>

It was THIS post from Chris O’Brien that made me hunt down this path :

http://www.sharepointnutsandbolts.com/2013/04/provisioning-content-search-web-part.html

..but then the page does not load (blank screen, HTML not output properly) and ULS shows the following runtime error:

Application error when access /SitePages/CSWP_Provisioned.aspx, Error=Cannot make a cache safe URL for “item_picture3lines_cob_provisioned.js”, file not found. Please verify that the file exists under the layouts directory.
at Microsoft.SharePoint.Utilities.SPUtility.MakeBrowserCacheSafeLayoutsUrl(String name, Boolean localizable, Int32 desiredVersion)   

I couldn’t check the log files – with Office 365 – but I think it’s the same issue.

My next thing to try was to change the markup within my .WEBPART – and I had success !

image

Changed to :

image

Just needed to change from ~ to the equivalent in encoding ~

I’m unsure exactly WHY this is occurring – perhaps the PowerShell upload is losing this and/or encoding it wrong – but it worked !

Hopefully that helps someone – or at least, this post is a reference for ME, if it ever happens AGAIN !

Office 365 send email using JavaScript (REST)

I have a fairly simple requirement for some JavaScript (jQuery) to send an email.   This is within the code for a SharePoint Online page (Office 365).

My initial thinking was to do a HTTP POST request to the Office 365 REST endpoint, but nothing could have prepared me for the next few hours of my day.

I had to jump through a few hoops, and perform backflips while juggling – and THEN changed tack entirely.   I thought I’d share my observations – and some sample code/link.

This was my starting point (MSDN) :    Send a new message on the fly (REST)

I wrangled with it for a few hours :

  • SharePoint hosted code (JS), calling to https://outlook.office365.com was resulting in a 401 – due to a X-Domain call.
  • HTTP POST with JSONP doesn’t work – only for GET
  • CORS is what was messing it up – 401’ing the request
  • Even trialling with the new http://graph.microsoft.com – same problem
  • Suggestions via Twitter were to do an Azure AD app – TO SEND AN EMAIL !!
  • I even found one ridiculous solution that was to create workflow from Visual Studio or SharePoint Designer – and then CALL it from CSOM.    #WTF !!

A colleague mentioned “surely SharePoint has something to send an email” – it could then process the email traffic SERVER SIDE.   

That was an unholy yee-haar gold nugget of joy.

Posting from my jQuery back to SharePoint Online is a simple REST call.   And, it does NOT suffer from X-Domain or CORS.    

http://tenant/_api/SP.Utilities.Utility.SendEmail

There’s a blog post that I found, with a great code sample – and it’s now working for me :

Sending email with SharePoint and jQuery

Just note that the recipient is limited to a valid SharePoint user for security reasons.    (within the same tenant)

So – it’s the simply “KISS” principle – keep it simple, stupid.   

😉