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!

Advertisements

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:

Sitecore Commerce: O catálago Adventure works não foi importado. Como fazê-lo manualmente?

Se você está trabalhando com Sitecore Commerce e está seguindo o guia de instalação você sabe que durante a instalação, o catálogo Adventure works é importado no Commerce Server e no Sitecore. E se ocorrer um problema durante a instalação, como posso fazê-lo manualmente?

Se você continuou lendo é porque possivelmente está tendo o mesmo problema que eu tive recentemente. O processo de importação ocorre quando você roda o commando de powershell InitializeCSSite. Porém se você abrir o Commerce Server e ele não tiver sido importado, eis o que você pode fazer:

1) Abra o Commerce Server e no link Catalogo click importar.

2) Navegue até o caminho da sua instância do Sitecore e procure pelo arquivo catalog.xml, por exemplo, C:\myproject\Website\SitecoreCommerce\ Data\Catalog\catalog.xml. Depois de fazer isso e clicar em OK você deverá ver, na aba Status do Commerce Server o seguinte:

3) É isso. Agora se você abrir o Editor de Conteúdo do Sitecore e navegar até o caminho do item Catalogs(/sitecore/Commerce/Catalog Management/Catalogs) você deverá ver o catálogo Adventure Wors disponível para ser selecionado, no campo Selected Catalogs. Clicando o checkbox deverá fazer o catálogo ficar disponível abaixo do item Catalogs:

Updating renderings properties using Sitecore Powershell Extensions

Have you ever needed to update a rendering property on several items in your Sitecore content tree and didn’t want to go through the process manually? Well recently I had, and if I had to do it manually one by one, it take a lot of time as I had several items I needed to perform such task. Whenever you face a situation like this, you might want to consider using the powershell extensions module.

By the way If you are working with Sitecore and you are not familiar with the powershell extensions module I highly recommend you start using it. It can save you a lot of time. You can download the module at the marketplace.

So in this scenario I wanted to update a rendering on all items of a specific template and set the caching “Vary By Data” property to checked on all of them.

Here is how you can easily do that by running this script:

$items = Get-ChildItem -Recurse | Where-Object {$_.TemplateID -match “{79A0F7AB-17C8-422C-B927-82A1EC666ABC}”} | ForEach-Object {

$renderingInstance = Get-Rendering -Item $_ -Rendering $rendering
if($renderingInstance){
$renderingInstance.VaryByData = 1
Set-Rendering -Item $_ -Instance $renderingInstance
}

}

 

So what this script is doing is pretty straight forward but in any case here is the explanation on what it does.

It is getting all descendants of the Sitecore Context, in this case sitecore\content\home and it is filtering for all descendants that have the TemplateID guid as “{79A0F7AB-17C8-422C-B927-82A1EC666ABC}”.
Next it will loop on the results and set to the renderingInstance variable the rendering. If it’s not null it sets the VaryByData property to 1(checked) and execute the Set-Rendering command.
At this point all renderings on the items will have been set to “Vary By Data” as checked as shown bellow:

Atualizando as propriedades dos renderings com Sitecore e Powershell Extensions

Voce já teve que atualizar uma propriedade em um rendering em vários itens da árvore de conteúdo e não queria atualizá-los manualmente? Eu recentemente tive que fazer isso e abaixou vou descrever como fazê-lo, utilizando o módulo Powershell Extensions.

Quando você tiver um cenário parecido com esse, eu recomendo você utilizar o módulo Powershell Extensions.

Se você trabalha com Sitecore e não conhece esse módulo, eu recomendo que começe a usar. Você pode ganhar bastante tempo utilizando ele. Você pode fazer o download do módulo na marketplace da Sitecore.

No cenário deste post, eu queria atualizar um rendering em todos os itens de um determinado template e setar a propriedade de cache “Vary By Data” para checado em todos os itens.

Para fazer isso basta rodar este script:

$items = Get-ChildItem -Recurse | Where-Object {$_.TemplateID -match “{79A0F7AB-17C8-422C-B927-82A1EC666ABC}”} | ForEach-Object {

$renderingInstance = Get-Rendering -Item $_ -Rendering $rendering
if($renderingInstance){
$renderingInstance.VaryByData = 1
Set-Rendering -Item $_ -Instance $renderingInstance
}

}

Caso você tenha alguma dúvida sobre o que ele está fazendo vou detalhar melhor.

Ele esta pegando todos os itens descendentes o item de contexto do Sitecore, que nesse caso é sitecore\content\home e está filtrando por todos os itens que sejam do template com o ID igual a  “{79A0F7AB-17C8-422C-B927-82A1EC666ABC}”.

Depois ele faz um loop em todos os resultados e pega o rendering de cada item no loop e armazena na variável renderingInstance. Se ela não for nula, ele seta a propriedade VaryByData com o valor 1(checado) e executa o comando Set-Rendering.

A essa altura todos os items que ele percorreu no loop terão a propriedade “Vary By Data” checada, conforme abaixo: