2014年5月アーカイブ

ハマりがちな罠。Facebookとかで既に書いたんだけど。

こういうの、よく作るよね。「記事が保存されたとき」をトリガーにして動作させるプラグイン。MTに限らないけど、WordPressだったらなんだ、フックっていうんだっけ。MTならコールバック。

以下のような実装が必要な時に使います。

  • 記事がポストされたらTwitterに投げる(FacebookでもWeiboでも何でもいいけど)
  • 記事が公開されたらメールで通知を送る
  • 記事が更新されたら関連するアーカイブを再構築する

Aceエディタ

BSD licenseで公開されています。110以上の言語の Syntax highlighting に対応しており、組み込みも簡単。気に入ったのは特に以下。

  • 動作が軽い
  • 自動補完(閉じ括弧とか)
  • 検索・置換に対応(Command(Ctl)+F)
  • タブインデントに対応。複数行を選択して、Tab押下。Shift+Tabキーでタブインデント下げ
  • シンタックス・チェック (警告表示)

シンタックス・チェック(警告表示も)

ドキュメントを見る限りキーバインドのカスタマイズや様々な拡張もできる模様。

PHPでログイン状態のチェックを行う

昨日、ログインの実装についてのコードを紹介しました。最後に宿題? 出しましたけど...

せっかくなのでたまには課題

では、これでログイン処理ができたとして、以降のリクエストのログインチェック、ユーザー情報の取得とコンテクストへのセットはどのようにすればよいでしょうか?

と、いうことで回答編(と、おまけ付き)。

PHPerのためのMovable Type講座

クッキーからのログイン状態のチェックについて

  • クッキーからセッションをチェック
  • セッションの有効期限チェック
  • セッションからユーザーを割り出し、存在するか、もしくは有効なユーザーかチェック
  • 必要に応じて権限のチェック

というような流れになります。

<?php
include('/path/to/mt/php/mt.php');
$mt = MT::get_instance(1, '/path/to/mt/mt-config.cgi');
if ( $session_id = $_COOKIE[ 'mt_commenter' ] ) {
    // セッションをチェック
    require_once( 'class.mt_session.php' );
    $session = new Session;
    $session->Load( "session_id='${session_id}'&session_kind='SI'" );
    if ( isset( $session ) ) {
        $ttl = $mt->config( 'UserSessionCookieTimeout' );
        if ( ( $session->session_start + $ttl ) < time() ) {
            // 有効期限切れ
            $session->Delete();
            echo 'Your session has expired. Please sign in again to comment.';
            return;
        } else {
            $author = $mt->db()->fetch_author_by_name( $session->session_name );
            // ユーザーチェック
            if ( isset( $author ) ) {
                if ( $author->author_status != 1 ) {
                    unset( $author );
                }
            }
        }
    }
}
if (! isset( $author ) ) {
    echo 'Permission denied.';
    return;
}
// 必要に応じて権限(mt_permissionやmt_association)のチェック

コンテキストにユーザーをセットする

こうすることで、MTAuthorFoo タグ、ユーザーカスタムフィールドの値が取得できるようになります。

// $ctx = $mt->context();
// $ctx->stash( 'author', $author );

プラグイン設定画面

管理画面から設定できたらいいよねぇってことで作ってみた。プラグイン設定から、ユーザー名とパスワードの設定ができます。

.htaccessで設定とか、.htpasswdをターミナルで生成とかやっぱり敷居高いもんな、とか思ったんだけども、Perl CGIでは $ENV{ 'HTTP_AUTHORIZATION' } はどうもデフォルトでは取得できないみたいで。

.htaccess に追加

IIS や nginx では未確認。こんな指定するくらいなら素直にBasic認証の設定を入れるよって? そりゃそうだ。まぁ何かの参考に、備忘録として。

RewriteEngine on
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]

関係(何とだ)しそうなところだけコードを貼っておく。

if ( my $auth = $app->get_header( 'AUTHORIZATION' ) ) {
    my @auths = split( /\s/, $auth );
    $auth = $auths[ 1 ];
    $auth = MIME::Base64::decode_base64( $auth );
    @auths = split( /:/, $auth );
    if ( scalar( @auths ) == 2 ) {
        my $username = $auths[ 0 ];
        my $password = $auths[ 1 ];
        if ( ( $username eq $auth_username ) &&
            ( $password eq $auth_password ) ) {
            $authrized = 1;
        }
    }
}
if (! $authrized ) {
    $app->logout();
    $app->delete_param( 'username' );
    $app->delete_param( 'password' );
    $app->user( undef );
    $app->response_code( '401' );
    $app->set_header( 'WWW-Authenticate', 'Basic realm="Please enter your ID and Password"' );
}

ログイン処理をPHPで実装してみよう、編。

PHPerのためのMovable Type

DynamicMTMLを使うと簡単に書けますが、今回はDynamicMTMLなしでも動くコードを紹介します。

ログインの処理フロー

  • ユーザー名とパスワードをポストする
  • ユーザー名から mt_author オブジェクトをロード
  • 渡されたパスワードを暗号化してデータベースの値と比較
  • 正しければ、クッキーをセット
  • mt_session レコードにセッション情報を保存

mt_session の session_kind は 'SI' とする。クッキー名は 'mt_commenter'。Session.pm のドキュメントにありますね。

=item SI

Active commenter sessions are held in SI sessions.

オフィシャルには最新版にしてくれ、としか言えないけど、本当に必要なのは何が危ないかを明らかにすることかと思うんですよねぇ。しょうがないけど。

MTを最新版にできない場合、とりあえずmt-upgrade.cgiを削除しよう。

無条件に削除するもの(古いMTにしかないもの)。

  • mt-add-notify.cgi
  • mt-view.cgi

削除がなんなら、実行権限を外すだけでもいいです。その他にも以下。

  • mt-check.cgi
  • mt-wizard.cgi
  • mt-testbg.cgi
  • mt-upgrade.cgi
  • mt-atom.cgi
  • mt-check.cgi
  • mt-check.php
  • mt-cp.cgi
  • mt-feed.cgi
  • mt-ftsearch.cgi
  • mt-xmlrpc.cgi
  • mt-data-api.cgi
  • mt-sp.cgi

コメントトラックバックを使っていなければ

  • mt-comments.cgi
  • mt-tb.cgi

も要らない。

昨日サポートに問い合わせがあった時に「面白そうなテーマだな」と思って早速手元で検証して記事をアップしたのですが。

夜中にふと思い立って(というか、社内で指摘はされたたんですけどうまく動かなかった=夜中に気になってコード読んでたらやっぱり行けそう...と)。

後は、乱数を得るところ。DynamicMTMLが有効な環境なら、これだけで済むわけですが。

<MTRand min="0" max="99">

DynamicMTML のMTSearchEntries ではなくせっかく MTEntries(のuniqueモディファイア活用)で行けることがわかったので、乱数の部分もMT標準だけでなんとか行けないだろうかと...

ということで、トライしてみた。乱数を取得するには基本、MTDateタグを使えばいいのですが、所詮秒単位です。しかも1ページを再構築する間にMTDateを取得しても値は同じになりますよね。よっぽどデカいテンプレートでなければ。ということで、なんちゃって実装を考えてみた。もっと良い方法がきっとある筈。是非教えてください。

<MTSetvar name="base" value="123456789">
<MTIgnore>↑これが、まぁ、なんちゃってなところであるが...</MTIgnore>
<MTdate format="%S" setvar="second">
<MTSetvar name="base" value="$second" append="1">

<MTIgnore>
・12345678902 みたいになるので、
 末尾の一文字を先頭に移動しながらループを回す。
・0から99までの乱数は、元の数字を100で割った余りで得られる
・数字の計算は opモディファイア、
 末尾の文字の切り出しは regex_replace モディファイア
 先頭に数字を移動するのは MTSetVarタグの prepend モディファイアで
</MTIgnore>

<pre>
<MTfor from="1" to="5">
<MTVar name="base" setvar="calc">
<MTVar name="base" setvar="lastchar">
<mtvar name="calc" op="mod" value="100"> ← 0〜99の乱数
<MTVar name="lastchar" regex_replace="/^.*([0-9])$/","$1" setvar="lastchar">
<MTSetVar name="base" value="$lastchar" prepend="1">
<MTVar name="base" regex_replace="/[0-9]{1}$/","" setvar="base">
</MTfor>
<MTIgnore>
例 : 56, 45, 14, 91, 89
</MTIgnore>

Movable Type for AWS

改めて書くまでもないと思うけども。一応。Movable Type for AWS(AMI) のセットアップをしてみた。最近はスタッフにお願いすることが多くなったので、リハビリ兼ねて。リハビリにもならなかったがw。

取り敢えずMicroインスタンスで。手順は以下のページの通り。こちらのクライアント環境はMacBook Pro(OSは10.9.2)。

上記手順の 6.Key Pair を選択 のところで作成した Key Pair をローカルに保存。パーミッションを変更。

