Software Programming

Kunuk Nykjaer

React ES6 getting Started – slides

leave a comment »

I made a slide about React ES6 getting started, you can view it here

https://doсs.google.com/presentation/d/19YgTFjvgsuTIfC7NdX8QXyf43U4LKjf8xZM2342_sO8/

Written by kunuk Nykjaer

August 22, 2016 at 10:06 am

Posted in Development, Javascript

Tagged with

Motion Design – slides

leave a comment »

I made a slide about Motion Design – you can view it here

https://doсs.google.com/presentation/d/1xycVCpGnVgz1VAnO0iaXDP7bc8K8bfl3JXZLiDvkDws/

Written by kunuk Nykjaer

August 22, 2016 at 10:03 am

Posted in Css, Design, Javascript

Tagged with

Face flipping

leave a comment »

I found this image and wanted to have fun with it. This is tested with Chrome and Edge. The person can look like two different persons by flipping half of the face.

Face flipping ver 2

 

Face flipping

Written by kunuk Nykjaer

May 27, 2016 at 10:41 am

Posted in Uncategorized

Favorite material design button with or without JS

leave a comment »

I am having fun creating things at Codepen.io.

I have created a toggle button with JS and without using JS.
For the non-JS version I used the radio-button hack technique with stacked radio buttons.

Without JS

 

With JS

Written by kunuk Nykjaer

March 10, 2016 at 4:57 pm

Posted in Css, Javascript, Visualization

Tagged with

2015 in review

leave a comment »

The WordPress.com stats helper monkeys prepared a 2015 annual report for this blog.

Here’s an excerpt:

The concert hall at the Sydney Opera House holds 2,700 people. This blog was viewed about 28,000 times in 2015. If it were a concert at Sydney Opera House, it would take about 10 sold-out performances for that many people to see it.

Click here to see the complete report.

Written by kunuk Nykjaer

December 31, 2015 at 12:13 am

Posted in Uncategorized

Code Challenge by the Trustpilot Development Team

with 6 comments

On the news

I heard Trustpilot got a huge funding.

I stumbled on their website to get some Idea what kind of company they were and to get some inspiration of how they build their website.

Then I saw they were looking for new developers and decided to see what they were looking for to get an idea of how the company operate, the tech behind the company.

They have an anagram puzzle which must be solved in order to apply for a job.
Wow, that is a very interesting concept.

Disclaimer

I will not give any spoiler, the secret phrase will not be given.

Code challenge

A programming task to test that the candidate is able to fit the job description.
The designed the assignment webpage in the Matrix movie theme.

They also have one for a frontend developer
http://followthewhiterabbit.trustpilot.com/fe/index.html

That challenge is quickly solved if you know some frontend, but in the end, they want you to create a Trustpilot widget (html component) and submit that along with your resume.
Great concept. They want to test that you can solve a realistic assignment before you are called in for an interview.


This one is for a backend developer.
http://followthewhiterabbit.trustpilot.com/cs/step3.html

You’ve made an important decision. Now, let’s get to the matter.

We have a message for you. But we hid it.
Unless you know the secret phrase, it will remain hidden.

Can you write the algorithm to find it?

Here is a couple of important hints to help you out:
– An anagram of the phrase is: “poultry outwits ants”
– The MD5 hash of the secret phrase is “4624d200580677270a54ccff86b9610e”
Here is a list of english words, it should help you out.

Type the secret phrase here to see if you found the right one
Trustpilot Development Team


The list of English words contains 99175 words.
When you type in the secret phase, the phase is translated to a SHA256 hash key which forms a part of the url which contains information of how to apply.

Ok I get it. Hash is for verification. I cannot derive the anagram words out of the MD5 hash value.
The SHA256 hash function generates a cryptic hash value, which forms part of a url. That means I cannot guess the url result.


Anagram puzzle illustrated with Legos

The Lego pieces represent the letters. The way the Lego’s are arranged represent the words.

This Lego (poultry outwits ants)
lego

Is an anagram of this Lego (the sentence we are searching for)
lego

Because they have the same amount of piece types and colors
lego

This represents a part of the sentence, an English word from the list
lego

