PowerCMS ブログ

2010年12月25日

New DynamicMTML(PHPによるMovable Typeの拡張)-2

引き続き新しいDynamicMTMLの拡張方法についてご紹介します。

PHPによるコールバックプラグイン

コールバックプラグインはDynamicMTMLの処理の各ポイントで呼び出されるコールバックイベントに対応するPHPコードです。コンテキストの内容を設定したりコンテンツやテンプレートに対して何らかの処理を割り込ませることが出来ます。

PHPによってプラグインを書くことができるため、Wordpressやその他のCMS用のプラグインを移植したりSmartyのプラグインをほぼそのまま利用することが可能です。

コールバックプラグインは/mt/plugins/PluginName/php/callbacks/以下にpluginname_callbackname.phpのような名前でファイルを設置し、function pluginname_callbackname($mt,&$ctx,&$args,&$content)という関数を定義することで呼び出されます(実行順はプラグイン名の昇順です)。

ビルド処理の後で呼ばれたコールバックでは第4引数$contentにビルドされた結果が含まれているため、&$contentとして値を受け取ることでコンテンツの内容を変更することが可能です。

DynamicMTMLの初期化直後には第3引数$argsには標準では下記の値が含まれています。これらの値は$app->stash()で取得することもできます(プラグイン内で再定義することも可能です)。

$argsに含まれる値

blog_idブログID
conditional条件付き取得が有効かどうか
use_cacheキャッシュを利用する設定であるかどうか
cache_dirキャッシュディレクトリへのパス
file現在のリクエストに対するサーバー上のファイルパス
base$app->base()と同様
path$app->path()と同様
script$app->script()と同様
request現在のURLからクエリー文字列を削除したURL情報
param$app->query_string()と同様
is_secure$app->is_secure()と同様
url現在のURL
contenttypeリクエストに対するmime_type
extensionリクエストされているファイルの拡張子
build_type'dynamic_mtml(DynamicMTML)','static_text(MTタグを含まないテキストファイル)','binary_data(バイナリファイル)','mt_dynamic(MTのダイナミックパブリッシング)'

コールバック(DynamicMTMLの処理におけるデフォルト・コールバック)

init_request()

アプリケーションの実行前に呼び出されます。

pre_run($mt,$ctx,$args)

MT::get_instanceを呼び出す直前に呼び出されます。この段階では$mt,$ctxは未定義です。$argsに初期値が格納されています。

post_init($mt,$ctx,$args)

MT::get_instanceの直後(DBへのアクセスが確立されMTが初期化された後)に$mt,$ctx,$argsパラメタを付けて呼び出されます。

mt_init_exception($mt,$ctx,$args,$error)

MT::get_instanceに失敗した時に呼び出されます。この段階では$mt,$ctxは未定義です。$errorにはMTInitExceptionによってthrowされたエラーメッセージが格納されています。独自に定義したエラーを返したり他のデータベースへ接続をリトライして処理を続行する等の処理の実装が可能です。

pre_resolve_url($mt,$ctx,$args)

DynamicMTML処理が行われる際に(存在するファイルをビルドするケース)resolve_urlをコールする直前に呼び出されます。この時 $args['build_type'] には'dynamic_mtml'が格納されています。

post_resolve_url($mt,$ctx,$args)

DynamicMTML処理が行われる際に(存在するファイルをビルドするケース)resolve_urlの直後に呼び出されます。この時 $args['build_type'] には'dynamic_mtml'が、$args['fileinfo'] にはresolve_urlの実行結果(MT::FileInfoオブジェクト)が格納されます。

pre_build_page($mt,$ctx,$args)

ページを読み込む直前(build_typeが'binary_data'の場合は値が返される直前、MTタグをビルド実行する場合はその直前)に呼び出されます。

build_typeが'dynamic_mtml'の場合、$app->stash('text')にビルドされるファイルのデータが含まれています。この内容を書き換えることでビルドされる前にテンプレートに対して処理を行うことができます。

build_page($mt,$ctx,$args,$content)

build_typeが'dynamic_mtml(DynamicMTML)','static_text(MTタグを含まないテキストファイル)','mt_dynamic(MTのダイナミックパブリッシング)'の時にページがビルドされた直後に実行されます。第4引数$contentにビルドされた結果が格納されています。&$contentとして値を受け取ることで、ビルド結果に対して処理を行うことが出来ます。

build_typeが'binary_data'以外の場合、このコールバックの直後にクライアントに対してデータが返されます。

この時、引数$mtはSmartyを拡張したクラスMTが、$contentにはビルドされたコンテンツが入っていますので、既存のSmartyプラグイン(outputfilterやmodifier)をそのまま利用することが出来ます。

例:ホワイトスペースをトリミングする

<?php
function pluginname_build_page ( $mt, &$ctx, &$args, &$content ) {
    require_once( 'outputfilter.trimwhitespace.php' );
    $content = smarty_outputfilter_trimwhitespace( $content, $mt );
?>
post_return($mt,$ctx,$args,$content)

クライアントへデータをリプライした直後に呼び出されます。build_typeが'binary_data(サイズの大きなバイナリファイル)'の時、$contentには何も含まれていません。

pre_save_cache($mt,$ctx,$args,$content)
キャッシュが保存される設定になっている時、キャッシュを保存する直前に呼び出されます。$app->stash('cache')でキャッシュのパス、$contentにキャッシュされる内容が取得できます。
take_down($mt,$ctx,$args,$content)

すべての処理が成功したケースで処理の一番最後に呼び出されます。

take_down_error()

MT::get_instanceに失敗した状態で最後まで処理が実行された場合に呼び出されます。

コールバックプラグインのサンプル

# init_request(Basic認証をかける)
# /plugins/PluginName/php/callbacks/pluginname_init_request.php
<?php
function pluginname_init_request () {
    if ( isset( $_SERVER[ 'PHP_AUTH_USER' ] ) && ( $_SERVER[ 'PHP_AUTH_USER' ] 
            === 'username' && $_SERVER[ 'PHP_AUTH_PW' ]
            === 'password' ) ) {
    } else {
        header( 'WWW-Authenticate: Basic realm=""' );
        header( 'HTTP/1.0 401 Unauthorized' );
        exit();
    }
}
?>
# init_request(別のmt-config.cgiを使って初期化する)
# /plugins/PluginName/php/callbacks/pluginname_init_request.php
<?php
function pluginname_init_request () {
    global $mt_config;
    global $mt_dir;
    $new_config = $mt_dir . DIRECTORY_SEPARATOR . 'mt-alt-config.cgi';
    if ( file_exists ( $new_config ) ) {
        $mt_config = $new_config;
    }
}
?>
# init_request(Perlによる動的ビルドを有効にする)
# /plugins/PluginName/php/callbacks/pluginname_init_request.php
<?php
function pluginname_init_request () {
    global $app;
    $app->stash( 'perlbuild', 1 );
?>
# mt_init_exception(デバッグモード指定時にエラー出力して終了する)
# /plugins/PluginName/php/callbacks/pluginname_mt_init_exception.php
<?php
function pluginname_mt_init_exception ( &$mt, &$ctx, &$args, $error ) {
    global $app;
    if ( $app->config( 'DebugMode' ) ) {
        echo htmlspecialchars( $error );
        exit();
    }
}
?>
# mt_init_exception(mt-alt-config.cgiの設定を使ってインスタンス生成をリトライする)
# /plugins/PluginName/php/callbacks/pluginname_mt_init_exception.php
<?php
function pluginname_mt_init_exception ( &$mt, &$ctx, &$args, $error ) {
    global $app;
    global $mt_dir;
    $config = $mt_dir . DIRECTORY_SEPARATOR . 'mt-alt-config.cgi';
    if ( file_exists ( $config ) ) {
        global $mt_config;
        global $blog_id;
        $mt_config = $config;
        try {
            $mt = MT::get_instance( $blog_id, $mt_config );
        } catch ( MTInitException $e ) {
            $mt = NULL;
        }
        if ( isset ( $mt ) ) {
            $app->configure( $mt_config );
            $app->init_callback_dir();
        }
    }
?>
# post_init(ログイン(username,passwordパラメタによるログインと$app->userのセット)/ログアウト)
# /plugins/PluginName/php/callbacks/pluginname_post_init.php
<?php
function pluginname_post_init ( $mt, &$ctx, &$args ) {
    if ( $app->mode() == 'login' ) {
       $app->login();
    } elsif ( $app->mode() == 'logout' ) {
       $app->logout();
    }
?>
# post_init(MTのユーザー名+パスワードを利用してBasic認証をかける)
# /plugins/PluginName/php/callbacks/pluginname_post_init.php
<?php
function pluginname_post_init ( $mt, &$ctx, &$args ) {
    $app = $ctx->stash( 'bootstrapper' );
    if ( isset( $_SERVER[ 'PHP_AUTH_USER' ] ) &&
       ( $app->is_valid_author( $ctx, $_SERVER[ 'PHP_AUTH_USER' ],
                             $_SERVER[ 'PHP_AUTH_PW' ] ) ) ) {
    } else {
        header( 'WWW-Authenticate: Basic realm=""' );
        header( 'HTTP/1.0 401 Unauthorized' );
        exit();
    }
}
?>
# post_init(MTのログイン機能を利用した認証)
# /plugins/PluginName/php/callbacks/pluginname_post_init.php
<?php
function pluginname_post_init ( $mt, &$ctx, &$args ) {
    $app = $ctx->stash( 'bootstrapper' );
    if (! $timeout = $mt->config( 'UserSessionTimeout' ) ) {
        $timeout = 14400;
    }
    $client_author = $app->get_author( $ctx, $timeout, 'comment' );
    if (! $client_author ) {
        $url = $args[ 'url' ];
        $return_url  = $mt->config( 'CGIPath' );
        $return_url .= $mt->config( 'CommentScript' );
        $return_url .= '?__mode=login&blog_id=' . $ctx->stash( 'blog_id' );
        $return_url .= '&return_url=' . rawurlencode( $url );
        $app->redirect( $return_url );
        exit();
    }
?>
# post_init(http://example.com/entry_1/パス名/ へのリクエストをhttp://example.com/entry_1/index.htmlへのリクエストとして処理する)
# 例:ブログ記事のアーカイブマップを「entry_<$mt:EntryID$>/%i」カテゴリのアーカイブマップを「category_<$mt:CategoryID$>/%i」として、テンプレート内の「<$mt:EntryPermalink$>」を「<$mt:EntryPermalink$><$mt:EntryTitle make_seo_basename="50"$>/」に<$mt:CategoryArchiveLink$>を「<$mt:CategoryArchiveLink$><$mt:CategoryLabel make_seo_basename="50"$>/」に変更することで、日本語URLによるページへのアクセスが可能になります。
# /plugins/PluginName/php/callbacks/pluginname_post_init.php
<?php
function pluginname_post_init ( $mt, &$ctx, &$args ) {
    $app = $ctx->stash( 'bootstrapper' );
    $file = $app->stash( 'file' );
    $url  = $app->stash( 'url' );
    $request = $app->stash( 'request' );
    if (! file_exists ( $file ) ) {
        $file = $app->path2index( $file, 'index.html' );
        if ( file_exists ( $file ) ) {
            $request = $app->path2index( $request );
            $url     = $app->path2index( $url );
            $app->stash( 'file', $file );
            $app->stash( 'request', $request );
            $app->stash( 'url', $url );
            $app->stash( 'contenttype', 'text/html' );
            $app->stash( 'extension', 'html' );
            $cache = $app->cache_filename( $ctx->stash( 'blog_id' ), $file, $app->query_string );
            $app->stash( 'cache', $cache );
        }
    }
?>
# pre_build_page(http://example.com/entry_1/ へのリクエストをエントリー(又はカテゴリ)の名前を用いて生成したURL http://example.com/entry_1/日本語を含む_パス名(<$mt:entrypermalink make_seo_basename="50"$>)/へ恒久的リダイレクトする)
# /plugins/PluginName/php/callbacks/pluginname_pre_build_page.php
<?php
function pluginname_pre_build_page ( $mt, &$ctx, &$args ) {
    $app = $ctx->stash( 'bootstrapper' );
    $request = $app->stash( 'request' );
    if ( preg_match( '!/$!', $request ) ) {
        $file = $app->stash( 'file' );
        $blog_id = $app->blog_id;
        if ( file_exists( $file )  && preg_match( '!/index\.html$!', $file ) ) {
            $fileinfo = $app->stash( 'fileinfo' );
            require_once( 'MTUtil.php' );
            if (! isset( $fileinfo ) ) {
                $fileinfo = $mt->db()->resolve_url( $mt->db()->escape( urldecode( $request ) ),
                                                    $blog_id, array( 1, 2, 4 ) );
            }
            if ( isset( $fileinfo ) ) {
                $app->stash( 'fileinfo', $fileinfo );
                $entry_id = $fileinfo->entry_id;
                $category_id = $fileinfo->category_id;
                if ( $entry_id || $category_id ) {
                    $obj = NULL;
                    if ( $entry_id ) {
                        if ( $fileinfo->archive_type == 'Page' ) {
                            $obj = $mt->db()->fetch_page( $entry_id );
                        } else {
                            $obj = $mt->db()->fetch_entry( $entry_id );
                        }
                    } elseif ( $category_id ) {
                        $obj = $mt->db()->fetch_category( $category_id );
                    }
                    if ( isset( $obj ) ) {
                        $title = NULL;
                        if ( $entry_id ) {
                            $title = $obj->title;
                        } elseif ( $category_id ) {
                            $title = $obj->label;
                        }
                        $title = strip_tags( $title );
                        if ( $title ) {
                            require_once ( 'dynamicmtml.util.php' );
                            $title = make_seo_basename( $title, 50 );
                            $url = $request . $title . '/';
                            $app->moved_permanently( $url );
                            exit();
                        }
                    }
                }
            }
        }
    }
}
?>
# build_page(携帯キャリアからのアクセス時にソースに含まれる画像をサムネイルに自動変換する)
# /plugins/PluginName/php/callbacks/pluginname_build_page.php
<?php
function pluginname_build_page ( $mt, &$ctx, &$args, &$content ) {
    $app = $ctx->stash( 'bootstrapper' );
    require_once ( 'dynamicmtml.util.php' );
    if ( $app->get_agent( 'keitai' ) ) {
        $type = 'auto';
        $scope = 'width';
        // $agent = $app->get_agent();
        // if ( $agent == 'DoCoMo' ) {
        //     $type = 'gif';
        // } else {
        //     $type = 'png';
        // }
        $content = convert2thumbnail( $content, $type, 100, 480, $scope );
    }
}
?>
# build_page(携帯キャリアからのアクセス時にShift_JIS変換する)
# /plugins/PluginName/php/callbacks/pluginname_build_page.php
<?php
function pluginname_build_page ( $mt, &$ctx, &$args, &$content ) {
    if ( $app->get_agent( 'keitai' ) ) {
        $charset = strtolower( $ctx->mt->config( 'PublishCharset' ) );
        $charset = preg_replace( '/\-/', '_', $charset );
        if ( $charset != 'shift_jis' ) {
            $pattern = '/<\?xml\s*version\s*=\s*"1.0"\s*encoding\s*=\s*"UTF-8"\s*\?>/s';
            $replace = '<?xml version="1.0" encoding="Shift_JIS"?>';
            $content = preg_replace( $pattern, $replace, $content );
            $pattern = '/<meta\s*http\-equiv\s*=\s*"Content\-Type"\s*content\s*=\s*"text\/html;\s*charset=UTF\-8"\s*\/>/s';
            $replace = '<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS" />';
            $content = preg_replace( $pattern, $replace, $content );
            $content = mb_convert_encoding( $content, 'SJIS-WIN', 'UTF-8' );
        }
    }
?>
# build_page(検索エンジン,サイト内検索から流入したユーザーの検索キーワードをハイライト表示する)
# /plugins/PluginName/php/callbacks/pluginname_build_page.php
<?php
function pluginname_build_page ( $mt, &$ctx, &$args, &$content ) {
    if ( preg_match( '/(^.*?<body.*?>)(.*?$)/si', $content, $html ) ) {
        $head = $html[1];
        $text = $html[2];
        require_once ( 'dynamicmtml.util.php' );
        $tag_start  = '<strong class="search-word">';
        $tag_end    = '</strong>';
        $qtag_start = preg_quote( $tag_start, '/' );
        $qtag_end   = preg_quote( $tag_end, '/' );
        $keywords   = array();
        $phrase = referral_search_keyword( $ctx, $keywords );
        foreach ( $keywords as $keyword ) {
            $keyword = htmlspecialchars( $keyword );
            $keyword = trim( $keyword );
            $keyword = preg_quote( $keyword, '/' );
            $pattern1 = "/(<[^>]*>[^<]*?)($keyword)/i";
            $replace1 = '$1' . $tag_start. '$2' . $tag_end;
            $pattern2 = "/($qtag_start)$qtag_start($keyword)$qtag_end($qtag_end)/i";
            $replace2 = '$2$3$4';
            $i = 0;
            while (! $end ) {
                $original = $text;
                $text = preg_replace( $pattern1, $replace1, $text );
                //Nest tag
                $text = preg_replace( $pattern2, $replace2, $text );
                if ( $text == $original ) {
                    $end = 1;
                }
                $i++;
                //Infinite loop
                if ( $i > 20 ) $end = 1;
            }
            unset( $end );
        }
        $content = $head . $text;
    }
?>
# build_page(電話番号をリンクに置換する)
# /plugins/PluginName/php/callbacks/pluginname_build_page.php
<?php
function pluginname_build_page ( $mt, &$ctx, &$args, &$content ) {
    if ( $app->get_agent( 'keitai' ) || $app->get_agent( 'smartphone' ) ) {
        if ( preg_match( '/(^.*?<body.*?>)(.*?$)/si', $content, $html ) ) {
            $head = $html[1];
            $text = $html[2];
            require_once ( 'dynamicmtml.util.php' );
            $tag_1 = '<a href ="tel:';
            $tag_2 = '">';
            $tag_3 = '</a>';
            $pattern1 = '/(<[^>]*>[^<]*?)(0\d{1,4}-\d{1,4}-\d{3,4})/';
            $replace1 = '$1' . $tag_1 . '$2' . $tag_2 . '$2' . $tag_3;
            $pattern2 = '/(<a.*?>\/*)<a.*?>(0\d{1,4}-\d{1,4}-\d{3,4})<\/a>([^<]*?<\/a>)/';
            $replace2 = '$2$3$4';
            $i = 0;
            while (! $end ) {
                $original = $text;
                $text = preg_replace( $pattern1, $replace1, $text );
                //Nest tag
                $text = preg_replace( $pattern2, $replace2, $text );
                if ( $text == $original ) {
                    $end = 1;
                }
                $i++;
                //Infinite loop
                if ( $i > 20 ) $end = 1;
            }
            unset( $end );
            $content = $head . $text;
        }
    }
?>
# build_page(ホワイトスペースを除去する)
# /plugins/PluginName/php/callbacks/pluginname_build_page.php
<?php
function pluginname_build_page ( $mt, &$ctx, &$args, &$content ) {
    require_once( 'outputfilter.trimwhitespace.php' );
    $content = smarty_outputfilter_trimwhitespace( $content, $mt );
?>
# post_return(アクセスログを記録する)
# /plugins/PluginName/php/pluginname_post_return.php
<?php
function pluginname_post_return ( $mt, &$ctx, &$args, &$content ) {
    $app = $ctx->stash( 'bootstrapper' );
    $url = $app->stash( 'url' );
    if ( $url ) {
        $app->log( $url );
    }
}
?>
# post_return(検索経由のアクセスの場合にキーワードをアクセスログに記録する)
# /plugins/PluginName/php/callbacks/pluginname_post_return.php
<?php
function pluginname_post_return ( $mt, &$ctx, &$args, &$content ) {
    $keyword = referral_search_keyword( $ctx );
    if ( $keyword ) {
        $keyword = trim( $keyword );
        $url = $app->stash( 'url' );
        $referral_site = referral_site();
        $app->log( "url : $url\nreferral_site : $referral_site\nkeyword : $keyword" );
    }
}
?>
カテゴリー
DynamicMTML
PowerCMS 2
プラグイン
技術情報

ページの先頭へ