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>



2016년 12월 23일 금요일

Sitecore 8.2의 xEditor 새로운 기능

사이트코어는 이번 8.2 버전에서 xEditor에 새로운 기능 몇몇을 추가하였다. 중요하며 아주 유용한 기능을 살펴보겠다.
  1. xEditor에서 어떤 페이지에서 선택되어진 렌더링 아이템을 사용하고 있는지 알수있다.



  2. 이전 버전에서는 오직 페이지 아이템만이 Workflow의 기능을 xEditor에서 사용할수 있었지만, 8.2부터는 렌더링 아이템에 적용되어진 Workflow를 개별적으로 사용할 수가 있다.



  3. "Presentation" 탬을 통하여, "Shared Layout" 과 "Final Layout"을 비교할수 있으며, 해당 페이지와 연관된 페이지 또는 아이템의 목록을 볼 수가 있다.



  4. "Versions" 탭을 통하여, 현재 페이지에 새로운 버전을 추가하거나 다른 버전으로 Roll-Back을 할 수가 있다.




2016년 12월 2일 금요일

사이트코어 xEditor에 휴지통 버튼 만들기

사이트코어 사용자 제한 설정에 있어서, 관리자 및 플랫폼 Provider는 사용자가 Content Editor 또는 xEditor 를 제한적으로 사용하도록 설정할수있다.

하나의 예를 들면, 사이트코어 사용자가 컨텐츠 관리(수정)을 하는데 있어서, 아이템을 삭제하고 또 다시 복구해야하는 경우가 간혹 생기는데, 이럴 경우 사용자는 Content Editor 또는 Dashboard로 이동 후, Recycle Bin (휴지통) 팝업 창을 열어야한다. 이런 불편함을 덜기 위하여, 이번 포스팅에서는 xEditor에 Recycle Bin 버턴을 만들어 xEditor에서 바로 Recycle Bin 창을 오픈할 수 있도록 해보겠다.

먼저, 대부분의 사이트코어 버턴은 'Sitecore.Shell.Framework.Commands.Command' 클래스를 통하여 명령어가 실행된다. 새로운 Visual Studio 클래스 프로젝트를 생성하여 'OpenRecycleBin' 이라는 클래스를 만들어 준다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
namespace MySitecore.Commands
{
    // Recycle Bin Command
    public class OpenRecycleBin : Sitecore.Shell.Framework.Commands.Command
    {
        public override void Execute(Sitecore.Shell.Framework.Commands.CommandContext context)
        {
            Sitecore.Shell.Framework.Windows.RunShortcut(Sitecore.Data.ID.Parse("{A2CF861E-77AB-4317-A72B-2F33D942520E}"));
        }
    }
}

'RunShortcut' method에서 아이템 ID는 아래의 사진처럼 'core' 데이타베이스의 Shortcut 아이템 ID 이다.



해당 프로젝트를 Build/Compile 한 후, 'MySitecore.Commands.dll' 파일을 Sitecore의 bin 폴더에 저장한 후, '/Website/App_Config/' 폴더에서 'Commands.config' 파일에 새로운 Command를 아래처럼 추가한다.


<command name="contenteditor:openrecyclebin" type="MySitecore.Commands.openrecyclebin, MySitecore.Commands"/>

이젠, 마지막으로 'Core' 데이타베이스에서 xEditor 상단 툴바에 들어가 버턴을 만들어 보기로 하자.

사이트코어 CMS에서 'Core' DB로 변경 후, 원하는 섹션에 새로운 버튼 아이템을 만든다. 저의 경우 선택한 경로는 "Advanced" 탭의 제일 마지막 버턴으로 정하였다.



My New Item 이라는 Chunk 아이템 (/sitecore/templates/System/Ribbon/Chunk) 아래, 'Recycle  Bin' 이라는 Large Button을 만들고, 가장 중요한 'Click' 필드에 사진처럼 기존에 만들었던 명령어 ('contenteditor:openrecyclebin')를 넣어준다.

이 모든것이 완료되고 나면, xEditor에서 새롭게 만들어진 Recycle Bin 버턴을 볼수 있다.





2016년 11월 29일 화요일

Sitecore 패키지 설치 에러

최근 Sitecore Package Wizard를 통하여 패키지를 만든 후, Install 하는 과정에서 "The File exists" 라는 오류가 발생하였다.

There was an Exception that occured on sitecore page:/sitecore/shell/applications/tools/installer/installationwizard

Exception: Exception of type 'System.Web.HttpUnhandledException' was thrown.

Stack:
at System.Web.UI.Page.HandleError(Exception e)
at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
at System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
at System.Web.UI.Page.ProcessRequest()
at System.Web.UI.Page.ProcessRequest(HttpContext context)
at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)


Inner Exception:
Message: The file exists.
Stack:
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.Path.InternalGetTempFileName(Boolean checkHost)
at Sitecore.Install.Zip.PackageReader.Populate(ISink`1 sink)
at Sitecore.Shell.Applications.Install.Dialogs.InstallPackage.InstallPackageForm.LoadPackage()
at Sitecore.Shell.Applications.Install.Dialogs.InstallPackage.InstallPackageForm.ActivePageChanging(String page, String& newpage)
at Sitecore.Web.UI.Pages.WizardForm.set_Active(String value)


URL Scope:
No params found

Form Scope:
=
__PARAMETERS=
__EVENTTARGET=NextButton
__EVENTARGUMENT=
__SOURCE=NextButton
__EVENTTYPE=click

패키지를 업로드 한 후, "Next" 버튼을 누른면 오류가 발생하며, 문제의 원인은 "C:\Windows\Temp" 부터 발생한다. "Temp" 폴더에 65,000개 이상의 파일이 존재할경우 이런 오류가 생기면, 불필요한 파일을 삭제하면 문제는 해결된다.

이런 문제가 지속적으로 발생한다면, 윈도우 스케쥴러를 통하여 주기적으로 해당폴더의 파일을 자동적으로 삭제 할 수가 있다.


2016년 9월 28일 수요일

Sitecore Query.MaxItems - 제한된 Query 늘이기

이전에 Dynamic Placeholder를 사용하는법에 대하여 포스트를 한적이 있다.
Placeholder는 컨텐츠를 제한되어진 범위내에 사용할수있도록 해주어, 사용자가 컨텐츠를 손쉽게 업데이트할수 있도록 유용하게 쓰인다.

하지만, 큰 프로젝트를 작업하는데 있어서 많은 Placeholder Setting Item이 필요하고, 이것을 일일히 템플릿 Layout에 적용할때는 상당한 시간이 걸린다. Sitecore는 Query Performance를 최소화하기 위하여 한번에 Sitecore Query API를 리딩할수있는 아이템을 수 (Query.MaxItems)를 "100"으로 제한해두며, 해당 정보는 Placeholder Cache에 저장한다. 이로 인하여, Placeholder Setting Item의 수가 100 이상이면, 해당 Request는 Placeholder Cache에 저장하지 않는다.

sitecore.config 파일에서 `<setting name="Query.MaxItems" value="100"/>`의 value 값을 변경하면 쉽게 문제를 해결할수가 있다.

해당 Placeholder에 렌더링 아이템을 추가해 놓았지만, 등록된 아이템을 볼수가 없다.


Value 값을 "1000"으로 업데이트 한 후, 등록되어진 아이템을 Placeholder에서 볼수가 있다.


1
<setting name="Query.MaxItems" value="1000"/>