This represents a part of the sentence, an English word from the list
lego

This represents a part of the sentence, an English word from the list
lego

They form the phrase we are looking for when taken together and arranged in a specific way.
lego

We can build Lego buildings by combining items from the list.
We know we have found the correct Lego building (secret phrase) when the MD5 function gives the provided value.


Easy solution?

This puzzle made me curious and I wanted to see if there was a ‘quick win’.
By looking at the phrase “poultry outwits ants” I get a feeling there is trustpilot word in there.
After subtracting trustpilot I got left “y ouw ants”. Yah the phrase must be “trustpilot wants you”

I type that in and expected the ‘quick win’.

To my surprise I get this page.


Yes! That is indeed an anagram of the text, you got the drift.

But you were just guessing. It is not the magic phrase.

There is no way around it. Create an algorithm to solve it for real!

Go back, and try again!


Huh?! They expected I would guess that. Cool, respect to the Trustpilot dev team to create such a puzzle that anticipated my move.
All right, well played Trustpilot dev team, well played. Game on, I take the challenge, I want to solve this.

I tried other moves to get my ‘quick win’.

I typed 4624d200580677270a54ccff86b9610e.html as part of the url which is MD5 hash which I hoped would be same as the expected url but got.

Nope 🙂

In hindsight, kinda weak try by me because SHA256 and MD5 don’t produce the same result.


They were also looking for a frontend developer and had a task which I solved and noticed it had /fe/ as part of the solution url.
I tried to substitute the url with /be/ but alas I got.

Nope 🙂


I tried to lookup the MD5 hash on md5 cracker online but no luck, no result.

Ok no quick win for me.
I have to solve this the ‘correct’ way if I want to see the result page.

This is a well-designed and fun programming challenge with the Matrix theme.
It is clearly that they have put some time and effort in building these coding challenges.
Text animations, well-thought concept, fonts and colors.

Kudos to the Trustpilot dev team.


Analysis

"poultry outwits ants"
and
"trustpilot wants you"

Have three words.
And with the two spaces in the anagram I suspect it must be 3 words from the english word list.

I sure hope so, else I suspect that this could be a subset sum problem.
If it could be any 1 to N words from the list which satisfies the anagram condition then I suspect that would be a NP-complete problem and game over for me.
Not feasible for me to solve with my knowledge and with given dataset.

Not likely to be the case as it would be like to give a challenge which is impossible to solve.
Like this XKCD 🙂 http://xkcd.com/287/

The way it is phrased in the assignment and by the anagram example suggest that it is three words from the English list in a specific order.


Goal

So the goal is clear. Find 3 words from the list in a specific order which gives the MD5 hash value "4624d200580677270a54ccff86b9610e".

Brute forcing the list with ~ 100.000 words is not feasible in a reasonable time.

Strategy

Minimize the list and filter words out which cannot be part of the solution, then when the list is small enough, do brute force for a solution.

One of the ways to test whether a is an anagram of a' is to sort and compare them.
When the list is small enough, we take three words from the list, combine the words and sort them.

Taking all possible 3 words from a list has the running time O(n^3) where n is the list length.
That means with the strategy the list must be reduced from the ~100.000 items to about in few thousands at most to get a running time within few minutes at most.

If the sorted value is the same as the sorted anagram solution, we have a 3-word candidate for a solution.
The 3-word are arranged in the possible 6-ways they can be combined and a MD5 hash is calculated. If the calculated hash value is "4624d200580677270a54ccff86b9610e", then we have found the 3-word sentence we were looking for.

Result

After coding for a while, I managed to filter and reduce the list to 1659 items.
I did the brute forcing and one matching candidate appeared after the algorithm completed in about 1 minute.

I typed in the solution.
The page navigated me to the congratulations page.

Congratulations! You found the secret phrase. Are you the One?! 😉

You’re curious and you’re up for a challenge! So we like you already.

Well, that was fun. Thanks for fun puzzles Trustpilot 🙂

Written by kunuk Nykjaer

June 2, 2015 at 8:40 am

Posted in Algorithm

Tagged with ,

Sitecore MVC walk-through of refactoring a static website