## XXXX.pem で保存
## $ chmod 400 XXXX.pem

7.以降を続行。完了したら EC2 Management Console を開く。インスタンスを選択して「Connect」をクリックしてダイアログを表示。

AWSのEC2コンソール

## まずは Transmitで ec2-user で XXXX.pem を指定してログインできることを確認、OK

Transmit でログイン

Windows AzureはMicrosoft Azureに変わったそうで。まぁ、名前はどうでもいい。

AZBlobDisk

Perl版は無いのですがPHPはSDKがあります。Azureのストレージへのアクセスはトークンの組み立てが面倒なので、それだけでもね。こういうのがあると手軽です。

注意点としては、PEARの HTTP/Request2.php が必要なところですね(Mail_mimeと Mail_mimeDecodeも必要)。

The PHP Client Libraries for Azure have a dependency on the HTTP_Request2, Mail_mime, and Mail_mimeDecode PEAR packages. The recommended way to resolve these dependencies is to install these packages using the PEAR package manager

use WindowsAzure\Common\ServicesBuilder;
require_once 'WindowsAzure/WindowsAzure.php';

$protocol = 'https';
$container_name = 'test';
$connectionString = "DefaultEndpointsProtocol=${protocol};AccountName=${account_name};AccountKey=${primary_access_key}";
$blobRestProxy = ServicesBuilder::getInstance()->createBlobService( $connectionString );

 // 一覧取得
$blob_list = $blobRestProxy->listBlobs( $container_name );
$blobs = $blob_list->getBlobs();

 // Blobの取得
$blob = $blobRestProxy->getBlob( $container_name, 'path/to/blob' );
ob_start();
fpassthru($blob->getContentStream());
$content = ob_get_contents();
ob_end_clean();
file_put_contents( 'path/to/out', $content );

// Blobの生成(アップロード)
$blobRestProxy->createBlockBlob( $container_name, 'path/to/new_blob', $content );

PHPerのためのMovable Type

ちょっと間空きましたけど、続きます。PHPでワーカー/タスクを作る方法。

DynamicMTMLでタスク/ワーカーを作る

config.php によるDynamicMTML対応プラグインを作ります。詳しくはその11を参照。

タスクとワーカーの違いは、

  • ワーカー = データベースに保存されたキューを実行する
  • タスク = キューの有無にかかわらず定期的にコードを実行する

キューを登録するのは主にPerl側のプラグインになるでしょう。こちらの作成方法はMTのドキュメントを参考に。

通常はplugins/Foo/Worker/WorkerFoo.pm を作って登録することになります。

// plugins/Foo/php/config.php

<?php
class Foo extends MTPlugin {
    var $app;
    var $registry = array(
        'name' => 'Foo',
        'task_workers' => array(
              'worker_foo' => array(
                            'label' => 'Label of Foo',
                            'code'  => 'worker_foo',
                            'class' => 'Foo::Worker::WorkerFoo', ),
        ),
    );
    function worker_foo ( &$app, $jobs ) {
        $mt = MT::get_instance();
        foreach ( $jobs as $job ) {
            $meta = $job->arg;
            $meta = preg_replace( "/^.*(SERG)/", '$1', $meta );
            $meta = $mt->db()->unserialize( $meta );
            // ワーカーの処理
        }
    }
}

第二引数 $jobs に、キューに保存されたジョブの配列が渡されるので、これに処理を書いて行けば良いです。$job->arg にシリアライズされたデータが入っているケースがありますが、$mt->db()->unserialize( $meta ); で配列に割り戻せます。

RebuikdTriggerというプラグインがあります。これ相当の処理はPowerCMSに含まれていますが、再構築のトリガーを細かく制御したい時に利用できる便利プラグインです。

ドキュメントは下記のエントリに記載したのですが、記載されているのは一部の機能のみで、他にもこのプラグインで実現できることがあります。ちょうど機能新しい機能(テンプレートタグ)を追加したので、紹介します。

システムプラグイン設定から再構築トリガーを登録できます。トリガーはcgiのパラメーターで設定します。書式はYAML形式です。

URLが mt.cgi?__mode=cfg_prefs&_type=blog&saved=1 の時、テンプレートのIDが1、2のテンプレートを再構築するには以下のように記述します。再構築出来るのはインデックスアーカイブのみです。YAMLの中に1等のテンプレートタグが記述できます。

TriggerName:
    params:
        - __mode=cfg_prefs
        - _type=blog
        - saved=1
    template_id:
        - 1
        - 2

