Movable Type 技術情報

クラウド上にMTのデータベースをバックアップするプラグインDropbox

onagatani 2012年01月16日

おはようございます。本日も帯広はマイナス14度でちょー寒いonagataniです。

昨日は半年ぶりくらいに個人的にコードを書く気力が出たのでMTプラグイン書いてみました。
(今回は完全趣味な上に業務時間外で書いているのでサポート等は会社に投げないでくださいませ。質問はTwitter@onagatani

できることは少なくてDropboxにMySQLDumpを保存するだけです。
何が嬉しいかというとクラウド上にデータを保存するだけでなくDropboxの機能でクライアントAppを入れたPC全てで
データを同期するので、サーバ機で破損等あった場合でもほぼ100%データが復元できる事です。

逆にまだ実装していない機能として、MTのコンテンツを複数サーバ間で同期する機能があります。
こちらはコンテンツをバックアップしつつ、lsyncの様な使い方ができそうなので鋭意作成中なので検証完了しましたら公開させて頂きます。

使い方

  1. 以下のCPANモジュールをインストールして下さいWebService::Dropbox、Time::Piece(例:curl -L http://cpanmin.us | perl - WebService::Dropbox Time::Piece)
  2. Dropbox.plをMTのpluginsディレクトリに設置して下さい
  3. Dropboxのアカウントを作成しこちらのページでアプリケーションの登録を行なって下さい(アプリケーション名等は自由に設定して下さい。アクセス権限はフルアクセスでもディレクトリ制限有りでも どちらでも問題ないはずです)
  4. アプリケーション登録を行ったら AppKeyとAppSecretをMTに登録して下さい(システム->ツールメニュー->プラグイン->Dropbox->設定
  5. Key、Secret登録が完了したシステム->ツールメニュー->dropbox authを押下して下さい。先ほど登録したDropboxアプリケーションへ認証を許可します。許可するとMTのダッシュボードへリダイレクトされます
  6. 4の設定画面でデータベースバックアップを保存ディレクトリを指定します。事前にDropboxにディレクトリを作成して下さい(例:mtbackup)
  7. 以上でrun-periodic-tasksが一日に一回MySQLDumpをDropbox上に保存する用になります。dumpファイルはgzipにて圧縮保存されます


設定項目(mt-configに記述する例です)

  1. MySQLDumpPath /usr/bin/mysqldump
  2. GZipPath /usr/bin/gzip

ダウンロード

Dropbox01.zip

ライセンス

  • 個人での利用はご自由にお願いします。但し問題があっても責任はもてません。
  • 企業での利用は何か問題があっても責任が取れないので事前にご連絡下さい。

注意

  1. CentOS5.6のMySQLサーバでのみ動作確認をしております。WindowsやMySQL以外のデータベースでは現状動作しませんので注意して下さい。要望あればWindowsや他DBに対応しますので@onagataniまで連絡下さい。
  2. 保存されるファイルのローテーションは現状実装されていません。ご自身で定期的に削除して下さい
  3. 本番環境に設置する前に事前に検証環境での動作確認を行なって下さい
  4. Dropboxにファイルをuploadするために/tmpを利用します。バックアップファイルが設置される空き容量が一時的に必要になります

 

投稿者 onagatani : 01/16

PageBute プラグインで一つのブログ記事を複数ページに分割する

ぴろり上西 2011年06月02日

 弊社で公開中の人気プラグイン PageBute を用いると、長くなりがちなリストアーカイブなどを、指定した記事数ごとに複数ページに分割することができます。一般的には、記事一覧などのリスト系ページを見やすい単位に分割する目的で使われることが多いと思いますが、使い方によっては、例えばこの例のように、 一つの長いブログ記事を複数のページに分割することにも利用できます。このエントリでは、PageBute プラグインを使って、一つのブログ記事をページングする方法を紹介します。

方法 1. mteval モディファイヤを用いる方法

 ブログ記事の個別アーカイブのテンプレートにおいて、ブログ記事の本文/追記を出力する部分のテンプレートタグを以下のように記述します。

<MTPageContents count="1">
<$mt:EntryBody mteval="1"$><$MTPageSeparator$>
</MTPageContents>

 次に、ブログ記事の本文/追記中で、改ページを行いたい場所に <$MTPageSeparator$> テンプレートタグを直接記述します。個別アーカイブのテンプレートにおいて mteval モディファイヤを指定しているので、ブログ記事中の <$MTPageSeparator$> タグがページの区切りとして処理され、一つのブログ記事が複数のページに分割されて生成されます。もし、<$MTPageSeparator$> を一つも記述しなければ、従来どおりに一つのブログ記事は一つのページとして生成されます。長いブログ記事では適当な長さでページ分割を行い、短いブログ記事ではページ分割を行わない、といった調整が自由に行えます。

この段落が 1 ページ目になります。
<$MTPageSeparator$>
この段落は 2 ページ目になります。

方法 2. mteval モディファイヤを用いない方法

 ブログ記事中にテンプレートタグが書いてあったり、テンプレートタグが無条件に実行されるのはちょっと不安など、なんらかの理由で mteval モディファイヤを使えない場合、ブログ記事の個別アーカイブのテンプレートにおいて、ブログ記事の本文/追記を出力する部分のテンプレートタグを以下のように記述します。

<MTPageContents count="1">
<$mt:EntryBody$><$MTPageSeparator$>
</MTPageContents>

 テンプレートはほぼ同じですが、ブログ記事の本文/追記中で、改ページを行いたい場所には、先ほどのテンプレートタグの替わりに<!-- Separator for PageBute --> と記述することで同じ動作が可能です。

この段落が 1 ページ目になります。
<!-- Separator for PageBute -->
この段落は 2 ページ目になります。

 この方法はブログ記事以外にもウェブページなどでも応用が可能です。

投稿者 ぴろり上西 : 06/02

変数名の罠

ぴろり上西 2011年05月28日

 Facebook 上の MT 関連コミュニティで次のような質問がありました。

<MTSetVarBlock name="folder-label">美しい日本語</MTSetVarBlock>
<MTSetVarBlock name="page-title">美しい日本語</MTSetVarBlock>
folder-label: <$MTGetVar name="folder-label"$>
page-title: <$MTGetVar name="page-title"$>
<MTIf name="folder-label" eq="$page-title">TRUE<MTElse>FALSE</MTIf>

 上記のようなテンプレートで変数比較が期待した動作にならない、というものです。質問された方は、変数値に日本語文字列が含まれていることが原因であると考えていらっしゃるようでしたが、これについて調べてみました。

 結論から言うと、変数値が日本語文字列であることとは関係なく、変数名が理由で期待した動作が行われていないことがその理由です。具体的には 5 行目の MTIf に渡されている like 引数が正しく変数展開されていないのです。
 確認のために 3 行目と 4 行目で変数にセットされた内容を出力していますが、ここではどちらも同じ値を出力します。しかし、その次の MTIf では、文字列比較に失敗します。

 理由は次の通りです。テンプレートタグのオプション値に $ から始まる文字列を指定すると、それを変数として解釈し、その変数の値が実際のオプション値として渡されます。この時、オプション値に渡された文字列をそのまま即値として解釈するか、変数として解釈するかの判定条件に原因があります。
 その判定を行っている処理は /lib/MT/Builder.pm の build 関数内にあり、オプション値が次の条件を満たす場合には変数として値が展開されます。

  • 1 文字目が $ で始まること
  • 2 文字目は英字またはアンダーバー(_)であること
  • 3 文字目以降がある場合、全て英数字またはアンダーバー(_)またはピリオドであること

 上記の条件を満たさない場合、渡された文字列は変数として解釈されず、そのままの値として渡されます。先のテンプレートでは、page-title という変数に使われているハイフン(-)が理由で、変数名として解釈されず変数が展開されなかったのでした。そのため、次のテンプレートは奇妙にも TRUE を返します。

<MTSetVarBlock name="folder-label">$page-title</MTSetVarBlock>
folder-label: <MTGetVar name="folder-label">
<MTIf name="folder-label" eq="$page-title">TRUE<MTElse>FALSE</MTIf>

 Facebook コミュニティであった質問の答えは「変数名にハイフンが含まれていて、期待した変数展開が行われていなかったため」ということで決着するのですが、変数名に関しては続きがあります。
 引数に渡された文字列が変数展開されるか否かの条件は先述の通りですが、引数の変数展開を考えないのであれば、変数名には全くの制限がありません。これは一般的なプログラミング言語では考えられないことです。例えば、次のようなテンプレートでも正しく動作してしまいます。

# 数字の変数名
<MTSetVar name="1" value="TEST STRING">
<MTGetVar name="1"> → TEST STRING

# 記号の変数名
<MTSetVar name="!#$%&'()-=^~" value="TEST STRING">
<MTGetVar name="!#$%&'()-=^~"> → TEST STRING

# 空白の変数名
# 空白の数の違いで別の変数扱いになる!
<MTSetVar name="   " value="TEST STRING">
<MTGetVar name=" ">
<MTGetVar name="  ">
<MTGetVar name="   "> → TEST STRING

# 空白を含む変数名
# "abc" と " abc " は別変数扱いになる!
<MTSetVar name=" abc " value="TEST STRING">
<MTGetVar name="abc">
<MTGetVar name=" abc "> → TEST STRING
# でも英字の大文字小文字は区別しないという...
<MTGetVar name=" aBc "> → TEST STRING

 MT 変数名は、プログラム内部でハッシュ変数のキー文字列として使われていて、実際のところMT で指定できないようなどんな値でもキー名=変数名に使うことができます。

まとめ

MT で使える変数名について

  • 一般的なプログラミング言語にあるような変数の命名規則は存在しない
  • 数字、記号、空白などのあらゆる文字を任意の順列で使用できる
  • 空白も文字として扱われるので、"abc" と " abc " は別変数になる
  • テンプレートタグなどの引数に $ から始まる文字列を指定すると変数展開されるが、その時、変数として解釈されるためには変数名に条件がある
  • なので、あまり変態的な変数名を用いず、英数字から成る平凡な変数名にしておくと、変な仕様に悩まされずに済む
投稿者 ぴろり上西 : 05/28

プラグイン周りについて ~ プラグインのロード順

ぴろり上西 2011年05月27日

 前回の記事に続いて、今回はプラグインの読み込まれる順番について情報展開しておきたいと思います。プラグインを利用される際に、プラグインが読み込まれる順番というのは普段は全く意識することはありませんし、それを意識しなくても問題が起こることはありません。しかし、複数のプラグインから構成されるプラグインセットのようなものを作ろうとすると、プラグインのロードされる順番が問題になることがあります。そういう意味では、この問題はプラグイン開発者が留意するべき問題で、一般にプラグインの利用者が気にするところではありませんが。

背景

 どういう時に問題になるのか具体的な例を挙げてみます。Movable Type を利用したあるプロジェクトがあり、そこでは必要な機能をプラグインとして実装することになりました。複数の開発メンバーが、夫々に必要な機能をプラグインとして作成し、plugins フォルダ以下に配置していきます。途中、共通する処理や便利関数などをライブラリとして使えるよう、ProjectCommon というプラグインを作って、その lib ディレクトリ内にプロジェクト独自に作った共通モジュールファイルを置くようにしました。これは、MT の既存ライブラリファイル群を汚さずに独自の共通モジュールを追加する良いアイディアです。そしてディレクトリ構成は以下のようになりました。

+- /plugins
   +- ProjectCommon
      +- ProjectCommon.pl
      +- lib
         +- ProjectCommon
            +- Foo.pm
            +- Bar.pm
   +- HogePlugin
      +- HogePlugin.pl
   +- FugaPlugin
      +- FugaPlugin.pl
   :

 /plugins/ProjectCommon/lib にはパスが通っていたので、HogePlugin.pl などからは、以下のようにして共通関数を呼び出すことができました。

use ProjectCommon::Foo;
my $ret = ProjectCommon::Foo::do_hogehoge ();

問題発生とその原因

 一見、うまく機能していた共通モジュールを提供するプラグインですが、この後、サーバによって ProjectCommon のモジュールが見つからないという問題に遭遇します。開発サーバでは問題なかったものが、本番サーバ上ではモジュールファイルが見つからず、Perl がコンパイルエラーになるというものです。ファイル内容、ファイル構成など、あらゆる物理的要素をチェックしても、開発サーバと本番サーバでは差異がありません。何が原因でしょう?

 ようやく本題です。その理由は、プラグインが読み込まれる順番にありました。つまり、HogePlugin.pl が共通モジュールを利用できるためには、HogePlugin プラグインに先立って ProjectCommon プラグインが読み込まれている必要があるということです。なぜなら、プラグインが読み込まれた段階で、そのプラグインのディレクトリ以下にある lib と extlib に Perl モジュールのパス(@INC)が通されるのですが、開発サーバでは、ProjectCommon プラグインがロードされ、/plugins/ProjectCommon/lib にパスが通った後に HogePlugin がロードされていたためこの問題が顕在化しませんでした。しかし、本番サーバでは、ProjectCommon プラグインがロードされるより前に、ProjectCommon のライブラリファイルを必要とする他のプラグインがロードされていて、/plugins/ProjectCommon/lib にパスが通っていなかったため、モジュールを見つけられなかったのです。

  1. /plugins ディレクトリの中を調べる (opendir~readdir~closedir)
    1. 拡張子が .pl のファイルであれば、Perl で書かれたプラグインとして読み込む
    2. 拡張子が .pl 以外のファイルは無視される
    3. サブディレクトリの中に config.yaml があれば、YAML で書かれたプラグインとして読み込む
    4. サブディレクトリの中を調べる (opendir~readdir~closedir)
      1. 拡張子が .pl のファイルであれば、Perl 通常ので書かれたプラグインとして読み込む
      2. 拡張子が .pl 以外のファイルは無視される

これは、MT がプラグインを読み込むところの概要手順ですが、ここで使われている readdir という Perl 関数がカギになります。readdir 関数は、先に opendir 関数で指定されたディレクトリ内に存在するファイル名やディレクトリ名のリストを取得するための関数なのですが、ここで取得されるリストは名前順でも日付順でもなんでもないという仕様なのです。ディスクドライブ上の論理環境などが異なると、リストに含まれる内容は同じでも、リストの順番が異なります。つまり、HogePlugin などより先に必ず ProjectCommon がロードされることを保証できないということになります。これは困った。

解決方法

 これを回避するには、PluginPath ディレクティブを利用します。このディレクティブを用いると、プラグインを配置したディレクトリを変更することができます。また、複数のディレクトリにプラグインを分散して配置することができます。これを利用できないでしょうか?

PluginPath ./plugins_first
PluginPath ./plugins_second
PluginPath ./plugins

 上記のように書いた場合、MTは plugins_first → plugins_second → plugins の順にプラグインを探すことがソースコード上、保証されます(/lib/MT.pm、init_plugins 関数)。各ディレクトリ内においてプラグインの読み込まれる順は先述の通り不定ですが、これを用いるとプラグインの読み込まれる順番をある程度制御できるというわけです。つまり、他のプラグインから共通して利用されるモジュールやコンポーネントを提供するプラグインは plugins_first ディレクトリに配置し、それらを利用する側のプラグインは plugins_second や plugins に配置する、という方法です。

まとめ

  • 一つのプラグイン ディレクトリ内において、プラグインの読み込まれる順番は環境に依存し、それを制御することはできない
  • PluginPath ディレクティブで複数のプラグイン ディレクトリを指定することができ、ディレクトリの走査される順番は PluginPath で指定した順番になる

 まぁ、判ってしまえば「なーんだ」なのですが、こんなディレクティブがあることに気が付くまでに無駄な時間を使ったので。

投稿者 ぴろり上西 : 05/27

プラグイン周りについて ~ プラグインセット

ぴろり上西 2011年05月25日

 Movable Type 5 ユーザーコミュニティ:MTQ で次のような質問がありました。

プラグインのセットを作りたいのですが
プラグインの名前でyamlを作成してみたりしたのですが、セットとして認識されませんでした。やはり、下記のように .pl で書き直さないとだめでしょうか?

 この質問についての答えは Yes です。/plugins の下の一つのサブディレクトリ内に複数のプラグインを配置するには YAML ではなく Perl スクリプトで書かれたプラグインを置かねばなりません。これに関係して、少し調べた部分をまとめておきます。
 プラグインを読み込んでいるところのソースコード(/lib/MT.pm, _init_plugins_core 関数、MT5.1 まで変更されていないみたい)を見ると、おおむね以下のような手続きになっていることがわかります。

  1. /plugins ディレクトリの中を調べる (opendir~readdir~closedir)
    1. 拡張子が .pl のファイルであれば、Perl で書かれたプラグインとして読み込む
    2. 拡張子が .pl 以外のファイルは無視される
    3. サブディレクトリの中に config.yaml があれば、YAML で書かれたプラグインとして読み込む
    4. サブディレクトリの中を調べる (opendir~readdir~closedir)
      1. 拡張子が .pl のファイルであれば、Perl 通常ので書かれたプラグインとして読み込む
      2. 拡張子が .pl 以外のファイルは無視される

 という感じです。YAML によるプラグイン定義は config.yaml というファイル名で固定されており、サブディレクトリ内に config.yaml が存在した時点でそれが読み込まれ、次のサブディレクトリに処理が移ります。ですので MTQ での質問のように、一つのディレクトリに複数のプラグインを配置したい場合には、拡張子が .pl の Perl スクリプトとして配置するしかありません。

 ところで、話しは逸れますが、MT4 以降、プラグインは従来の Perl スクリプト形式に加えて YAML 形式が採用されました。シックスアパートでは YAML によるプラグイン作成を推奨しているようですが、個人的には従来の Perl スクリプトによる作成方法が好ましいように感じています。理由は以下の通りです。

 1. Perl スクリプトで書かれたプラグインは、一つのサブディレクトリ内に一つ以上配置できますが、config.yaml による記述では、一つのサブディレクトリにつき一つのプラグインしか配置できません。そのため、それぞれ関連する機能を提供しあう複数のプラグインの集合という管理ができません。
 プラグインによって提供されたある一連の機能について、それらをまとめて削除したい場合、Perl スクリプトで提供されるプラグインは、そのサブディレクトリ一つを削除すれば問題ありませんが、YAML によって提供されたプラグインは、どれらのサブディレクトリを削除すればよいのか人間が知っている必要があります。
 MTCMS に搭載されているサイトマップダッシュボード機能はプラグインとして提供されている機能の一つで、ダッシュボード上に某 Windows 風なサイトマップを表示することができます。このダッシュボード上から、ダイアログウィンドゥを開いて、いきなりサブカテゴリやサブフォルダを作成したり、フォルダごとにアセットをフィルタリングするなどの機能が提供されていますが、これらの機能はこのダッシュボードの拡張として存在しています。故に、これらの機能はそれぞれが .pl ファイルとして一つのディレクトリに配置されています。もしサイトマップダッシュボードのプラグイン自体が不要になれば、そのディレクトリ一つを削除すればよいのです。

 2. プラグインの下位互換性を考えた場合、例えば、簡単なテンプレートタグを拡張する程度のプラグインであれば、config.yaml を使わずに、更に registry さえも使わずに、MT::Template::Context->add_tag と MT3 時代の方法を使い続けることで、MT3 ユーザさえもその恩恵に預けることができます。
 また、registry を利用する場合であっても、例えば、メニュー項目を追加するプラグインでは MT4 と MT5 で registry のキー名が異なる場合、プログラム的に MT のバージョンを判別して処理を分岐する必要があります。

 3. config.yaml による記述では、stuff な(単純なくだらない)プラグイン一つのために複数のファイルが必要になります。つまり、プラグインの定義が書かれた config.yaml ファイルと、処理の実体が書かれた Perl のモジュールファイル(*.pm)です。Perl スクリプトによる形式であれば、一つのファイル内にプラグインの登録処理(MT->add_plugin)と処理の実体(sub hogehoge {...})を合わせて書くことができます。
 config.yaml 内でもブロックスタイルを用いて処理の実体(Perl スクリプト)を書くことができますが、1) Perl による Syntax Check を行えない、2) エディタの Syntax Hilight 機能に預かれない、3) 一つのファイル中に YAML、Perl という言語仕様が混在することのメンテナンス性の低下、などの問題があります。

 4. config.yaml によるプラグイン定義において、その YAML 内に Syntax Error があった場合、そのエラーが判りづらいというのがあります。インデントするつもりで Tab を打ったらホワイトスペース 4 つじゃなくて \t が入っていて構文エラーですとか、ホワイトスペースが一つ足りずに構文エラーですとか → 参考:MT plugin の config.yaml には注意

 ...という理由です。YAML はスタートダッシュが秀逸で、サクサクッとプラグインを作るのには適していると思います。ただ、長期に渡って複数の開発者で改善改修を行い続ける必要があったり、プラグインがどんどん肥大化、高機能化していくケースを考えると、最初が多少面倒な Perl スクリプトであっても、結果的には保守性や可読性を維持できる方がメリットがあると感じています。

 次回は、複数のプラグインがどのような順番でシステムに読み込まれるのかを解説したいと思います(続くのかよ)

投稿者 ぴろり上西 : 05/25
お問い合わせはこちらから お電話でのお問い合わせ03-5475-5101

MTエンジニアブログ

MTCMS Smartキャンペーン!
承認フロー付きCMS「MTCMS Smart(98,000円)」をお買い上げの方、MTライセンス1本がタダに!

配布プラグイン一覧

注目記事

RSS

ページの先頭へ戻る