leave a comment »

About

I will give an example of refactoring a static responsive website into an existing web management system (WMS) or a content management system (CMS).
More specifically this is an example of how a static webpage can be extracted into a Sitecore MVC system.

Summary

The static website is a one-page website where content and presentation are hard-coded into one big html file.

The page is refactored to different sections of views and the content is extracted into the WMS as data sources for the corresponding views.
The result is a one Layout, which has sections of views with their data sources.

Examples of static and dynamic view binding has been provided, examples of how to use shared data sources and how to code for unknown N items to be rendered.

Disclaimer

To understand this post you properly have to know some level of Sitecore.
There are many ways to structure and place the templates and data. This example is just one approach of many possibilities.

Some might argue that a better best practice is to use custom view-models, mapper tool/approach like Glass mapper, alternatives to used navigation data source setup.
In a real bigger project that might be the case, but for this task I chose the simple and faster approach.

Visual studio files and Sitecore package

All the created items and files for this refactoring task are available at.
https://github.com/kunukn/Sitecore/tree/master/startbootstrap-creative-example/Sitecore8

If you want to play with the result:

  • Install the Sitecore package
  • Add the VS files to your Sitecore project and deploy

Sitecore

This is a commercial WMS based on the .Net stack. Sitecore MVC is based on Asp.net MVC.
Sitecore is considered as one of the leading WMS according Gartner and Forrester Research.

Creative Bootstrap theme

License & Author Details:
Apache 2.0 by Start Bootstrap
http://startbootstrap.com/template-overviews/creative/
https://github.com/IronSummitMedia/startbootstrap-creative

Live preview of the static markup can be seen here
http://ironsummitmedia.github.io/startbootstrap-creative/

The webpage is responsive and the layouts adapts to different screen sizes.
The page has a navigation on the top with the following navigation items:
about, services, portfolio, contact.
When you click on the navigation item, it scrolls to the section of the page.

Here is the bird’s eye layout structure of the webpage.

bootstrap theme

We have a hero and a download section that is not part of the navigation.

Analysis and strategy

As it is a one-page website, we will only use one Layout. The different sections of the page will be extracted to separate views.

There are no business logic in this example.
That makes the re-factoring all about moving content inside the Sitecore WMS.

I will only use View Renderings to keep things simple. Controller Renderings are not needed for this re-factoring task. I will only use the model RenderingModel. That means no custom view-models. Sometimes the code will be cleaner when using custom view-models, but I want to keep things as simple as possible for this example. I will instead create a helper method to make the code in the view more DRY.

The location of the files will be centralized as much as possible to make it easier to maintain and to isolate it from other Sitecore stuff.

For the Visual studio (VS) project.
All the assets files will be moved to the folder /assets/start-bootstrap
All the layout and views will be created in the folder /Views/start-bootstrap

visual studio


 

My Sitecore installation runs at http://sitecore8.local
The url of the page will be at http://sitecore8.local/start-bootstrap

The naming strategy is to use meaningful names and to align the names in the VS project with the Sitecore items.

For the Sitecore content setup.
All the content will be at sitecore/Content/Home/start-bootstrap
The layout file will be at sitecore/Layout/Layouts/start-bootstrap
The renderings files will be inside sitecore/Layout/Renderings/start-bootstrap
All the templates will be inside the folder sitecore/Templates/start-bootrap

sitecore

The approach

The approach is relatively trivial.

  • The first step is to get the static website to work in Sitecore.
  • The following steps is to gradually re-factor the code until the content and presentation are separated into to logically allocated views and where the fields are editable in edit mode.

This is the steps I am going to follow.

strategy


 

Step 1.

Get it to work with Sitecore.

Create the layout file start-bootstrap.cshtml in Visual studio and copy the content from index.html into the cshtml file.
Update the resource path to start from ~/assets/start-bootstrap

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="">

    <title>Creative - Start Bootstrap Theme</title>

    <!-- Bootstrap Core CSS -->
    <link rel="stylesheet" href="~/assets/css/bootstrap.min.css">
...

