2017년 12월 6일 수요일

사이트코어 개발자 시험버전 다운로드하기

최근 사이트코어는 XP를 처음 사용해보는 개발자들에게 60일 무료 시험버전을 제공하기 시작했다. 아래의 사이트에서 등록을 하면, 사이트코어는 60일 제한 라이센트를 제공하며, 개발자는 그 기간내에 어떻게 플랫폼의 API 및 Helix 프레임워크 사용과 플랫폼 확장 개발등을 할수가 있다. 만약 60일 기간이 만료가 되었다면, 한번 더 연장이 가능하므로 최대 120일까지 사용할수가 있다.

*혹시, 설치 및 사용에 대하여 궁금한점이 있다면 오른쪽의 메일 보내기를 통하여 글쓴이에게 연락하여 주십시오.




===================== 업데이트 =====================

최근 사이트코어는 이전에 공개하였던 개발자버전의 시험 라이센스 중단하였다. 개발자 라이센스는 사이트코어의 프리뷰 버전으로 공개하였던것으로, 2018년도에 새로운 라이센스버전을 공개할 예정이다. Waiting List에 등록을 해놓는다면 조금 더 빨리(?) 라이센스를 받고 테스트할수있는 기회가 주어진다.





2017년 11월 20일 월요일

Sitecore 9.0 Dynamic Placeholder 사용법

사이트코어는 9.0을 릴리즈하면서, DynamicPlaceholder라는 새로운 SitecoreHelper를 소개하였다.

기존의 @Html.Sitecore().Placeholder("key")와는 달리, @Html.Sitecore().DynamicPlaceholder("Key", optional parameters)는 새로운 Placehoder를 좀 더 효율적으로 생성 및 관리할수 있으며, Placeholder Key이름에 렌더링 아이템 아이디와 인덱스를 추가하여 똑같은 Placeholder key를 중보긍로 사용할수 있도록 하였다.

예: placeholderName-RenderingID-Count
content-{4C770DD9-F840-45AB-A22F-8CCAAC42F646}-0

먼저, 아래는 DynamicPlaceholder Method에서 사용가능 한 Parameter의 목록을 나열하였고, 각각 어떻게 사용되어지는지 알아보도록 하자.