このプラグインではテンプレート・タグによって再構築トリガーを指定することができます。タグが実行(ビルド)される時に指定されたテンプレートやブログを再構築するような動作をします。タグはいずれもファンクションタグです。

MTRebuildBlog

ブログをまるっと再構築します。archive_typeモディファイアを指定することで、指定されたアーカイブのみを対象にすることができます。以下の例では、ブログ(IDが、1、2)の記事アーカイブを再構築します。

<MTRebuildBlog archive_type="Individual" blog_ids="1,2">

制限事項なのですが、このタグで再構築できるのは、MT標準のアーカイブのみです。PowerCMSのカスタムオブジェクトアーカイブやフォルダアーカイブ、タグアーカイブといった拡張されたアーカイブは再構築できません(これらのアーカイブの再構築トリガーについては後述します)。

MTRebuildIndexById

トップページを再構築する、など特定のインデックステンプレートへのトリガーに利用できます。モディファイアにはテンプレートのIDを指定します。

<MTRebuildIndexById id="77">

MTRebuildIndexByBlogId

ブログIDを指定して、インデックスアーカイブをまるっと再構築します。上記のタグでも実現できるのですが。

<MTRebuildIndexByBlogId blog_id="1">

この指定は、以下の指定と同様となります。

<MTRebuildBlog archive_type="index" blog_id="1">

MTRebuild

あららに追加したタグがこれになります。urlモディファイアを指定して再構築を行いますので、これはMT標準以外の拡張アーカイブにも対応できます。カスタムオブジェクトアーカイブなどにも有効です(が、PowerCMSとの併用については未サポート(怒られるのでw))、次期バージョンでPowerCMSがサポートするまでお待ちくださいm(_ _)m。

以下は、記事の同一カテゴリに属する記事アーカイブを再構築するテンプレートの例です。昨日紹介した 横着なMTSetVar - SetFields2Varsプラグイン を併用しています。

<MTEntryCategory setvar="cat_name">
<MTEntries lastn="100" category="$cat_name">
<MTSetFields2Vars object="entry">
<MTRebuild url="$permalink" blog_id="$blog_id">
</MTEntries>

皆さん、ブログ書いてますか? プラグイン書いてますかー?

We Love Blog

mt:ifで比較したいためだけにイチイチSetVarするの面倒くさくないですか?

<mt:EntryTitle setvar="entry_title">
<mt:if name="entry_title" like="Movable Type">
これはMovable Typeについて書かれた記事です。
</mt:if>

みたいな。一つくらいならまぁいいんですけど、比較のためだけにいくつも書くのってスマートじゃなくない?

そこで、SetFields2Varsというプラグインを作った。

<mt:SetFields2Vars object="entry" prefix="entry.">

# これ以降、mt_entryのカラムの値が varにセットされます。

<mt:var name="entry.title"> => 記事のタイトル
<mt:var name="entry.text"> => 記事の本文
<mt:var name="entry.permalink"> => 記事のURL
<mt:var name="entry.field.basename"> => カスタムフィールドの値

URLが変わらないのがもちろん一番なんですけどね。変わってしまうとき、URLのリダイレクト設定をしなければならないことがあります。

旅行ブログ、ノーマルブログの「ブログ記事」「ブログ記事一覧(カテゴリー)」のアーカイブマッピングを変更しました。

そのため、テーマを更新するとブログ記事ページとカテゴリーページのURLが変わります。旧URLから新URLへリダイレクトさせるテンプレートを用意しました。

プラグインを書いてダイナミックパブリッシングエラーテンプレートを利用する

.htaccessでリダイレクトする例が紹介されています。これはこれでいいのですが、.htaccessの場合、すべてのリクエストに対して処理が入るので、ちょっとでも負荷を減らしたいという時の裏技? として、ダイナミックパブリッシングエラーテンプレートを使う方法をご紹介します。

以下のファイルを用意します。

plugins/MyRedirect/php/function.mtmyredirect.php

ダイナミックパブリッシングエラーテンプレートの先頭に、以下のようにタグを記述します。

<mt:MyRedirect>

# <mt:MyRedirect>は存在しません、とか言われますが気にしなくていいです。
# スタティック用のタグがないだけで、ちゃんとこれで動作するので。

DynamicMTMLが有効なら、エラーページのURLを簡単に得る方法があるのですが、そうでなければ以下のようなテンプレートにして、エラーメッセージからパスを取得しても良いでしょう。

<mt:ErrorMessage setvar="error_message">
<mt:MyRedirect error="$error_message">

function.mtmyredirect.php の内容は以下のような感じです。