start-bootstrap.cshtml

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<meta name="description" content="">
	<meta name="author" content="">

	<title>Creative - Start Bootstrap Theme</title>

	<!-- Bootstrap Core CSS -->
	<link rel="stylesheet" href="~/assets/start-bootstrap/css/bootstrap.min.css">
...


 

In Sitecore I create a start-bootstrap item below the Home item with a basic template (no fields for now).
I create a Layout item called start-bootstrap with path to /Views/start-bootstrap/start-bootstrap.cshtml.
The start-bootstrap item is set to have the created layout.

Now I am ready to test that it works in Sitecore.
I start my browser at http://sitecore8.local/start-bootstrap?sc_mode=normal and check the console for errors.

It complained about woff2 extension types so I added this in the web.config and re-published the website and the errors went away.
This is because my IIS has to know it is allowed to run those file extension types.

  <system.webServer>
    <staticContent>		
      <mimeMap fileExtension="woff" mimeType="application/font-woff"/>
      <mimeMap fileExtension=".woff2" mimeType="application/font-woff2"/>    
    </staticContent>
  </system.webServer>

I tested re-sizing the screen and navigating around. Seems to work. So far so good.
What I have a static website inside Sitecore that works.


 

Step 2

The next step is to re-factor the start-bootstrap.cshtml file in to multiple views.

What I would like to have is partial views for each section in the webpage and separate the content into data sources. I will wait with the data sources until step 3.

The following views are created.
navigation, hero, about, services, portfolio, download, contact.

The navigation and hero will be statically bound because those sections have fixed positions for the webpage. Navigation is always at the top and the hero section is the landing page.
The remaining sections will be dynamically bound which means the section order can be changed.


 

A static binding with a data source looks like this.

@Html.Sitecore().ViewRendering("path-to-view-file.cshtml",
		new { DataSource = "path-to-sitecore-data-source-item" })


 

A dynamic binding looks like this

@Html.Sitecore().Placeholder("placeholder-name")


 

index.html

...
<body id="page-top">

<!-- navigation -->
<nav id="mainNav" class="navbar navbar-default navbar-fixed-top">...</nav>

<!-- hero section -->
<header>...</header>

<!-- about section -->
<section class="bg-primary" id="about">...</section>

<!-- services section -->
<section id="services">...</section>

<!-- portfolio section -->
<section class="no-padding" id="portfolio">...</section>

<!-- download section -->
<aside class="bg-dark">...</aside>

<!-- contact section -->
<section id="contact">...</section>

...

start-bootstrap.cshtml

...
<body id="page-top">

<!-- navigation -->
@Html.Sitecore().ViewRendering("/Views/start-bootstrap/navigation.cshtml",
new { DataSource = "/sitecore/content/Home/start-bootstrap/data sources/navigation" })

<!-- hero section -->
@Html.Sitecore().ViewRendering("/Views/start-bootstrap/hero-section.cshtml",
new { DataSource = "/sitecore/content/Home/start-bootstrap/data sources/hero section" })

<!-- The remaining sections -->
@Html.Sitecore().Placeholder("section")

...


 

The content from the index.html are split out into the following views.

views


 

The layout files include all the partial views through static and dynamic binding. The layout file is decided to be in the start-bootstrap folder to keep the files centralized as much as possible.

In Sitecore the sitecore/Content/Home/start-boostrap item has the following presentation details setup.

setup


 

The dynamic binding of the about section Rendering is as follows.
All the other dynamic views also has the section value for the placeholder key.

about section


 

The content of the navigation.cshtml is simply the the navigation part refactored to that view.

<nav id="mainNav" class="navbar navbar-default navbar-fixed-top">
    <div class="container-fluid">
        <!-- Brand and toggle get grouped for better mobile display -->
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" 
              data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
     <       </button>
            <a class="navbar-brand page-scroll" href="#page-top">Start Bootstrap</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav navbar-right">
                <li>
                    <a class="page-scroll" href="#about">About</a>
                </li>
                <li>
                    <a class="page-scroll" href="#services">Services</a>
                </li>
                <li>
                    <a class="page-scroll" href="#portfolio">Portfolio</a>
                </li>
                <li>
                    <a class="page-scroll" href="#contact">Contact</a>
                </li>
            </ul>
        </div>
        <!-- /.navbar-collapse -->
    </div>
    <!-- /.container-fluid -->
</nav>


 

Step 3

Now this is the most fun part. We separate the content into data sources.
This is all my created data sources for the views.

data sources


 

The navigation items are created as children items of the navigation data source.
With this approach we can have N navigation items and avoid hard-coding the number of items in advance.

There is a navigation item that links to the about section.
I add a data source type in my about section data source which points to the about navigation item.

A data source type is a reference type.
Now the about navigation item is shared by the navigation view and by the about section view
If I need to update the navigation link I will only do it once.
This ensures a DRY-way and is less error prone for content management where I don’t have to remember to update data multiple places.

about data source


 

A dynamic field binding which is editable in edit mode looks like this.
The Model.Item will be used when a Sitecore item is not provided in the html helper method.


@Html.Sitecore().Field("field-name")

 

A specific Sitecore item can also be applied for the field.

@Html.Sitecore().Field("field-name", item)

navigation.cshtml is updated to use a data source.
To render the N navigation items a foreach loop is used.

@using Sitecore.Mvc
@using Sitecore.Mvc.Presentation
@using Sitecore.Data.Items
@model RenderingModel
			
<nav id="mainNav" class="navbar navbar-default navbar-fixed-top">
	<div class="container-fluid">
		<!-- Brand and toggle get grouped for better mobile display -->
		<div class="navbar-header">
			<button type="button" class="navbar-toggle collapsed" 
			  data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
				<span class="sr-only">Toggle navigation</span>
				<span class="icon-bar"></span>
				<span class="icon-bar"></span>
				<span class="icon-bar"></span>
			</button>
			<a class="navbar-brand page-scroll" href="#page-top">
				@Html.Sitecore().Field("title")
			</a>
		</div>

		<!-- Collect the nav links, forms, and other content for toggling -->
		<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
			<ul class="nav navbar-nav navbar-right">				
			@foreach (Item navigationItem in Model.Rendering.Item.Children)
			{
				<li>
					<a class="page-scroll" 
						href="#@Html.Sitecore().Field("link", navigationItem)">
						@Html.Sitecore().Field("title", navigationItem)
					</a>
				</li>					
			}
			</ul>
		</div>
		<!-- /.navbar-collapse -->
	</div>
	<!-- /.container-fluid -->
</nav>


 

I use the following extension helper method for the views to get to the data source referenced item.

ItemHelper.cs

using Sitecore.Data.Fields;
using Sitecore.Data.Items;

namespace Sitecore8.Models
{
    public static class ItemHelper
    {
        public static Item
        GetDataSourceTargetItemByFieldName(this Item item, string fieldName)
        {
            DatasourceField datasourceField = item.Fields[fieldName];
            return datasourceField.TargetItem;
        }
    }
}

 

about.cshtml is updated to use a data source.

@using Sitecore.Data.Items
@using Sitecore.Mvc
@using Sitecore.Mvc.Presentation
@using Sitecore8.Models
@model RenderingModel	
		   
@{
	Item linkItem = Model.Rendering.Item
		.GetDataSourceTargetItemByFieldName("navigation item");		
	var link = linkItem["link"];
}
 		
<section class="bg-primary" id="@link">
	<div class="container">
		<div class="row">
			<div class="col-lg-8 col-lg-offset-2 text-center">
				<h2 class="section-heading">
					@Html.Sitecore().Field("title")
				</h2>
				<hr class="light">
				<p class="text-faded">
					@Html.Sitecore().Field("teaser")
				</p>
				@Html.Sitecore().Field("link")
			</div>
		</div>
	</div>
</section>

 

Now the about section is editable.
I type this in my browser and navigate to the about section.
http://sitecore8.local/start-bootstrap?sc_mode=edit

I can edit the fields, great.
about edit mode

But I cannot save!?
My browser console log shows
Uncaught TypeError: saveButtonState.onchange is not a function

It seems the JavaScript used by Sitecore in edit mode and the JavaScript files in the webpage are colliding somehow.

