サイトコアは通常 プレビュー画面でアイテムがどのようにレンダリングされるかを確認し、アイテムを外部公開用のパブリッシュターゲット(Webデータベース) にパブリッシュします。

ユーザーライセンス費用の問題で ライセンス数が不足し、サイトコアユーザーインタフェースにログインしてページエディターのプレビュー機能を利用したページを事前確認できなかったり、実際のモバイルデバイスを利用して事前にページがどのように表示されるか確認したいという要望により事前確認用のサイト(本記事では テスト公開用サイト と記載します。) にアイテムをパブリッシュしたい場合があります。

サイトコア既定の実装では、ワークフローで管理されているアイテムは最終状態でなければアイテムはパブリッシュできません。逆に、最終状態になるとテスト公開用サイトにパブリッシュしたいだけなのにもかかわらず、公開用のパブリッシュターゲットにたいしてサイトのパブッシュを行うと本番環境の外部公開用サイト(Webデータベース)にアイテムがパブリッシュされます。

本来、アイテムをパブリッシュした場合にモバイルデバイスなどでどのようのページが表示されるかを確認したい場合、ステージング環境で確認すべきです。実際には、予算の関係で検証環境を用意できず、本記事でご紹介するようなテスト公開サイトを使用する場合があります。

上記要望を解決するためには、テスト公開用のパブリッシュターゲットに最終状態ではないアイテムをパブリッシュできる必要があります。そんなことできるんかいなと思っていたのですが、素晴らしいことに Publish to pre-production web database に本機能を実現する記事が公開されていました。せっかくなので私も試してみましたので覚書を記載します。

上記リンクや本記事で使用する方法は  EventQueue が有効な場合は(CM 環境と CD 環境が別々の環境などが該当します)ではうまく動作しませんので注意してください。EventQueueが有効とは、Web.config の <setting name="EnableEventQueues" value="true"/> となっている環境になります。

詳細は割愛しますが、EventQueue テーブルを経由する関係で 下記で説明する ExtendedPublishOptions クラスのインスタンスの情報がロストされて PublishOptions クラスになるためです。

検証環境

  • Sitecore CMS 6.5 Service Pack 1
  • 外部公開用、テスト公開用サイトともに1つのサーバーを使用

【重要】2014/05/21 追記 Sitecore 7.2 からプレビューパブリッシュターゲットの機能が提供されています。
今後はプレビューパブリッシュターゲットの機能を使用してください

1. テスト公開用のパブリッシュターゲットの定義

今回はテスト効果用のパブリッシュターゲットとして web2 というパブリッシュターゲットを作成することにしました。

手っ取り早くパブリッシュターゲットを定義するために、 Web.config の <databases> タグの <database id="web" .. をコピーして id="web2" というパブリッシュターゲット定義します。

web2 パブリッシュターゲット用に web2 という名前で App_Config/ConnectionStrings.config に接続文字列を追加します。 web 用の接続文字列をコピペし、データベース名のみこれからつくるパブリッシュターゲット用のデータベース名に変更しました。

SQL Server Management Studio を使用して、 SQL Server に接続します。 webデータベースをバックアップし、web2データベース用に別名でリストアします。

データベースの準備ができましたので、最後にパブリッシュターゲットの定義アイテムを master データベースに作成します。/sitecore/システム/パブリッシュターゲット を選択し、挿入オプションからパブリッシュターゲットの定義アイテムを作成します(下図参照)。ターゲットデータベース フィールドに web2 を入力して保存します。

以上でテスト公開用の パブリッシュターゲット の準備ができました。

2. テスト公開用サイトの定義

パブリッシュターゲットweb2 を使用して アイテムを表示するための テスト公開用サイトの定義をしていきます。 Web.config を編集し v65sp1stage とうホスト名でアクセスした場合、web2パブリッシュターゲットを使用する テスト公開用サイトの定義を追加します。

Web.config の <sites タグ内の <site name="website" のサイトの定義をコピーして、name="website"のタグの上に貼り付けます。siteの定義で name="website2"に変更しました。今回はv65sp1stage というホスト名でアクセスされた場合に web2データベースを使用する テスト公開用サイトを使用するので、 name属性のほか、 language="ja-jp" hostName="v65sp1stage" を追加し、database="web2" に変更しました。

上記の他、 hosts ファイルにv65sp1stageのエントリを追加。IISマネージャー上のサイトコアをセットアップした サイトのバインドの設定を編集し、下図のようにバインドを追加しておきます。

パブリッシュ時に今回作成したサイト(website2) のキャッシュをクリアする設定をしておきます。Web.configのevent タグの name="publish:end"と name="publish:end:remote" に website2 をクリアするよう<site>website2</site>のタグを追加しました。

以上で テスト公開用にサイトの設定は完了です。

3.パブリッシュクラスの作成

まず、最終状態かどうかを考慮するための RequireApproval プロパティを持つクラスExtendedPublishOptionsを作成する。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Sitecore.Data;
using Sitecore.Publishing;
using Sitecore.Globalization;

namespace SC.Web.Core
{
    /// <summary>
    /// 既定のPublishOptionsに最終状態考慮用のプロパティを追加したオプションクラス
    /// </summary>
    public class ExtendedPublishOptions : Sitecore.Publishing.PublishOptions
    {
        public bool RequireApproval { get; set; }
        /// <summary>
        /// PublishOptions のコンストラクタに requireApproval を追加したコンストラクタ
        /// </summary>
        public ExtendedPublishOptions(Database sourceDatabase, Database targetDatabase, PublishMode mode, Language language, DateTime publishDate, bool requireApproval)
            : base(sourceDatabase, targetDatabase, mode, language, publishDate)
        {
            this.RequireApproval = requireApproval;
        }
    }
}

ExtendedPublishOptions をオプションクラスとして受け取る カスタムの PublishHelper クラス ExtendedPublishHelperを作成します。 ExtendedPublishHelperでは  GetVersionToPublish メソッドをオーバーライドし、 ExtendedPublishOptions を使用してPublishHelper クラス(ExtendedPublishHelperクラス )を生成した場合、 最終状態かどうかを考慮してパブリッシュ可能なアイテムを取得するようにします。 デフォルトのPublishHelperでは必ず最終状態であるアイテムを取得するようハードコードされています(GetValidVersionの第二引数が必ずtrueになっている。)。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace SC.Web.Core
{
    public class ExtendedPublishHelper : Sitecore.Publishing.PublishHelper
    {
        public ExtendedPublishHelper(ExtendedPublishOptions options) : base(options) { }

        public override Sitecore.Data.Items.Item GetVersionToPublish(Sitecore.Data.Items.Item sourceItem)
        {
            if (this.Options is ExtendedPublishOptions)
            {
                ExtendedPublishOptions extOptions = this.Options as ExtendedPublishOptions;
                return sourceItem.Publishing.GetValidVersion(this.Options.PublishDate, extOptions.RequireApproval);
            }
            else
            {
                // ベースクラスのGetVersionToPublish メソッドを呼び出す
                return base.GetVersionToPublish(sourceItem);
            }
        }
    }
}

web.configのpublishManager に設定するカスタム PublishProviderを作成します。今回は、ExtendedPublishProvider.cs という名前でクラスを作成しました。CreatePublishHelper をオーバーライドして、 ExtendedPublishOptions が使用された場合、 ExtendedPublishHelperのインスタンスを生成するようにしています。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace SC.Web.Core
{
    /// <summary>
    /// カスタムパイプラインパブリッシュプロバイダーを実装する。
    /// 特定のオプションクラスを使用した場合、最終状態を考慮するかを指定して
    /// アイテムをパブリッシュターゲットにパブリッシュできる。
    /// </summary>
    public class ExtendedPublishProvider : Sitecore.Publishing.PipelinePublishProvider
    {
        public override Sitecore.Publishing.PublishHelper CreatePublishHelper(Sitecore.Publishing.PublishOptions options)
        {
            Sitecore.Diagnostics.Assert.ArgumentNotNull(options, "options");
            if (options is ExtendedPublishOptions)
            {
                return new ExtendedPublishHelper(options as ExtendedPublishOptions);
            }
            else
            {
                return base.CreatePublishHelper(options);
            }
        }
    }
}

Web.config内の、 PublishManager のプロバイダを、作成した ExtendedPublishProviderに設定します。今回は次のように編集しました。

    <publishManager defaultProvider="default" enabled="true">
      <providers>
        <clear/>
        <!-- デフォルトのパブリッシュプロバイダーはコメントアウト -->
        <!--<add name="default" type="Sitecore.Publishing.PipelinePublishProvider, Sitecore.Kernel"/>-->
        <add name="default" type="SC.Web.Core.ExtendedPublishProvider, SC.Web"/>
      </providers>
    </publishManager>

以上で準備ができました。以上で、準備ができました。PublishManager.Publish メソッドに渡す引数に ExtendedPublishOptions のインスタンスを使用することで、カスタムのパブリッシュ(最終状態を考慮するかどうかを指定できるパブリッシュ)を行えるようになります。

3.動作確認

作成した ExtendedPublishHelper を使用するサンプルを作成してみます。例えば、ワークフローアクションを作成して ステータスが遷移したタイミングで自動的に事前確認用(テスト公開用)のパブリッシュターゲットにアイテムをパブリッシュさせることができます。(そのほかリボンにコマンドを配置して手動パブリッシュできるようにする方法も考えられます。)

今回は PublishToStagingAction.csというワークフローアクション用のクラスを作成しました。カスタムワークフローアクションでExtendedPublishOptionsを使用してアイテムのパブリッシュを行います。 deep=1パラメータが指定された場合は web2に対してサブアイテムを含むアイテムのパブリッシュを行います。requiredApproval=1 が指定されると 最終状態のアイテムをパブリッシュします。そうでない場合は、最終状態かどうかは考慮せず、パブリッシュ可能なアイテムをパブリッシュします。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Sitecore.Data;
using Sitecore.SecurityModel;
using Sitecore.Data.Items;
using System.Collections.Specialized;
using Sitecore.Web;
using Sitecore.Workflows.Simple;
using Sitecore.Publishing;
using Sitecore.Globalization;