DynamicPlaceholder(string placeholderName, int count = 1, int maxCount = 0, int seed = 0)
DynamicPlaceholder(string placeholderName, TagBuilder chrome, int count = 1, int maxCount = 0, int seed = 0)
DynamicPlaceholder(string placeholderName, Func<DynamicPlaceholderRenderContext, TagBuilder> chromeResolver, int count = 1, int maxCount = 0, int seed = 0)
DynamicPlaceholder(string placeholderName, Func<HtmlString, HtmlString> outputModifier, int count = 1, int maxCount = 0, int seed = 0)
DynamicPlaceholder(DynamicPlaceholderDefinition definition)


  1. DynamicPlaceholder(string placeholderName, int count = 1, int maxCount = 0, int seed = 0)

    기본 Placeholder Method처럼 @Html.Sitecore().DynamicPlaceholder("placeholder name") 처럼 사용할 수 있지만, 추가적으로 기본 선택적인 Parameter인 count, maxCount, 그리고 seed를 사용함으로써 해달 Placeholder를 제한적으로 사용할 수가 있다.

    @Html.Sitecore().DynamicPlaceholder("content", count:2)
    

    - count: 사용하고 싶은 Placeholder 수를 정함.
    - maxCount: 맥시멈 Placeholder 수를 정함. 예를들어, count가 "5"이고 maxCount를 "3"으로 정했다면, 페이지에서는 maxCount인 3개의 Placeholder만 나타남.
    - seed: Placeholder의 카운티은 "0"에서 시작되며, 특정하게 시작하는 수는 정하고 싶다면 seed를 사용하면 됨


  2. DynamicPlaceholder(string placeholderName, TagBuilder chrome, int count = 1, int maxCount = 0, int seed = 0)

    첫번째, 메소드의 형식과 달리 이번 메소드는 TagBuilder 오브젝트를 패스하여 새로운 HTML 마크업을 생성할수가 있다.

    @{
        TagBuilder newTag = new TagBuilder("div");
    
        newTag.GenerateId("unique-id-here");
        newTag.AddCssClass("custom-class-name-here anotheer-class-here");
        newTag.InnerHtml = "This DIV is built by TagBuilder";
    }
    
    @Html.Sitecore().DynamicPlaceholder("content", newTag, count: 2, seed: 5)
    
    // 또는,
    
    @Html.Sitecore().DynamicPlaceholder("content", 
                                        output =>
                                        {
                                            newTag = new TagBuilder("Div");
                                            newTag.GenerateId("unique-id-here");
                                            newTag.AddCssClass("custom-class-name-here another-class-here");
                                            return newTag;
                                        }, 
                                        count: 2,
                                        seed: 5)
    

    위의 예제처럼, 새로운 태크 생성을 위한 Argument를 메소드에 전달하였으며 seed를 "5"로 세팅함으로써 Placeholder 카운터가 "5"부터 시작한다.

    HTML Preview Output 
    <div class="custom-class-name-here another-class-here" id="unique-id-here"></div>
    <div class="custom-class-name-here another-class-here" id="unique-id-here"></div>
    

  3. DynamicPlaceholder(string placeholderName, Func<DynamicPlaceholderRenderContext, TagBuilder> chromeResolver, int count = 1, int maxCount = 0, int seed = 0)

    두번째 예제에서의 문제점은 똑같은 HTML 마크업을 생성할수는 있으나, 마크업의 Attribute (클래스 이름 또는 아이디 이름)등을 Unique하게 생성할수가 없다. 똑같은 클래스 이름이나 아이디를 사용하면 Javascript 이벤트를 불러오는데어서 Conflict 문제가 생길수가 있다. 사이트코어 Presentation 네임스페이스의 DynamicPlaceholderRenderContext 클래스를 사용하여, Placeholder의 속성값을 가져올수가 있다.

    // @View
    <div class="row">
        @functions
        {
            TagBuilder CreateColumn(DynamicPlaceholderRenderContext context)
            {
                var col = new TagBuilder("div");
                col.GenerateId("column-id-" + context.Index);
                col.AddCssClass("col-lg-" + 12/context.PlaceholdersCount));
                return col;
            }
        }
        @Html.Sitecore().DynamicPlaceholder("content", CreateColumn, count: 3, seed: 20, maxCount: 4)
    </div>
    

    위의 예제는 context.PlaceholderCount을 Argument에 세팅한 Count의 값 "3"의 불러와 새로 생성되는 "<div>" 마크업의 클래스이름 "col-lg-{count}"으로 정하도록 하였다. 또한, Unique한 ID를 생성하여 context.Index값을 차려로 나열하도록 만들었다.



    HTML Preview Output
    <div class="row">
        <div class="col-lg-4" id="column-id-0"></div>
        <div class="col-lg-4" id="column-id-1"></div>
        <div class="col-lg-4" id="column-id-2"></div>
    </div>
    

  4. DynamicPlaceholder(string placeholderName, Func<HtmlString, HtmlString> outputModifier, int count = 1, int maxCount = 0, int seed = 0)

    이번에도 3번째 예제와 비슷하게 Placeholder 속성값을 전달하여 새로운 HTML 마크업 또는 값을 생성하여, 원하고자하는 Placeholder의 마크업을 생성할수있다.

    @Html.Sitecore().DynamicPlaceholder("content", 
                                        (input, context) => new HtmlString(String.Format("<div class=\"custom-class-{1}\">{0}</div>", 
                                                            input, 
                                                            context.DynamicKey)
                                        ), 
                                        count: 50,
                                        maxCount: 3, 
                                        seed: 50)
    

    이번 예제에서는 해당 Placeholder의 DynamicKey의 값을 사용하였고, 값은 PlaceholderName-UniqueRenderingID-Count 값으로 HTML 마크업에 나타난다.

    <div class="custom-class-content-{4C770DD9-F840-45AB-A22F-8CCAAC42F646}-50"></div>
    <div class="custom-class-content-{4C770DD9-F840-45AB-A22F-8CCAAC42F646}-51"></div>
    <div class="custom-class-content-{4C770DD9-F840-45AB-A22F-8CCAAC42F646}-52"></div>
    

  5. DynamicPlaceholder(DynamicPlaceholderDefinition definition)

    마지막으로 DynamicPlaceholderDefinition 오브젝트는 지금까지 설명한 모든 옵션을 통합한 클래스이다. 새로운 오브젝트에 Placeholder 이름을 정하고, 필요한 인덱스값을 정해주며, Placeholder의 속성값을 HTML 마크업의 Unique한 값으로 정할수가 있다.

    @Html.Sitecore().DynamicPlaceholder(new DynamicPlaceholderDefinition("content")
    {
        Count = 5,
        MaxCount = 10,
        Seed = 100,
        OutputModifier = (input, context) => new HtmlString(String.Format("<div class=\"definition-{1}\">{0}</div>", input, context.Index))
    })
    


    <div class="definition-0">content-{4C770DD9-F840-45AB-A22F-8CCAAC42F646}-100</div>
    <div class="definition-1">content-{4C770DD9-F840-45AB-A22F-8CCAAC42F646}-101</div>
    <div class="definition-2">content-{4C770DD9-F840-45AB-A22F-8CCAAC42F646}-102</div>
    <div class="definition-3">content-{4C770DD9-F840-45AB-A22F-8CCAAC42F646}-103</div>
    <div class="definition-4">content-{4C770DD9-F840-45AB-A22F-8CCAAC42F646}-104</div>
    