I postpone the deeper investigation and as a quick fix for now, I ensure the webpage JavaScript are not running in edit mode.
I update my Layout view file.

 

start-bootstrap.cshtml

...
@if (Sitecore.Context.PageMode.IsNormal)
{
	<!-- jQuery -->
	<script src="~/assets/start-bootstrap/js/jquery.js"></script>

	<!-- Bootstrap Core JavaScript -->
	<script src="~/assets/start-bootstrap/js/bootstrap.min.js"></script>

	<!-- Plugin JavaScript -->
	<script src="~/assets/start-bootstrap/js/jquery.easing.min.js"></script>
	<script src="~/assets/start-bootstrap/js/jquery.fittext.js"></script>
	<script src="~/assets/start-bootstrap/js/wow.min.js"></script>

	<!-- Custom Theme JavaScript -->
	<script src="~/assets/start-bootstrap/js/creative.js"></script>
}
...

And now I can click save and update the fields in edit mode.

 

services-section.cshtml is updated to use a data source.
The icon can be updated by a text-field in edit mode.

Depending on the page mode the content can be adjusted by checking the PageMode.
E.g. this snippet only runs in edit mode.


@if (@Sitecore.Context.PageMode.IsPageEditorEditing)
{
...
}

@using Sitecore.Mvc
@using Sitecore.Mvc.Presentation
@using Sitecore.Data.Items
@using Sitecore8.Models
@model RenderingModel

@{
	Item linkItem = Model.Rendering.Item
		.GetDataSourceTargetItemByFieldName("navigation item");
	var link = linkItem["link"];
}

<section id="@link">

	<div class="container">
		<div class="row">
			<div class="col-lg-12 text-center">
				<h2 class="section-heading">
					@Html.Sitecore().Field("title")
				</h2>
				<hr class="primary">
			</div>
		</div>
	</div>

	<div class="container">
		<div class="row">

		@foreach (Item serviceItem in Model.Rendering.Item.Children)
		{
		<div class="col-lg-3 col-md-6 text-center">
			<div class="service-box">
				<i class="fa fa-4x fa-@serviceItem["icon"] wow bounceIn text-primary">
				</i>

				@if (@Sitecore.Context.PageMode.IsPageEditorEditing)
				{
					<div>
						@Html.Sitecore().Field("icon", serviceItem)
					</div>
				}

				<h3>
					@Html.Sitecore().Field("title", serviceItem)
				</h3>
				<p class="text-muted">
					@Html.Sitecore().Field("teaser", serviceItem)
				</p>
			</div>
		</div>
		}

		</div>
	</div>
</section>

 

Services section data source

The icon is simply a text-field type.
The icons used are from Font Awesome web-fonts and they use css classes with fa- prefix.

services data source

 

Services section in edit mode

services edit mode

The icon text-field is only displayed in edit mode.
Here I can for example type car and click save to update to a new icon
Font Awesome car – http://fortawesome.github.io/Font-Awesome/icon/car/

I will stop the walk-through now as the rest of the refactoring is similar in method to what has been presented.

The rest of the refactored views are available to see at my GitHub repository:
https://github.com/kunukn/Sitecore/tree/master/startbootstrap-creative-example/Sitecore8

 

Dynamic binding of the views.
Because the last 5 sections were dynamically bound I can easily re-order them in the layout.
In Sitecore I go to presentation details and re-order so the download section is at the top.

sc sections reorder

Then when I re-load the webpage I can see the download section is now at the top just below the hero section.

sections reorder

 

Prerequisites

You will need to have Sitecore 8 installed and running, xDB is not needed for this example.

Installation guide

http://www.sitecore.net/Learn/Blogs/Technical-Blogs/Getting-to-Know-Sitecore/Posts/2014/12/Sitecore-8-and-Sitecore-Instance-Manager.aspx

I have used these steps to setup my Sitecore installation
https://github.com/kunukn/Sitecore/blob/master/Installation/Sitecore8/README.md

Reference learning materials

http://jockstothecore.com/sitecore-mvc-item-maze/

https://www.youtube.com/user/SitecorePM

Written by kunuk Nykjaer

May 17, 2015 at 2:05 pm