Diego Moretto is awarded with Sitecore Technology 2018 MVP

FLORIANOPOLIS, BRAZIL — January, 31, 2018. Diego Moretto a Senior Sitecore developer at Brainjocks has been awarded with the Most Valuable Professional(MVP) today. He is one of the only 208 Technology MVP worldwide to be awarded as Sitecore MVP this year.

“The Sitecore MVP awards recognize and honor those individuals who make substantial contributions to our loyal community of partners and customers,” said Pieter Brinkman, Sitecore Senior Director of Technical Marketing. “MVPs consistently set a standard of excellence by delivering technical chops, enthusiasm, and a commitment to giving back to the Sitecore community. They truly understand and deliver on the power of the Sitecore Experience Platform to create personalized brand experiences for their consumers, driving revenue and customer loyalty.”

Now in its 12th year, Sitecore’s MVP program recognizes individual technology, strategy, and commerce advocates who share their Sitecore passion and expertise to offer positive customer experiences that drive business results. The Sitecore MVP Award recognizes the most active Sitecore experts from around the world who participate in online and offline communities to share their knowledge with other Sitecore partners and customers.

More information on the MVP program can be read here.

 

Licença de trial para Sitecore disponível!

Semana passada a Sitecore lançou um programa que da acesso a uma licença de trial de 60 dias para desenvolvedores. Esta é uma iniciativa que favorece muito para aqueles que querem começar a trabalhar com Sitecore porém não tem licença.  Para aplicar para receber a licença é necessário preencher o formulário disponível nesse link.
Aproveite!

5 passos para o Sitecore suportar Source Parameters e Datasources dinamicos em fields TreeList

Recentemente eu trabalhei em um projeto multi-site em Sitecoore com requisitos específicos. Eu precisava que o field Treelist suportasse queries com datasources dinamicos funcionando em paralelo com source parameters. Neste caso é importante mencionar que esse field era usado no contexto de um template customizado da media library, que nesse caso, não tinha contexto do site, então isso era necessário ser determinado dinamicamente.
Como ja explicado nesse post do Kamruz, queries e source parameters não funcionam em paralalo sem criar um novo field.

O resultado final que estava tentando alcançar, era o field Source declarado dessa forma:
DataSource=query:../../&ExcludeTemplatesForSelection=NotDesirableTemplate.
Mas há um problema com a query acima. Eu precisava de alguma forma determinar o caminho dinamicamente, já que como mencionado, seria necessário primeiro determinar o site e colocar o caminho baseado no site.

Ficou confuso?  Lembre: Estamos tentando ter o field TreeList suportando Sources dinamicos e ainda usar paremeters, por exemplo ExcludeTemplatesForSelection.
Abaixo eu descrevo 5 passos, para implementar esse requisito com algumas considerações importantes de se ter presente.

 

1. Criar um token para a query

DataSource=query:#mytoken#&ExcludeTemplatesForSelection=NotDesirableTemplate.

Seguinte nós precisamos fazer duas coisas: Extender o field TreeList e implementar uma forma de manipular o #mytoken#, para substituir o token com o caminho do site.

 

2. Criar um novo Field

Após logar no Sitecore e acessar o Desktop, mude para o banco Core e crie um novo List Type. Para fazer isso, navegue até o caminho: /sitecore/system/Field Types/List Types e crie um novo List Type. No meu exemplo eu chamei de ‘CustomTreeList’.
Depois preencha os campos Assembly e Class. Abaixo uma imagem de como ficou definido no meu caso:

3. Implement o novo TreeList Field

Agora precisamos implementar o novo field e o token definido no passo 1. Eu usei o código do Kamruz como base e adaptei para este requisito. No final ficou definido dessa forma:

 


namespace MyNamespace.CustomFields
{
    public class CustomTreeList : Sitecore.Shell.Applications.ContentEditor.TreeList
    {
        private string _ds = string.Empty;


        /// <summary>
        /// Override the TreeList DataSource property to support the mytoken on the Source
        /// </summary>

        public override string DataSource
        {
            get
            {
                if (_ds.StartsWith("query:"))
                {
                    if (Sitecore.Context.ContentDatabase == null || base.ItemID == null)
                        return null;
                    var current = Sitecore.Context.ContentDatabase.GetItem(base.ItemID);

                    if (_ds.Contains("#mytoken#"))
                    {                       
                        //uses the current media item to resolve what the tenant which it belongs to
                        var myTenant = Tenant.GetTenant(new ID(ItemID));
                        if (myTenant != null)
                        {
                            var sourceFolder = myTenant.GetChildren().FirstOrDefault(i=>; i.Template.ID.ToString() == Constants.Home.ID);
                            if (sourceFolder != null)
                            {
                                //found the Home item under the tenant item so replace the datasource with the Home Path
                                _ds = _ds.Replace("#mytoken#", sourceFolder.Paths.FullPath);
                            }
                        }
                        else
                        {
                            Log.Warn("Could not find tenant for item "+ItemID+ " : the path /sitecore/content is returned by default. This could be because the item is _standard values" , ItemID);
                            _ds = "/sitecore/content";
                        }

                    }

                    Item item = null;
                    try
                    {
                        item = Enumerable.FirstOrDefault<Item>((IEnumerable<Item>)LookupSources.GetItems(current, _ds));
                    }
                    catch (Exception ex)
                    {
                        Log.Error("Treelist field failed executing the query", ex, (object)this);
                    }

                    return item == null ? null : item.Paths.FullPath;
                }
                return _ds;
            }
            set { _ds = value; }
        }
    }

}

 

O que esse código esta fazendo é, pegar o #mytoken# e definido no Source e sobrescrever com o item Home do Site que ele pertence.
Por exemplo, se voce tem uma árvore de conteúdo que tem um item de midia que resolve para o site 1. após executar esse código, o resultado do Source ficaria assim definido:

DataSource=query:/Sitecore/Content/Site1/Home&ExcludeTemplatesForSelection=NotDesirableTemplate.


Isso funciona, porém não acabamos ainda
.

 

Este é um field TreeList, o que significa que ele usa o Link Database. De maneira que se queira manter as funcionalidades relacionadas a links quebrados, atualização de links, etc, você precisa informar o Sitecore o que precisa acontecer quando esse field for feito o rebuild no painel de controle do Sitecore.
Outra coisa que será necessário fazer é garantir que esse field funciona com Content Search.

4. Atualizar o Link Database

Conforme mencionado, para manter compatibilidade com as funcionalidades do link database, voce precisa implementar um novo field que deriva do tipo CustomField e registrá-lo. A forma de fazer isso é bem explicada aqui. Registrando o field:

 



<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
    <sitecore>
        <fieldTypes>
            <!--Used to update the link database-->
            <fieldType name="CustomTreeList" type="MyNamespace.CustomFields.CustomTreeListType, MyNamespace" />
        </fieldTypes>
    </sitecore>
</configuration>


Implementação da Classe:

namespace MyNamespace.CustomFields
{
    public class CustomTreeListType : CustomField
    {
        public CustomTreeListType(Field innerField) : base(innerField)
        {
        }

        public CustomTreeListType(Field innerField, string runtimeValue) : base(innerField, runtimeValue)
        {
        }
       
        private void AddLink(ID id)
        {
            if (!Value.EndsWith("|") && Value.Length > 0)
            {
                Value += "|";
            }
            Value += id.ToString();
        }

        
        private void ClearLink(ID targetItemID)
        {
            Value = Value.Replace(targetItemID.ToString(), "");
            Value = Value.Replace("||", "|");
            Value = Value.TrimEnd('|');
            Value = Value.TrimStart('|');
        }

        private void ClearAllInvalidLinks()
        {
            foreach (var itemId in Value.Split('|'))
            {
                var targetItem = InnerField.Database.GetItem(new ID(itemId));
                if (targetItem != null)
                    ClearLink(targetItem.ID);
            }
        }
       
        public override void Relink(ItemLink itemLink, Item newLink)
        {
            Assert.ArgumentNotNull(itemLink, "itemLink");
            Assert.ArgumentNotNull(newLink, "newLink");
            Database database = Factory.GetDatabase(itemLink.TargetDatabaseName);
            if (database != null)
            {
                Item targetItem = database.GetItem(itemLink.TargetItemID);
                if (targetItem == null)
                {
                    ClearLink(itemLink.TargetItemID);
                }
                else
                {
                    if (Value.Contains(itemLink.TargetItemID.ToString()))
                    {
                        Value = Value.Replace(itemLink.TargetItemID.ToString(), newLink.ID.ToString());
                    }
                    else
                    {
                        AddLink(newLink.ID);
                    }
                }
            }
        }

        public override void RemoveLink(ItemLink itemLink)
        {
            Assert.ArgumentNotNull(itemLink, "itemLink");
            ClearLink(itemLink.TargetItemID);
        }

        public override void UpdateLink(ItemLink itemLink)
        {
            Assert.ArgumentNotNull(itemLink, "itemLink");
            Database database = Factory.GetDatabase(itemLink.TargetDatabaseName);
            if (database == null)
            {
                ClearLink(itemLink.TargetItemID);
            }
            else
            {
                ClearAllInvalidLinks();
                Item targetItem = database.GetItem(itemLink.TargetItemID);
                if (targetItem != null)
                    AddLink(targetItem.ID);
            }
        }

        public override void ValidateLinks(LinksValidationResult result)
        {
            Assert.ArgumentNotNull(result, "result");

            string itemIds = this.Value;
            if (string.IsNullOrEmpty(itemIds))
            {
                Log.Info("Validate links ID is empty", itemIds);
                return;
            }
                
            Log.Info("IDs = "+Value, result);
            foreach (var itemId in this.Value.Split('|'))
            {
                var targetItem = this.InnerField.Database.GetItem(new ID(itemId));
                if (targetItem != null)
                    result.AddValidLink(targetItem, targetItem.Paths.FullPath);
                else
                    result.AddBrokenLink(itemId);
            }
        }
    }
}


5. Mantendo compatibilidade com Content Search

Estamos quase finalizando! A última parte é armazenar o novo field de maneira que ele funcione com Content Search. Do contrário, você terá problemas como esse here.
Como explicado na resposta do Richar Seal, a maneira defazer os resultados returnarem é criando um novo arquivo de configuração que muda o valor de StorageType=Yes. No meu caso, fico assim definido:
FieldTypeName=”CustomTreeList” and StorageType=”Yes.”

 


<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
    <sitecore>
        <contentSearch>
            <indexConfigurations>
                <defaultLuceneIndexConfiguration>
                    <fieldMap>
                        <fieldTypes hint="raw:AddFieldByFieldTypeName">
                            <!--needed to set the storagetype=Yes so the field shows within the index-->
                            <fieldType fieldTypeName="customtreelist" storageType="YES" indexType="TOKENIZED" vectorType="NO" boost="1f" type="System.String" settingType="Sitecore.ContentSearch.LuceneProvider.LuceneSearchFieldConfiguration, Sitecore.ContentSearch.LuceneProvider" patch:after="*[1]" />
                        </fieldTypes>
                    </fieldMap>
                </defaultLuceneIndexConfiguration>
            </indexConfigurations>
        </contentSearch>
    </sitecore>
</configuration>

Espero que este post seja util para vocês!

Issue while authenticating on Sitecore Commerce: What was it and how I fixed it

Recently we encountered a problem while setting up Sitecore Commerce 8.2.1 instance. We followed all the setup steps from the guideline (found here). During the setup we did not face any issues. But after going through all the steps and smoke testing the site we ran into an issue when the user tried to authenticate on the site.

The user’s domain we were trying to authenticate with was CommerUsers. If we tried to change the password using Sitecore Security manager for example we would also have the same issue which was:

The requested profile could not be retrieved because the key name provided does not exist or is not an indexed property.  The profile type is ‘UserObject’.  The key name provided is ‘GeneralInfo.ExternalID’.

It turns out that the issue was related to the execution of the InitializeCSSite powershell command which failed to import the profile. This is the first step of the Set up a Commerce Server site step, as you can see here.