namespace SC.Web.Core
{
    /// <summary>
    /// 検証用のパブリッシュターゲットにアイテムをパブリッシュする
    /// </summary>
    public class PublishToStagingAction
    {
        public void Process(WorkflowPipelineArgs args)
        {
            Item dataItem = args.DataItem;
            Database database = Sitecore.Configuration.Factory.GetDatabase("web2");
            Item innerItem = args.ProcessorItem.InnerItem;
            ExtendedPublishOptions options = new ExtendedPublishOptions(dataItem.Database, database, PublishMode.SingleItem, dataItem.Language, DateTime.Now, GetRequireApproval(innerItem))
            {
                RootItem = dataItem,
                Deep = GetDeep(innerItem)
            };
            PublishManager.Publish(new PublishOptions[] { options });
        }
        /// <summary>
        /// deep パラメーターが1の場合にサブアイテムを含めてパブリッシュを行う
        /// </summary>
        /// <param name="actionItem"></param>
        /// <returns></returns>
        private bool GetDeep(Item actionItem)
        {
            if (actionItem["deep"] == "1")
            {
                return true;
            }
            NameValueCollection nameValueCollection = WebUtil.ParseUrlParameters(actionItem["parameters"]);
            return nameValueCollection["deep"] == "1";
        }

        private bool GetRequireApproval(Item innerItem)
        {
            return WebUtil.ParseUrlParameters(innerItem["parameters"])["requireApproval"] == "1";
        }
    }
}

カスタムワークフローアクションを使用するワークフローを作成します。今回は下図のように 既定で作成される サンプルワークローと同じ構造の TestWorkflow とうワークフローの定義アイテムを作成しました。 異なるのは、下図のように TestPublished というワークフロー状態に、 "事前確認環境に公開 " というワークフローアクションが設定されていることです。

ワークフローアクションの定義アイテムには下図のように タイプフィールドに ワークフローアクションのシグネチャを設定しています。 パラメーターにはサブアイテムを含むアイテムのパブリッシュを行い、 最終状態を考慮せずにパブリッシュ可能なアイテムをパブリッシュする設定をしました。

ワークフローの定義の図で、自動パブリッシュのワークフローアクションには、下図のようにサンプルワークフローの自動パブリッシュの定義アイテムと同じ設定をしています。既定で提供されるワークフローアクションはすべてのパブリッシュターゲットにアイテムをパブリッシュします。

あとは、任意のデータテンプレートのスタンダードヴァリュー定義アイテムにデフォルトワークフローとしてTestWorkflowを設定します。

後は実際にワークフローを回してみます。下図のように挿入オプションの テンプレートから挿入 でアイテムを作成します。

適当にフィールドを編集します。

下図のように投稿ワークフローコマンドを実行します。

この時点で テスト公開用サイトにアイテムがパブリッシュされています。実際に下図のようにテスト公開用のサイトでアイテムを表示するとページが表示されます。(レイアウト詳細の設定は定義済みの想定です。)

一方、公開用サイトのパブリッシュターゲット(Webデータベース)にはアイテムがパブリッシュされていません。アイテムのページを表示すると エラー画面が表示されます(下図参照)。

さらにワークフローを進めます。下図のように承認コマンドを実行します。

今度は 外部公開用のサイトでもアイテムを表示することができます(下図参照)。

4.まとめ

説明は以上です。カスタムのパブリッシュプロバイダーを実装することで 最終状態ではないアイテムをパブリッシュできるようになります。

実運用では アイテムのパブッシュのみをしていると mater データベースのコンテンツツリーの内容と テスト公開用の コンテンツツリーの内容が少しづつずれてくる可能性があります。例えば、ワークフロー管理されていないアイテムをテスト公開用のサイトにパブリッシュし忘れる等によりです。

そのため、定期的にテスト公開用のパブリッシュターゲットに対してサイトのパブリッシュを行いコンテンツツリーのアイテムを最新の状態になるよう同期をとる必要があるので注意してください。標準で用意されているサイトのパブリッシュを行い場合は 最終状態のアイテムがパブリッシュされます。これにより、webとweb2のパブリッシュターゲットの状態を同じにすることができます。

繰り返しになりますが、本記事の方法は EventQueu を使用する環境(Web.config の EnableEventQueue が true) では動作しないので注意してください。そのような環境の場合は、例えば、CreatePublishHelper メソッド内の処理を PublishOptions のプロパティでテスト公開用のパブリッシューターゲットでかつ、パブリッシュモードがSingleItem の場合に最終状態ではないアイテムの数値バージョンをパブリッシュ対象とするようにするなどのPublishOptions で得られるプロパティ情報のみを使用して判断をするようにする工夫が必要となります。