続き。これでMovable Typeクラウド版が売れてもオイラの懐には一銭も入らんので(アフェリエイトとかないのかw)単なる感想です。ちなみに、PowerCMSはクラウド版のMT上では動きません。このエントリは以下の記事の続き。

結論から言うと、複数台でロードバランスするような大規模な案件であったり、サイトのフロント側で動的で複雑な処理をするような案件でなければ(とはいえPHPは使えるし、cgiも設置できる)良い選択ではないかと思います。特にサーバー管理者のいない組織では。

要するに、

結構いいんじゃないですか? 選択肢として検討に値する

Movable Type Cloud

実案件で使うにはXSi プランのメモリ0.5GBってのがちょっと不安かなと思うので、実質S2i プランからになると思います。税別で14,200円/月。9万円のライセンスを購入して自前でサーバーに設置するのかクラウド版を選ぶのかについてはまぁ要件に応じて検討すれば良いと思う。

クラウド版とはいっても、オートスケールとかロードバランサを使った構成とかはないようなので、一台サーバーで運用するのが前提。但し、サーバー配信機能があるので、FTPで別サーバーに配信すれば公開側は複数台運用もできる。

サーバー配信の設定画面

これ、実際に使って運用したことはないんですが、画面を見る限り日時を指定しないといけないようで、そうであればちょっと毎回セットするってのは不便かも。

スケジュール・タスクは5分おきに実行されている

ちょっと短すぎないかこれと思うけども。タスク溜まりまくったら終了しないなんてことにもつながりそうで。再構築キューとかで大量のファイルの再構築を非同期にしようとかすると辛いかも。通常のスタティックで動かすなら問題ないと思いますが。

addonsは設置できないので、plugins以下に置く

見出しの通り。現状ではaddonsは設置できないし、標準プラグインやMTのソースにはアクセスできない。自分でパッチを当てることはできない。お任せとのトレードオフ。

プラグインを認識させるには「全般設定」にアクセスし、保存

常駐型なので、プラグインを設置したり直接編集しても反映されない。認識させるためには「全般設定」にアクセスし、設定を保存する必要がある。

mt-foo.cgiは各プラグインフォルダ直下に置く

PSGI/Plackに対応したプラグインにして、plugins/Foo以下に置く。パーミッション等は特に意識せずそのまま(747)。

plugins/Foo/mt-foo.cgi
# に設置した場合、以下のURLになる。
https://example.com/mt/mt-foo.cgi

MemcachedServers設定済み

mt-cofig.cgiへはアクセスできないので、以下のテンプレートで調べた。

<MTVar name="config.MemcachedServers">
127.0.0.1:11211

.htaccessが使える

Apacheと全互換ではないような気がするけども、ダイナミックパブリッシングの設定をすると.htaccessが生成されて、mtview.phpにちゃんとリライトされる。

PHPが利用可能。

但し、PHPファイルを直接変更したときはプラグインを認識させる時と同じ手順で全般設定を保存しなければならない。但し管理画面からテンプレート出力したファイルはこの限りではない。 尚、DynamicMTMLはpluginsディレクトリに設置することでちゃんと動く模様。

立ち位置を最初に書かないとな。一応ウェブ系なんだけど、境目がだんだん曖昧になってきていると感じる今日この頃です。

エンタープライズ系ってなんだろう。ウェブ系ってなんだろう。勝手に脳内イメージを言語化してみた。読者諸氏のコメントを待つ。(想像でものを言っています)

IEの脆弱性問題を受けて、どういう行動に出るかで比較すると良くわかると思う。

ウェブ系

「Chromeのダウンローダー作りましょう。Internet Explorer使わずにダウンロードできるソフト。」

エンタープライズ系

「FirefoxをCD-ROMに焼いて顧客に売りに行きましょう。」

で、続きがあって、

「Firefoxだと顧客に納めた業務アプリが動作しません。」

「そっちはそっちで後から営業に見積もり出しに行かせろ。」

みたいなことに、な、ならないか。

追記

これはエンタープライズ系に対する皮肉でも何でもなくて、顧客の実情を知っていればこういう発想が出てきても全然不思議じゃないし、「顧客のために、顧客の必要としているものを迅速に提供して、報酬をもらう」という普通のビジネス感覚をもっていれば出てくる発想なんじゃないかと思うのですね。

Facebook

Twitter

このアーカイブについて

このページには、2014年5月に書かれたブログ記事が新しい順に公開されています。

前のアーカイブは2014年4月です。

次のアーカイブは2014年6月です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

Powered by Movable Type 6.2.6