To address this issue we had to import the profile.xml schema manually following the instructions on this link. The profile schema can be found at the website’s root under the path: Website\SitecoreCommerce\Data\Profiles\Profiles.xml

This xml file is part of the Storefront package installation. Hopefully this will save others some time!

O resultado gerado por um Sitecore Sheer UI dialog apagava o valor do campo Rich Text Editor. Qual era o problema e como resolvi?

Ao trabalhar com Sitecore depois de um tempo, nos deparamos com requisitos a serem implementados que são bem corriqueiros, como criar um botão no Rich Text editor e realizar alguma ação. Porém e se o botão abrir uma janela de Sheer UI, da qual você precise selecionar um valor e atualizar um campo no rich text editor?

Você conseguiria mas o valor do campo pode ser sobrescrito. Se você está procurando por uma solução para esse comportamento descrito, continue lendo 🙂

CENARIO

Neste cenário eu criei um botão no Rich Text Editor e quando o usuário clicava o botão, um comando de abrir uma janela de Sheer UI era disparado, e o usuario poderia selecionar de uma lista e depois um link era gerado e mostrado no editor a partir do valor selecionado. O problema, como mencionei a pouco, é que o restante do conteúdo era apagado. O link que eu estava gerando era similar a qualquer link de midia gerado pelo Sitecore:

 

<a href="~/media/c91e6ce67216446c8c2af1ae5d2a8120.ashx">PDF_test</a>

O meu diálogo Sheer herda de Sitecore.Web.UI.Pages.DialogForm. Eis como o método OnOk do meu diálogo foi definido(adaptado para este post). A variável dialogValue era uma string que continha um link, e passava ele para a janela de Sheer.

protected override void OnOK(object sender, EventArgs args)
{
        //previous code cleared from this question
        //this would bring the link to be assigned to the dialog
        var dialogValue = AssetService.GetValue();
        SheerResponse.SetDialogValue(dialogValue);
        base.OnOK(sender, args);
}

Este era o command que eu implementei no arquivo RichTextCommands.js para fazer a chamada da janela de Sheer:

Telerik.Web.UI.Editor.CommandList["SelectAsset"] = function (commandName, editor, args) {
    scEditor = editor;

    editor.showExternalDialog(
        "/sitecore/shell/default.aspx?xmlcontrol=SelectAsset&currentItemId=" + scItemID + "&assetType=link",
        null, //argument
        1105,
        700,
        scInsertSitecoreLink,
        null,
        "Asset",
        true, //modal
        Telerik.Web.UI.WindowBehaviors.Close, // behaviors
        false, //showStatusBar
        false //showTitleBar
    );
};


SOLUÇÃO

Para resolver esse problema existem algumas coisas que precisam serem feitas. Abaixo seguem 4 passos que fiz para resolvê-las.

1- Defina as funções de javascript em um arquivo de js:


function GetDialogArguments() {
	return getRadWindow().ClientParameters;
}

function getRadWindow() {
	if (window.radWindow) {
		return window.radWindow;
	}

	if (window.frameElement && window.frameElement.radWindow) {
		return window.frameElement.radWindow;
	}

	return null;
}

var isRadWindow = true;

var radWindow = getRadWindow();

if (radWindow) {
	if (window.dialogArguments) {
		radWindow.Window = window;
	}
}

function scClose(url, text) {
    //builds the link and anchor name
	var returnValue = {
		url: url,
		text: text
	};

	getRadWindow().close(returnValue);

}

function scCancel() {
	getRadWindow().close();
}

Note a função scClose. Esta função será referenciada abaixo on método OnOk. Em outras palavras, quando o usuário clica na janela de Sheer no botão OK, e depois que o processamento do lado do servidor terminou, esta função de js é chamada no lado do cliente(ver passo 4)

2- Referencie o arquivo de js que você criou no arquivo XML do seu diálogo Sheer.

3- Na classe de code beside do diálogo adicione uma nova propriedade. No meu caso ela ficou definida assim:

protected string Mode{
     get{
          string str = StringUtil.GetString(this.ServerProperties["Mode"]);
          if (!string.IsNullOrEmpty(str))
               return str;
          return "shell";
      }
      set{
          Assert.ArgumentNotNull((object)value, "value");
          this.ServerProperties["Mode"] = (object)value;
      }
}

4- No método OnOk agora a variável dialogValue é um objeto com duas propriedades: Url e Text. Após realizar essas mudanças o método OnOk ficou definido assim:


          var dialogValue = AssetService.GetRawValue();          
          //Rich text asset
          if (this.Mode == "webedit" || dialogValue == null)
          {
                SheerResponse.SetDialogValue(StringUtil.EscapeJavascriptString(dialogValue));
                base.OnOK(sender, args);
          }
          else
               //writes the url and link name(dialogValue .Value). This will build the link on the Rich Text Editor
               SheerResponse.Eval("scClose(" + StringUtil.EscapeJavascriptString(dialogValue.Url) + "," + StringUtil.EscapeJavascriptString(dialogValue.Value) + ")");
            

Note que na clausula else, o método base.OnOk não está sendo chamado mas sim o método Eval referenciando a função scClose e passando os parametros url e text(utilizados para construir o link). No fim o resultado era um link no mesmo formato descrito no início deste post mas o conteúdo do campo Rich Text editor não era mais apagado

 

Configurando um workflow em todos os templates utilizando SPE

Recentemente eu tive que implementar um novo workflow em um projeto de Sitecore. Todos os templates existentes necessitariam utilizar o novo workflow. Eu até poderia ir manualmente em cada template e atualizar manualmente, mas como eu sou um desenvolvedor, normalmente quando eu tenho que realizar uma atividade repetitiva eu penso em formas de automatizá-la. Neste caso, como haviam alguns templates a serem atualizados, eu decidi escrever um script de powershell para atualizar todos.

O módulo Sitecore Powershell Extensions é otimo para automatizar atividades como essa, das quais normalmente necessitam de passos manuais e repetitivos. Se você é um desenvolvedor de Sitecore e ainda não utiliza, eu recomendo dar uma olhada. Esse módulo vem com várias funcionalidades interessantes como relatórios, integrações com o Editor de Conteúdo, Painel de controle, além de vir com muitos comandos que facilitam a construção de scripts para manipular os itens em Sitecore.

Script

Voltando ao cenário descrito no início do post, eu criei o seguinte script(ligeiramente adaptado para o fim do post):

$templates = Get-ChildItem -Path "/sitecore/templates/My Project Templates" -Recurse | Where-Object { $_.Name -match "__Standard Values" } | ForEach-Object{
       $_.__Workflow = "{A5BC37E7-ED96-4C1E-8590-A26E64DB55EA}"
}

O que esse script está fazendo é pegando todos os itens standard values que são descendentes do caminho “/sitecore/templates/My Project Templates”, que é o caminho onde os templates do meu projeto estavam. Depois ele percorre os resultados e configura o no campo workflow o GUID do workflow que eu havia criado, que nesse caso era “{A5BC37E7-ED96-4C1E-8590-A26E64DB55EA}”.

Simples certo? Bem é por isso que eu pessoalmente gosto de utilizar o módulo Sitecore Powershell Extensions sempre que eu posso, pois ele facilita muito as coisas. Se você tiver interesse, é possivel fazer o download diretamente daqui.  Você também pode ver o livro online que tem muitos exemplos interessantes e uma documentação bem completa sobre o módulo, além de ter o suporte da comunidade no canal do Slack #module-spe.

Same post in english here

Como implementar um site de pre-produção em Sitecore? Eis algumas dicas de como fazer isso

Um requisito muito comum para quem trabalha com Sitecore é a necessidade dos autores de conteúdo de enviar URLs para outras pessoas, normalmente pessoas que não tem acesso ao Sitecore. Se essas pessoas normalmente não tem acesso, como elas podem revisar o conteúdo antes de ser publicado?

