2017년 5월 25일 목요일

사이트코어 8.0에서 Sitecore Developer Center 및 XPath Builder 접근하기

사이트코어 8.0부터 기존에 Control Panel또는 데스트탑 모드에서 제공되었던 Developer Center기능이 사라졌다. 하지만, 아래의 URL을 직접 입력함으로써, XPath Builder 기능 또는 File Explorer등의 기능을 사용할수가 있다.

http(s)://yourdomain/sitecore/shell/default.aspx?xmlcontrol=IDE

Sitecore Developer Center

Sitecore XPath Builder


참고로, 사이트코어에서 아이템을 찾거나 검색을 하는데, Fast Query는 아주 유용하게 쓰인다. Fast Query는 SQL 엔진을 사용함으로써, 검색 수행 능력을 향상시킬뿐 아니라, 아이템의 결과 값만 검색하므로 캐쉬 메모리에 불필요한 정보를 저장하지 않는다.

Sitecore Fast Query Cheat Sheet
http://sitecoreworld.blogspot.com/2014/09/querying-items-from-sitecore.html


2017년 5월 2일 화요일

DateTime 포맷 변경하기

이번에는 사이트코어의 DateTime 값에 대하여 알아보도록 하자.

Content Editor에서 아이템의 Statistical 데이타를 보면 언제 아이템이 만들어졌고, 업데이트 되어었는지에 대한 날짜와 시간정보가 나온다.

이 값을 Raw Value로 보면, "yyyymmddThhmmss" 포맷으로 해당 필드의 값을 ISO 포멧으로 저장을 한다. 어플리케이션을 만들다 보면, 해당 값을 다른 Date Time 포맷을 변경을 해야하는 경우가 있는데, 이럴경우 Sitecore.DateUtil class를 통하여 해당 포맷을 쉽게 변경할수가 있다.


1
2
3
4
5
6
7
8
string convertedDateTime = string.Empty;
Sitecore.Data.Items.Item item = Sitecore.Context.Item;
Sitecore.Data.Fields.DateField date = item.Fields["__Updated"];
System.DateTime datetime = Sitecore.DateUtil.IsoDateToDateTime(date.Value);
string originalValue = date.Value; // 20170426T184902Z
convertedDateTime = Sitecore.DateUtil.FormatLongDateTime(originalValue);     // Wendsday, April 26, 2017 2:49 PM
convertedDateTime = Sitecore.DateUtil.FormatShortDateTime(originalValue);    // 4/26/2017 2:49 PM
convertedDateTime = Sitecore.DateUtil.DateTimeToMilitary(originalValue);     // 26-APR-2017 14:49:40


2017년 4월 14일 금요일

코드를 이용한 아이템 Publish 옵션 살펴보기

이번에는 프로그램 코드로 아이템을 페블리쉬하는 방법에 대하여 소개하겠다.

사이트코어는 Sitecore Publishing API를 이용하여, 다양한 Publishing 모드를 제공한다. 이번에는 각각의 Publishing 모드가 어떤 상황에서 적절히 쓰여야할지 알아보도록 하겠다.
  1. 새로운 "Publisher" 오브젝트를 이용하여 아이템을 Publish하는 방법

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    Sitecore.Publishing.PublishOptions publishOptions =
      new Sitecore.Publishing.PublishOptions(item.Database,
                                             Database.GetDatabase("web"),
                                             Sitecore.Publishing.PublishMode.SingleItem,
                                             item.Language,
                                             System.DateTime.Now);
    Sitecore.Publishing.Publisher publisher = new Sitecore.Publishing.Publisher(publishOptions);
    publisher.Options.RootItem = item;
    publisher.Options.Deep = true;
    var publishResult = publisher.PublishWithResult();
    
    • 장점
      • 프로세스가 끝나자마, 아이템을 즉시 Publish한다.
      • "PublishWithResult()" 메써드를 사용하여 Publish가 성공적으로 끝났는지 혹시 실패되었는지 확인할수가 있다.
    • 단점
      • "Job Manager"를 사용하지 않으므로써, 퍼블리쉬 진행상황을 "Job Viewer"를 통하여 확인할 수가 없다.
    • 언제 적절하게 사용되어질수 있나?
      • 코드에서 마스터 데이타베이스에 새로운 아이템을 생성하거나 기존 아이템을 수정하고, 즉시 아이템을 Publish해야하는 상활일 경우.
  2. "PublishPipeline"을 사용하는 방법

    1
    2
    PublishContext publishContext = PublishManager.CreatePublishContext(publishOptions);
    var publishResult = PublishPipeline.Run(publishContext);
    
    • 장점
      • Pipeline에서 곧바로 publishing을 진행하기 때문에 이전 옵션보다 신속하다
    • 단점
      • 사용하는데 있어서 복잡하다
      • 메뉴얼 세팅없이는 진행상황을 보고하지 않는다.
      • 대부분의 Sitecore Configuration을 일일히 체크한다.
    • 언제 적절하게 사용되어질수 있나?
      • 개인적으로 이 방범은 추천하지 않는다. 왜냐하면 페블리쉬된 아이템을 Versioning 하는데에 문제가 생길수가 있다.
  3. "Publishing Queue"를 이용하는 방법

    1
    Sitecore.Publishing.PublishManager.AddToPublishQueue(item, ItemUpdateType.Created);
    
    • 장점
      • "PublishingQueue table"에 publishing 목록을 신속하게 추가할수가있다.
      • 중복되어진 목록은 "Incremental Publish" 모드를 통하여 자동으로 제외한다.
    • 단점
      • 만약 Content Editor에서 아이템을 퍼블리쉬할 경우, Incremental Publish 옵션을 선택해야지만 Queue에 목록되어진 아이템이 퍼블리쉬 될수가 있다.
    • 언제 적절하게 사용되어질수 있나?
      • 많은 아이템의 업데이트가 자주가 일어날 경우, 이 옵션이 사용되어질수있다.
      • 개인적으로 이 옵션을 메인 publishing 모드로 사용할 경우, 기존의 메뉴얼 퍼블리쉬 기능은 숨겨두고, Publishing Agent를 통하여 아이템을 퍼블리쉬할수 있도록 사용자에게 권하는것이 좋다.
      • *현재 진행되고 있는 프로젝트에서 Workflow와 데이타소스들을 일괄적으로 업데이트하고 퍼블리쉬하는데 있어서 이 옵션이 사용되었다.
  4. "PublishManager"를 사용하여 Publish 하는 방법

    1
    2
    3
    4
    Database master = Sitecore.Configuration.Factory.GetDatabase("master");
    Database[] targetDBs = new Database[] { Sitecore.Configuration.Factory.GetDatabase("web") };
    Language[] languages = new Language[] { Sitecore.Data.Managers.LanguageManager.GetLanguage("en") };
    Sitecore.Publishing.PublishManager.PublishSmart(master, targetDBs, languages);
    • 장점
      • Sitecore UI를 통하여 아이테을 퍼블리쉬할시, 사용되는 기본 기능이다.
      • Sitecore 로그에 퍼블리쉬 상황을 등록할수가 있으며, 그 등록 기능은 "JobManager"를 통하여 작동된다.
    • 단점
      • 비동기적으로 퍼블리쉬를 수행하여 신속하지만, 프로그램 코더에서는 현지 진행되고 있는 아이템이 잘 퍼블리쉬되고있는지 또는 아닌지를 모니터해야한다.




2017년 4월 13일 목요일

RichText Editor 태그 변경 수정하기 - Telerik's RadEditor

사이트코어에서 에디터툴로 사용되고 있는 Telerik's RadEditor를 수정하고 있는중 Jason's 블로그에서 좋은 정보를 찾았다.

RichText Editor의 에디터를 오픈하고 HTML탭에서 <b> 와 <i> 태그를 사용하면, 수정된 HTML을 저장하는 중 <b>와 <i> 태그를 <strong>, 그리고 <em> 태그로 변경을 한다.

Front-End 개발자와 프로젝트를 진행하는 중 디자이너들은 Bootstrap 아이콘을 <b>와 <i> 태그를 사용하여야 하며, RichText Editor의 필터링에 의해 많은 제약을 받는다.

아래의 코드를 <b> 와 <i>태그의 변경을 예외로 설정 할수가 있다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class RichTextEditorCustomConfiguration: Sitecore.Shell.Controls.RichTextEditor.EditorConfiguration
{
    /// <summary>
    /// Initializes a new instance of the <see cref="RichTextEditorCustomConfiguration"/> class.
    /// </summary>
    /// <param name="profile">The profile.</param>
    public RichTextEditorCustomConfiguration(Item profile)
        : base(profile)
    {
    }

    /// <summary>
    /// Setup editor filters. 
    /// </summary>
    protected override void SetupFilters()
    {
        //Disable the automatic conversion of <i> and <b> tags to <em> and <strong> for icon-* classes 
        this.Editor.DisableFilter(EditorFilters.FixUlBoldItalic);
        this.Editor.DisableFilter(EditorFilters.MozEmStrong);
        this.Editor.EnableFilter(EditorFilters.IndentHTMLContent);
        base.SetupFilters();
    }
}

아래의 .config파일을 /App_Config/Include/ 폴더에 저장하면 된다.

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <settings>
      <setting name="HtmlEditor.DefaultConfigurationType" value="MyProject.RichTextEditorCustomConfiguration, MyProject"/>
    </settings>
  </sitecore>
</configuration>


2017년 3월 22일 수요일

RSS 피드에 새로운 필드 추가하기

회사에서 Migration 프로젝트를 진행하는 중, WordPress에 포스트 되어진 Article 과 RSS Feed 정보를 사이트코어 데이타베이스로 옮겨야하는 프로젝트가 생겼다.

기존 WordPress 데이타를 RSS Feed URL을 통하든지 아니면 MySQL에서 Article에 관련된 모든 정보를 파일 (예, .csv file)로 export하여,  사이트코어 데이타베이스에 Import 하면 된다고 생각할수 있지만, 데이타베이스 구조가 다를 뿐더러, 사이트코어에서 기본사항으로 지원하는 Feed Element가 한정 되어있다.



사이트코어의 Feed element를 추가하기 위하여, 리서치하는 중 @mike_i_reynolds 의 Article (https://sitecorejunkie.com/2014/11/03/add-additional-item-fields-to-rss-feeds-generated-by-sitecore/)을 찾았으며, 포스트된 코드를 응용하여, 다이나믹하게 새로운 필드 element를 추가할수 있도록 코드를 업데이트 하였다.



  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
using System.ServiceModel.Syndication;
using Sitecore.Diagnostics;
using Sitecore.Configuration;
using Sitecore.Data.Items;
using Sitecore.Pipelines;
using Sitecore.Pipelines.RenderField;
using Sitecore.Syndication;
using System.Linq;
using System.Collections.Generic;
using System.Xml.Linq;

//// This config file must be added into /App_Include/RssFeedAddtionalFields.config
//<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
//    <sitecore>
//        <settings>
//            <!-- You can list new field names seperated by comma(,) -->
//            <setting name="RssFeedAddtionalFields.Fields.FieldNames" value="ThumbnailImage,Categories,Tags" />
//            <setting name="RssFeedAddtionalFields.Pipelines.RenderField" value="renderField" />
//        </settings>
//    </sitecore>
//</configuration>

namespace Sitecore.Custom.RSS
{
    public class NewRssFields : PublicFeed
    {
        private static List<string> FieldNames { get; set; }
        private static string RenderFieldPipelineName { get; set; }

        static NewRssFields()
        {
            FieldNames = Settings.GetSetting("RssFeedAddtionalFields.Fields.FieldNames").Split(',').ToList();
            RenderFieldPipelineName = Settings.GetSetting("RssFeedAddtionalFields.Pipelines.RenderField");
        }
 
        protected override SyndicationItem RenderItem(Item item)
        {
            SyndicationItem syndicationItem = base.RenderItem(item);
            AddHtmlToContent(syndicationItem, GetFieldHtmls(item));
            return syndicationItem;
        }

        protected virtual Dictionary<string, string> GetFieldHtmls(Item item)
        {
            if (FieldNames.Count <= 0)
            {
                return null;
            }
            return GetFieldHtmls(item, FieldNames);
        }

        private static Dictionary<string, string> GetFieldHtmls(Item item, List<string> FieldNames)
        {
            Assert.ArgumentNotNull(item, "item");
            Assert.ArgumentNotNullOrEmpty(RenderFieldPipelineName, "renderField");
            Dictionary<string, string> argsResult = new Dictionary<string, string>();

            foreach (var fieldName in FieldNames)
            {
                if (item == null)
                {
                    return argsResult;
                }
                RenderFieldArgs args = new RenderFieldArgs { Item = item, FieldName = fieldName };
                CorePipeline.Run(RenderFieldPipelineName, args);

                if (!args.Result.IsEmpty)
                {
                    string contents = args.Result.ToString();
                    string multilistItems = "";

                    // Image Field Type - Get only image path
                    if (args.FieldTypeKey == "image")
                    {
                        Sitecore.Data.Fields.ImageField iFld = args.Item.Fields[fieldName];
                        Sitecore.Resources.Media.MediaUrlOptions opt = new Sitecore.Resources.Media.MediaUrlOptions();
                        //opt.AlwaysIncludeServerUrl = true;
                        opt.AbsolutePath = true;
                        string mediaUrl = Sitecore.Resources.Media.MediaManager.GetMediaUrl(iFld.MediaItem, opt);
                        contents = mediaUrl;
                    }

                    // Multilist Field Type - Convert Item ID to Item Name
                    if (args.FieldTypeKey == "multilist")
                    {
                        List<string> itemId = args.Result.ToString().Split('|').ToList();
                        int count = 1;
                        foreach (string i in itemId)
                        {
                            Item theItem = item.Database.GetItem(Sitecore.Data.ID.Parse(i));
                            if (theItem != null)
                            {
                                multilistItems += theItem.Name;
                                if (count != itemId.Count)
                                {
                                    multilistItems += "|";
                                }
                            }
                            count++;
                        }
                        contents = multilistItems;
                    }
                    argsResult.Add(fieldName, contents);
                }
            }
            return argsResult;
        }

        protected virtual void AddHtmlToContent(SyndicationItem syndicationItem, Dictionary<string, string> getHtml)
        {
            if (!(syndicationItem.Content is TextSyndicationContent))
            {
                return;
            }
            foreach (KeyValuePair<string, string> pair in getHtml)
            {
                syndicationItem.ElementExtensions.Add(new XElement(pair.Key, new XCData(pair.Value)).CreateReader());
            }

            TextSyndicationContent content = syndicationItem.Content as TextSyndicationContent;
            syndicationItem.Content = new TextSyndicationContent("![CDATA[" + content.Text + "]]", TextSyndicationContentKind.Html);
        }
    }
}




2017년 2월 7일 화요일

로그인 중인 사용자 강제 추방 시키기

이전의 포스트의 보충 설명으로써, 사이트코어는 라이센스 속성에 따라 동시간 접속자 수 및 사이트코어 설치 수 를 제한한다. 이 부분의 대한 상세 목록은 license.xml 파일을 오픈하던지, 아니면 사이트코어의 CMS의 License Detail에서 확인할 수가 있다.

혹, 동 시간대에 제한된 유저의 수가 초과되어지면, 로그인을 시도한 사용자는 "There are too many users using the system at this time." 라는 메세지를 받게된다. 사이트코어 관리자는 현재 어떤 유저가 로그인 되어있는지 확인을 한 후, 해당 유저가 Idle 또는 Inactive 유저 상태라면, 강제퇴장을 시켜야한다. (세션 정보를 지움)

아래는 현재 로그인 중인 사용자의 목록을 확인 및 임시 유저 제한 수 증가를 시킬수가 있다.


  • Sitecore 버전이 8.0 이전 버전이라면,
    http://YourSitecoreDomain/Sitecore/shell/Applications/Login/Users/Kick.aspx

  • Sitecore 버전이 8.0 이후 버전이라면,
    http://YourSitecoreDomain/sitecore/client/Applications/LicenseOptions/KickUser


현재 로그인 중인 유저를 선택하고, "Kick off user" 버튼을 통하여 강제 추방을 시킬 수 있다.



2017년 2월 6일 월요일

사이트코어 - 비활동중인 유저의 잠김 아이템을 풀림으로 설정하기 - Sitecore

일반 웹사이트들처럼 사이트코어 CMS는 Logout을 통하여 현재 로그인한 유저의 정보를 Session에서 삭제할수가 있다. 보통 유저들은 (나 역시,) CMS 작업 후, Logout 대신 브라우저의 X버튼을 누르면, 마치 로그아웃이 된 듯 CMS를 클로즈 한다. 여기에서 하나의 문제점이 있다.

사이트코어는 SessionID에 해당 유저가 여전히 존해하는것으로 알고, Idle 유저 또는 Inactive 유저로 인식한다. 고로, 해당 유저가 CMS 작업을 마친 후, 아이템 풀림을 설정하지 않고, 브라우저를 클로즈해버리면, 다른 유저들은 해당 아이템이 Unlock 될때가지 작업을 할수가 없다.

사이트코어의 기본적인 세팅으로, 아이템을 수정하기 위해서는 유저가 받드시 아이템 잠김상태를 하여, 다른 유저가 중복적으로 수정하지 못하도록 하여야 한다. 만일, 다른 유저가 잠김 상태의 아이템 수정을 필요로 하면, 잠금을 한 유저에게 풀림을 요청하던지, 아니면 관리자 (슈퍼 어드민) 에게 요청을 해야한다.

이런 불편함을 덜 하기위하여, 이번에 강제추방(?)의 클래스를 추가하였다.

아래의 코드는 사이트코어의 Session 정보를 받아들여 Session 정보에 들어있는 유저들의 마지막 활정 시간을 가져온다. DomainAccessGuard 클래스는 유저세션 정보를 가져오는 클래스로써, 만약 특정 유저의 마지막 활동이 현재 시간보다 55분 (config 파일 설정)이 초과하였을 경우, 해당의 유저가 잠금을 한 모든 아이템을 풀림으로 설정을 한다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
namespace YourNamespace.Unlock
{
    public class UnlockItemWhenIdle
    {
        private TimeSpan maximumIdleTime;

        public UnlockItemWhenIdle(string maximumIdleTime)
        {
            this.maximumIdleTime = TimeSpan.Parse(maximumIdleTime);
        }

        public void Run()
        {
            List<DomainAccessGuard.Session> userSessionList = DomainAccessGuard.Sessions;

            if (userSessionList != null && userSessionList.Count > 0)
            {
                foreach (DomainAccessGuard.Session userSession in userSessionList.ToArray())
                {
                    TimeSpan span = (TimeSpan)(DateTime.Now - userSession.LastRequest);

                    if (span > this.maximumIdleTime)
                    {
                        string currentUserName = userSession.UserName;

                        var database = Sitecore.Configuration.Factory.GetDatabase("master");
                        Item[] items = database.SelectItems("fast:/sitecore/content//*[@__lock='%" + currentUserName + "%']");
                        
                        foreach (var item in items)
                        {
                            item.Editing.BeginEdit();
                            item.Locking.Unlock();
                            item.Editing.EndEdit();
                        }
                    }
                }
            }
        }
    }
}

아래의 코드는 Maximum inactive 시간을 설정하는 config 파일이다. 새로운 .config 파일을 만들어도 되며, 또는 Sitecore.config 혹은 web.config 파일의 스케쥴 노드에 추가하면 된다. 필자는 `/Website/App_Config/Include/` 폴더에 `YourNamespace.Unlock.config` 파일을 만들었다. 스케쥴은 5분만다 한번씩 유저 정보를 체크하는것으로 되어있으며, 마지막 활동이 55분을 초과하면 강제추방이 작동하도록 설정을 하였다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
    <sitecore>
        <scheduling>
            <!-- kick users who are idle for 55 minutes -->
            <agent type="YourNamespace.Unlock.UnlockItemWhenIdle, YourNamespace.Unlock" method="Run" interval="00:05:00">
                <param desc="maximumIdleTime">00:55:00</param>
            </agent>
        </scheduling>
    </sitecore>
</configuration>