2017년 11월 7일 화요일

Sitecore 9 Forms의 새로운 기능

이전 포스트에 소개한것처럼, 사이트코어 9 에서는 Forms 라는 모듈이 소개되었다.

기존에 사용하던 WFFM (Web Forms For Marketers)를 대체하는 모듈로써, WFFM보다 더 진보(?) 되어진 모듈이라고 할수있으며, Forms는 Web Form이 아닌 MVC 기반으로 만들어져 새로운 UI/UX를 통하여 Non-Technical 유저에게도 사용하기 편하도록 만들어졌다.

기본적으로 HTML Element를 쉽게 추가하고 목록을 변경할수 있도록 Drag & Drop 기능이 소개되었으며, 아래는 Forms 모듈의 기능을 나열하였다.


  • 웹사이트 Visitor들의 폼 페이지를 렌더링 수
  • 필드 검증절차를 통하여, 오류 빈도 및 소요한 시간 분석
  • 멀티 페이지 (예, 페이지1 -> 다음 -> 페이지2 -> 다음 -> 페이지 3 -> 등록) 등록 가능
  • Submit 버턴을 통하여 마케팅 캠페인과 호환
  • Javascript 및 CSS 등 추가 리소스 등록가능
  • CSS Class 필드를 통하여, 새로운 스타일 생성가능
  • Forms 템플릿을 생성하여 많은 페이지를 쉽게 생성할수있음
  • Ajax를 이용하여 페이지 로딩없이 페이지를 이동할수있음
  • 필요에 따라 추가적인 필드 검증과 액션버튼 이벤트 생성 가능

사이트코어 - Sitecore 9 Forms Tab

사이트코어 - Sitecore 9 Forms



* 사이트코어는 9.0까지 WFFM를 지원하지만, 버전 9.1부터는 WFFM Support를 중단할 예정이다.



2017년 10월 26일 목요일

Sitecore 9.0 새로운 기능 및 기술

저번 주에 라스베가스에서 열린 Sitecore Symposium 2017을 무사히 마치고 돌아왔며, 이번 Symposium에서 사이트코어는 Sitecore Experience Cloud 9.0 릴리즈 하였다.

개인적으로 이번 토론회는 처음 참석하는 것이므로 무엇보다 기대가 많았다. Sitecore MVP로써 Sitecore 9.0을 미리 시험할수 있었으며, MVP들의 피드백을 토대로 사이트코어는 새로운 SIF (Sitecore Installation Framework) 보완하는 계기도 되었다.

아래는 이번 Sitecore 9.0 에서 적용되는 새로운 중요 기술 및 기능들이며, 차후 각각 기술에 대한 상세한 설명을 포스트 할 예정이다.

xConnect

사이트코어 XP와 xDB 사이에 존재하는 새로운 Service Layer로써, xConnect는 사이트코어가 아닌 다른 플랫폼 및 디바이스와 연동 할수가 있다. xConnect는 Collection과 Search 서비스로 구성되어 있으며, 다른 플랫폼과 디바이스는 xConnect를 통하여 사이트코어 xDB의 데이타를 불러올 수 있으며, 또한 xConnect는 다른 플랫폼의 데이타를 xDB를 가져올수가 있다.

SQL Server

Sitecore 7.5부터 적용해오던 MongoDB 를 필수요소에서 제외하였다. SQL Server를 기본 Provider로 설정되어졌으며, MongoDB 는 선택적인 항목이 되었다. 사용자 및 개발자 환경에 따라 SQL Server, SQL Azure, CosmosDB, 또는 MongoDB를 사용할수있으며, 어떤 DB 타입 상관없이 xConnect는 이 모든 데이타베이스 서버와의 호환되며 작동한다.


Forms

사이트코어 8.2까지 WFFM (Web Form For Marketers) 모듈을 사용하여, 온라인 폼 페이지를 만들고, xMarketing과 컨넥하여 사용자 및 유저의 정보를 모아왔으나, 9.0부터는 새로운 Form 빌더를 Sitecore XP (Experience Platform)에 추가였으며, UI/UX 역시 더 세련(?)되어 만들어졌다.. 이로써, 새로운 폼 모듈을 추가적으로 설치 하지 않아도 된다. Sitecore 9.0까지는 계속 WFFM을 지원하고 서포트를 하지만, 9.1부터는 WFFM 모듈 및 서포트를 중단할 계획인다.

Marketing Automation

기존에 사용해오던, Engagement Plan를 보완한 기능이며, Drag/Drop을 통하여 쉽게 마케팅 로직을 생성할수있다. 손 쉬운 UI/UX 바탕으로 마케터는 쉽게 자동화된 마케팅 캠페인을 생성할 수 있다.

Search

Sitecore 8.2까지 기본 서치엔진이던 Lucene 사용을 중단하고, 기본 엔진으로 Solr 또는 Azure Search를 사용한다. Lucene를 파일을 기반으로한 서치엔진으로 모든 데이타가 파일형식으로 저장되는것으로 반해, Solr 은 웹어플리케이션으로써 Lucene 을 탑재하여 다양한 API를 제공하며, 서치엔진은 사용 용도에 맞게 구현할수가 있다. 
Sitecore 9.0는 OWIN authentication 미드웨어를 통하여 새로운 로그인 방식을 구현한다. 기존의 LDAP 모듈을 이용하여 Active Directory의 데이터를 Sitecore CMS와 통합하여 로그인을 구현하였지만, 이번 Federated Authentication을 사용함으로써 아래의 다양한 플랫폼과 연동시킬수 있다. 예를 들면, Sitecore의 로그인을 Azure AD와 통합을 할수가 있다.
  • OpenId Connect (AzureAD, identity server) 
  • Microsoft Account 
  • Google
  • Facebook 
  • Twitter 
  • WS-Trust (Web Services Trust Language)
  • OAuth 
  • SAML


Dynamic Placeholder

이전부터 많이 문제가 제기되어왔던 기존 Placeholder에 대한 기능은 많이 제약이 따랐다. 예를 들어 렌더링 아이템에는 같은 이름의 Placeholder를 사용하지 못하였으며, 이로인하여 각각 새로운 Placeholder Setting을 적용해야만 했다. 하지만, 이름처럼 이를 다이나믹하게 보안하여 하나의 Placeholder Setting 아이템을 다양하게 구현하도록 만들었다. 차후, 여기에 대한 자세한 설명을 포스트하도록 하겠다.





2017년 10월 4일 수요일

사이트코어 필드 아이템 Versioned, Unversioned 그리고 Shared 속성 적용하기

이번에는 사이트코어의 기본기능에 대하여 소개해보도록 하겠다.

모든 페이지 아이템은 탬플릿에 의하여 생성되며, 탬플릿을 생성할시 유저는 필드 이름과 필드 타입을 설정하여야 한다. 탬플릿을 생성하고 필드를 추가하면 해당 필드의 설정에 Unversioned 와 Shared라는 체크박스가 있다.



필드를 새로 성성하면, 자동적으로 두 체크박스를 선택되어지지 않으므로써 Versioned라는 세팅으로 필드를 생성한다.

먼저, 페이지 아이템에 2개의 언어가 설정되어있고, 각각 2개 및 3개의 페이지 아이템 버전이 있다고 생각하자.
필드 이름: "Test", 필드 기본값: "Test Version"
    • PageItem 버전1 : 영어
    • PageItem 버전2 : 영어
    • PageItem 버전3 : 영어
    • PageItem 버전1 : 일본어
    • PageItem 버전2 : 일본어


아래는 각각 설정이 어떻게 적용되어지는 알수가 있다.

  • Unversioned (적용 안함), Shared (적용 안함)
    필드에 새로운 값이 적용되면, 오직 해당 아이템 버전과 해당 언어에만 새로운 값이 적용된다. 예를 들어, "PageItem 버전 2: 영어" 아이템의 "Test" 필드 값을 "Test Version 2"로 설정할시,
    • PageItem 버전1 : 영어 - 필드값: "Test Version"
    • PageItem 버전2 : 영어 - 필드값: "Test Version 2"
    • PageItem 버전3 : 영어 - 필드값: "Test Version"
    • PageItem 버전1 : 일본어 - 필드값: "Test Version"
    • PageItem 버전2 : 일본어 - 필드값: "Test Version"

  • Unversioned (적용), Shared (적용 안함)
    필드에 새로운 값이 적용되면, 오직 해당 언어의 모든 아이템 버전에 새로운 값이 적용된다. 예를 들어, "PageItem 버전 2: 영어" 아이템의 "Test" 필드 값을 "Test Version 2"로 설정할시,
    • PageItem 버전1 : 영어 - 필드값: "Test Version 2"
    • PageItem 버전2 : 영어 - 필드값: "Test Version 2"
    • PageItem 버전3 : 영어 - 필드값: "Test Version 2"
    • PageItem 버전1 : 일본어 - 필드값: "Test Version"
    • PageItem 버전2 : 일본어 - 필드값: "Test Version"

  • Unversioned (적용 안함), Shared (적용)

    필드에 새로운 값이 적용되면, 모든 언어의 모든 아이템 버전에 새로운 값이 적용된다. 예를 들어, "PageItem 버전 2: 영어" 아이템의 "Test" 필드 값을 "Test Version 2"로 설정할시,
    • PageItem 버전1 : 영어 - 필드값: "Test Version 2"
    • PageItem 버전2 : 영어 - 필드값: "Test Version 2"
    • PageItem 버전3 : 영어 - 필드값: "Test Version 2"
    • PageItem 버전1 : 일본어 - 필드값: "Test Version 2"
    • PageItem 버전2 : 일본어 - 필드값: "Test Version 2"

2017년 8월 31일 목요일

사이트코어 크롬 익스텐션 (Chrome Extension)

사이트코어 CMS를 사용하다보면 UI 디자인부분 또는 자바스크립 기능에서 불편함 점이있다.

예를 들면, 데스크탑 모드에서 작업을하다보면 Maser 또는 Web DB를 이동해야하는 경우가 간혹(?) 발생하며, 또는 Content Editor에서 아이템 정보를 일괄적으로 늘려야 하는경우도 생긴다.

추가적으로 많은 기능을 필요로화 하지만, Siteocre에서는 모든것은 추가할수가 없다. 이것을 보완하기 위하여, 필자는 크롬 익스텐션에서 Sitecore Extension 이라는 플러그인을 사용한다.

플러그인에는 아주 많은 기능이 있어 모두 다 나열하지는 못하지만, 필자가 생각하는 가장 좋은 기능은 Ctrl+Space key를 사용하여 Shortcut 기능을 수행할수가 있다.

Install at Chrome: Sitecore Extension





 

2017년 8월 30일 수요일

Placeholder 컨트롤 수 제한하기

xEditor를 사용하다보면, 사이트코어 사용자의 권한 설정에 따라 특정한 Placeholder에 하나 이상의 컴포넌트를 제한해야하는 경우가 있다.

또한, 특정한 필드가 각각의 페이지가 아닌, 템플릿 __Standard Values에서 일괄적으로 변경을 해야하는 경우가 있는데, 이럴경우 사이트코어의 GetChromeDataArgs 클래스 파이프라인을 통하여 제한설정을 할수가 있다.


 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
using System;
using System.Collections.Generic;
using System.Linq;
using Sitecore.Pipelines.GetChromeData;
using System.Text.RegularExpressions;
using Sitecore.Diagnostics;
using Sitecore.Data.Items;
using Sitecore.Security.Accounts;
using Sitecore.Layouts;
using Sitecore.Data;
using Sitecore.Data.Fields;

namespace Sitecore.Placeholder.Restriction
{
    public class RemoveDeleteButton : GetPlaceholderChromeData
    {
        public override void Process(GetChromeDataArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            Assert.IsNotNull(args.ChromeData, "Chrome Data");
            if ("placeholder".Equals(args.ChromeType, StringComparison.OrdinalIgnoreCase))
            {
                string placeholderKey = args.CustomData["placeHolderKey"] as string;

                Sitecore.Data.Fields.LayoutField layoutField = new Sitecore.Data.Fields.LayoutField(Context.Item.Fields[Sitecore.FieldIDs.FinalLayoutField]);
                LayoutDefinition layoutDefinition = LayoutDefinition.Parse(layoutField.Value);
                DeviceDefinition deviceDefinition = layoutDefinition.GetDevice(Context.Device.ID.ToString());

                // Initial setting path is "/sitecore/system/Modules/Placeholder Restriction Manager/Remove Delete Button"
                Item removeDeleteButtonItem = Context.Item.Database.GetItem("/sitecore/system/Modules/Placeholder Restriction/Remove Delete Button");

                if (removeDeleteButtonItem == null)
                {
                    Assert.IsNull(removeDeleteButtonItem, "Item is null");
                    return;
                }

                MultilistField listOfRenderingItemsDelete = removeDeleteButtonItem.Fields["List of Rendering Items"];
                string listOfUserRolesDelete = removeDeleteButtonItem.Fields["List of Users and Roles"].Value.Trim();
                List<string> eachListOfUserRolesDelete = !String.IsNullOrEmpty(listOfUserRolesDelete) ? listOfUserRolesDelete.Split(';').ToList() : null;

                // Remove "Delete" button in placeholder
                if (eachListOfUserRolesDelete != null && eachListOfUserRolesDelete.Select(e => User.Current.IsInRole(e) || User.Current.LocalName.Equals(e)).Any())
                {
                    args.ChromeData.Custom["removeAddHereButton"] = true;
                    foreach (ID renderingItemId in listOfRenderingItemsDelete.TargetIDs)
                    {
                        RenderingItem r = RenderingItem.GetItem(renderingItemId, Context.Data.Database, true);
                        string renderingName = Regex.Replace(r.Name.ToLower(), @"\s+", "");
                        string displayName = Regex.Replace(args.ChromeData.DisplayName.ToLower(), @"\s+", "");

                        // Only when rendering name matches to its display name
                        // Only when rendering item's display name contains rendering name
                        if (renderingName == displayName || displayName.Contains(renderingName))
                        {
                            args.ChromeData.Custom["editable"] = false;
                        }
                    }
                }
            }
        }
    }
}


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <getChromeData>
        <processor
          type="Sitecore.Placeholder.Restriction.RemoveDeleteButton, Sitecore.Placeholder.Restriction"
          patch:after="processor[@type='Sitecore.Pipelines.GetChromeData.GetPlaceholderChromeData, Sitecore.Kernel']"/>
      </getChromeData>
    </pipelines>
  </sitecore>
</configuration>

2017년 8월 8일 화요일

사이트코어와 Slack (슬랙) API 연동하기

이번에는 사이트코어와 Slack API 연동에 대하여 알아보도록 하겠다.

Slack API은 메신저와 같은 커뮤니케이션 공간으로써, 많은 유저들이 하나의 커뮤니티 공간에서 많은 채널 (채팅방)을 서로 공유하며, 정보를 주고 받을 수가 있다.

개발자들 및 IT 유저들 사이에는 이미 널리 알려진 웹어플리케이션으로써, 필자가 일하고 있는 회사에서도 역시 직원들의 커뮤니티 공간으로 잘 활용을 하고 있다.

이전에 포스트에서 올려놓았던 Workflow Related Item 업데이트 하기에서 Custom Workflow를 소개하였으며, Workflow가 업데이트 될때마다 관리자 및 검증자가 업데이트 되어진 정보를 이메일로 받는 동시에, Slack API를 통하여 Slack 채널에도 업데이트된 정보가 올려지도록 클래스를 많들었다.

우선, Real-Time message를 전송하기 위하여, Slack 플러그인 (Incoming WebHooks)을 추가적으로 설치한 후,  Webhook URL의 값을 WebClient의 UploadString 메쏘드 파라미터로 입력을 한다.


 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
public static class PostSlackMessage
{
    public static void PostMessage(Item pageItem, User user, bool isPublished, string comment = null)
    {
        if (pageItem == null) {
            Sitecore.Diagnostics.Assert.IsNull(null, "Page Item can't be found!");
        }
        if (user == null) {
            Sitecore.Diagnostics.Assert.IsNull(null, "User can't be found!");
        }

        string pagePath = pageItem.Paths.Path.ToString().ToLower();
        pagePath = pagePath.Replace(Sitecore.Context.Data.Site.RootPath.ToLower(), "");
        pagePath = pagePath.Replace(Sitecore.Context.Data.Site.StartItem.ToLower(), "");

        string requestedByEmail = user.Profile.Email;
        string requestedByFullName = user.Profile.FullName;
        string updatedByEmail = User.FromName(pageItem.Statistics.UpdatedBy, true).Profile.Email;
        string updatedByFullName = User.FromName(pageItem.Statistics.UpdatedBy, true).Profile.FullName;
        string requestedDate = DateTime.Now.ToString("g");
        
        string pretextMsg = "Approval Request :file_cabinet:";
        string fallbackMsg = "ReferenceError - UI is not defined";
        string titleMsg = "<https://yoursitecms.com" + pagePath + "|" + pageItem.Fields["Page Title"].Value + "> - " + pagePath;
        string commentMsg = comment;
        string colorMsg = "#FF8000";
        
        if (isPublished)
        {
            pretextMsg = "Approved and Published :white_check_mark:";
            titleMsg = "<http://yoursitecds.com" + pagePath + "|" + pageItem.Fields["Page Title"].Value + ">";
            colorMsg = "#36a64f";
        }
        var field = new[]{
                new {
                    title = (isPublished) ? "Approved by" : "Requested by",
                    value = "<mailto:" + requestedByEmail + "|" + requestedByFullName + ">",
                    @short = true
                },
                new {
                    title = "Updated by",
                    value = "<mailto:" + updatedByEmail + "|" + updatedByFullName + ">",
                    @short = true
                },
                new {
                    title = (isPublished) ? "Approved Date" : "Requested Date",
                    value = requestedDate,
                    @short = true
                },
                new {
                    title = "Page Version",
                    value = pageItem.Version.Number.ToString(),
                    @short = true
                }
        };

        var message = new
        {
            username = "Mr. Sitecore",
            channel = "G690XEDK4", // Private Channel "Sitcore Publishing"
            icon_emoji = ":sitecore:",
            attachments = new[]{ 
                new {
                    pretext = pretextMsg,
                    fallback = fallbackMsg,
           title = titleMsg,
           text = commentMsg,
                    color = colorMsg,
                    fields = field
                }
            }
        };

        var json = JsonConvert.SerializeObject(message);

        var webClient = new WebClient();
        webClient.Headers[HttpRequestHeader.ContentType] = "application/json";
        webClient.UploadString("https://hooks.slack.com/services/your/code/here", json);
    }
}



1
2
// Post Slack Message
PostSlackMessage.PostMessage(args.DataItem, User.Current, true, args.CommentFields["Comments"].ToString());








2017년 7월 21일 금요일

사이트코어 토론회 - Sitecore Symposium 2017 in Las Vegas, USA

사이트코어는 매년(간혹, 2년마다) Sitecore Symposium 2017을 개최한다.

작년, 2016년도에는 미국 New Orleans에서 개최되었으며, 이번 해에는 미국 Las Vegas에서 개최된다. 기간은 10월 16일부터 10월 19일까지이다. 사이트코어는 Symposium을 개최할때마다, 차후 릴리즈될 새로운 기술 및 기능들을 소개하며, 테크놀리지별 트레이닝 부스를 만들어 많은 유저들이 새로운 정보 및 기술을 배우고 공유할수 있는 자리를 마련한다.

글쓴이는 처음으로 미국에서 개최되는 Sitecore Symposium을 참석할 예정이며, 혹시 미국 또는 라스베가스로 방문/여행할 계획이 있는분들은 아래의 링크 (100불 할인)를 통하여, 참가 신청을할 수 있다.

아래는 토론회 정보 및 메인 주제들이며, 해당 주제에 맞는 다양한 이벤트 및 강의가 진행될 예정이다.

  • Analytics (분석)
  • Commerce (상업)
  • Content management (컨텐츠 관리)
  • Contextual Intelligence / 360 View of the Customer (상황지능 및 적용)
  • Deploying Sitecore on Cloud (사이트코어 클라우드)
  • Digital Transformation (디지털화)
  • Email (이메일 마케팅)
  • Interactive Brand Experiences (IoT, Virtual Reality, Augmented Reality) (인터렉션 뉴테크놀로지)
  • Managing multilingual content at scale (다국어 컨텐츠 관리 및 최적화)
  • Mobile Experience (모바일)
  • Personalization (개인 정보화)
  • Social (소셜 네크워크)
  • Testing and Optimization (테스트 및 최적화)


등록
https://sitecoresymposium2017.smarteventscloud.com/portal/registration/JKIM

기간:  2017년 10월 16일 ~  2017년 10월 19일
장소: The Mirage Hotel & Casino in Las Vegas, NV

사이트코어 토론회 - Sitecore Symposium 2017




2017년 7월 10일 월요일

사이트코어 WFFM (Web Forms for Marketers)

이번에는 지금까지 소개하지 못한 WFFM (Web Forms For Marketer)에 대하여 소개해보도록 하겠다.

웹사이트를 개발 및 운영을 하다보면, Client의 요청으로 인하여 온라인 웹폼을 생성해야하는 경우가 있다. 이럴경우, ASPX 또는 MVC (Razor) 를 통하여 온라인 폼을 생성하고, 해당 컴포넌트를 사이트코어의 렌더링 아이템으로 적용을 시켜야한다. 이런 추가적인 컴포넌트 생성을 좀 더 효울적으로 하고자, 사이트코어는 WFFM라는 모듈을 런칭하였다. WFFM은 사이트코어 버전 8.0부터 소개되었으며, 해당 링크를 통하여 설치방법및 패키지를 다운받을수 있다.



WFFM을 설치는 CM와 CD, 그리고 마케터 리포트를 포함하며, CM/CD 패키지를 설치하기전에 마케터 리포트의 패키지를 푼다. 파일 시스템에 복사되어진 .sql 파일을 Analytics DB에 실행시켜 WFFM Reporting이 작동하도록 설정을 한다. 위의 링크를 통하면, 설치 매뉴어가 자세히 나와있으므로 참고할수 있다.

CM 패키지를 설치하고 나면, Modules tree에 "Web Forms for Marketers"가 생성되며, Sample Forms 통하여, 어떻게 WFFM를 생성하고 디자인하는지 테스트 할수가 있다.










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);
        }
    }
}