Conforme descrito aqui. na versão 7.2 foi adicionada a possibilidade de implementar a publicação para ambientes de pré-produção. No link mencionado tem algumas dicas interessantes de como setar um ambiente assim. Eis aqui outras que podem ser úteis para quem quiser configurar um ambiente desse.

1- Você precisa configurar um site definition. Para fazer isso voce pode usar o arquivo SiteDefinitions.config, localizado na pasta App_Config/Include e adicionar a definição conforme o exemplo:

<site name=”preprod” patch:before=”site[@name=’website’]” virtualFolder=”/” physicalFolder=”/” rootPath=”/sitecore/content” content=”preprod” startItem=”/home” database=”preprod” domain=”sitecore” allowDebug=”true” cacheHtml=”false” htmlCacheSize=”0″ registryCacheSize=”0″ viewStateCacheSize=”0″ xslCacheSize=”0″ filteredItemsCacheSize=”0″ enablePreview=”true” enableWebEdit=”true” enableDebugger=”true” disableClientData=”false” hostName=”preview.mywebsite.com”/>

Note a propriedade hostName. Esta é a url que os autores de conteúdo irão prover para os usuários que não tem acesso ao sitecore. Outra coisa importante de notar aqui é o banco “preprod”. Ver item 3.

2- No IIS, adicione o binding preview.mywebsite.com no seu site. Quando o fizer essa URL vai resolver no seu site.

3- Adicone um novo banco de dados master ou crie uma cópia do existente e adicione novamente ao SQL Server. Note abaixo como deve ficar a connection string:

<add name=”preprod” connectionString=”user id=myuser;password=mypassword;Data Source=MyDataSource;Database=sitecore_master_preview” />

4- No Sitecore, crie um novo publication target. Para fazer isso, abra o Editor de Conteúdo e navegue até o caminho e crie um novo item, que no exemplo abaixo eu nomeei Preprod. Depois disso sete o valor para checado no campo  “Preview publishing target”

5-  Atualize o workflow para ter uma ação do tipo __OnSave para publicar para o banco preprod. Isso é importante para garantir que o conteúdo sendo editado esteja disponível para visualização no site de pré-produção:

Note que o parâmetro tem o mesmo nome que o publishing target recém criado.

 

6-  Caso você esteja lidando com um ambiente escalável você precisa configurar alguns itens também. Sugiro que você revise o scaling guide do Sitecore para isso mas os itens a seguir já vão te ajudar nisso.

If you are dealing with an scaled environment you need to make sure that the publishing is properly configured. Please review Sitecore’s scaling guide for more details about this but items 7 and 8 will help you with that.

7- Você precisa limpar a cache do site preprod quando uma publicação ocorrer. Para fazer isso você precisa adicionar uma definição no event publis:end e publish:end:remote conforme abaixo:

 <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
 <sitecore>
       <events>
            <event name="publish:end">
                  <!--<handler type="Sitecore.Modules.RSS.PublishingHandler, Sitecore.Modules.RSS" method="OnPublishEnd"/>-->
                 <handler type="Sitecore.Publishing.HtmlCacheClearer, Sitecore.Kernel" method="ClearCache">
                       <sites hint="list">
                           <patch:delete />
                       </sites>
                       <sites hint="list">
                            <site>website</site>
                            <site>preprod</site>
                       </sites>
                </handler>
            </event>
            <event name="publish:end:remote">
                   <handler type="Sitecore.Publishing.HtmlCacheClearer, Sitecore.Kernel" method="ClearCache">
                          <sites hint="list">
                               <patch:delete />
                          </sites>
                          <sites hint="list">
                                <site>website</site>
                                <site>preprod</site>
                          </sites>
                   </handler>
            </event>
        </events>
</sitecore>
</configuration>

Continue reading

Ever wonder how to implement a pre-production preview website on Sitecore? Here are a few hints to get that done!

It is a common request from content authors in Sitecore that from time to time they needed to send internal URL to non-Sitecore users. Since these users generally do not have access to Sitecore how can they review the content before it goes live, for example?
As described here, on Sitecore 7.2 it was added the ability to implement pre-production publishing. Take a look at the link provided with a few useful hints to setup the pre-production publishing. Here are others that need to be done in order to get that working properly.

1- You need to have a site definition in place. To do that you can use the SiteDefinition.config file, located under App_Config/Include to add your site definition. Here is one example:

<site name=”preprod” patch:before=”site[@name=’website’]” virtualFolder=”/” physicalFolder=”/” rootPath=”/sitecore/content” content=”preprod” startItem=”/home” database=”preprod” domain=”sitecore” allowDebug=”true” cacheHtml=”false” htmlCacheSize=”0″ registryCacheSize=”0″ viewStateCacheSize=”0″ xslCacheSize=”0″ filteredItemsCacheSize=”0″ enablePreview=”true” enableWebEdit=”true” enableDebugger=”true” disableClientData=”false” hostName=”preview.mywebsite.com”/>

Note the hostName property. This is the url that the content authors will provide to non-sitecore users. Also the property database points to a new database “preprod”

2- On IIS, add the preview.mywebsite.com to the binding of your website. When you do that you will enable it to be resolved by your website.

3- Attach a new master database or create a snapshot of the existing one and re-attach to SqlServer. The connection string for this new database should be set to preprod:

<add name=”preprod” connectionString=”user id=myuser;password=mypassword;Data Source=MyDataSource;Database=sitecore_master_preview” />

4- On Sitecore create a new publication target. To do that, open the content editor and navigate to the path /sitecore/system/Publishing targets and create a new target which I`m naming preprod. After you do that check the checkbox “Preview publishing target”

5- Update your workflow definition to have the __OnSave action to publish it to the preprod database. This is required to help content authors when creating the content and having the content landing on the preview site:

Note that the target parameter has the same name as the publishing target created.

 

6- If you are dealing with an scaled environment you need to make sure that the publishing is properly configured. Please review Sitecore’s scaling guide for more details about this but items 7 and 8 will help you with that.

7- Make sure you preprod site is getting it’s cache cleared when the publishing is happening. To do that need to add it’s definition to the publish:end and publish:end:remote configurations to a HtmlCacheClear file definition.

Here is an example of how it could be defined:

 <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
 <sitecore>
       <events>
            <event name="publish:end">
                  <!--<handler type="Sitecore.Modules.RSS.PublishingHandler, Sitecore.Modules.RSS" method="OnPublishEnd"/>-->
                 <handler type="Sitecore.Publishing.HtmlCacheClearer, Sitecore.Kernel" method="ClearCache">
                       <sites hint="list">
                           <patch:delete />
                       </sites>
                       <sites hint="list">
                            <site>website</site>
                            <site>preprod</site>
                       </sites>
                </handler>
            </event>
            <event name="publish:end:remote">
                   <handler type="Sitecore.Publishing.HtmlCacheClearer, Sitecore.Kernel" method="ClearCache">
                          <sites hint="list">
                               <patch:delete />
                          </sites>
                          <sites hint="list">
                                <site>website</site>
                                <site>preprod</site>
                          </sites>
                   </handler>
            </event>
        </events>
</sitecore>
</configuration>

Continue reading

Sitecore Commerce: Adventure works catalog did not get imported. How to do that manually?

If you are working with Sitecore Commerce and you follow the setup guideline you know that it imports the Adventure works catalog into Commerce Server and Sitecore. What if something goes wrong and you need to import it manually?

If you are reading this most likely you ran into the same issue I did recently. The import process happens when you are running the powershell command InitializeCSSite but if you open Commerce server and it’s not showing up here is what you need to do:

1) Open Commerce Server, and on the Catalogs click import.

2) Browse to your Sitecore Website folder and look for the catalog.xml file i.e. C:\myproject\Website\SitecoreCommerce\Data\Catalog\catalog.xml. Once you do that you should be see on the Status View into Commerce Server the following:

3) That’s it. Now if you open Sitecore content Editor and navigate to the Catalogs root (/sitecore/Commerce/Catalog Management/Catalogs) you should see the Adventure Wors Catalog available on the Selected Catalogs field. Clicking the checkbox should make it show under the Catalogs Item as bellow: