MTプラグインの重複配置は動作異常になる可能性があります

MTプラグインの重複配置は動作異常になる可能性があります

Posted at December 24,2019 11:59 PM
Tag:[MovableType]

この記事は Movable Type Advent Calendar 2019 24日目の記事です。

MTプラグインの重複配置は動作異常になる可能性があります、というお話です。

1.はじめに

当方で開発しているMTプラグインを導入頂いたお客様から、「期待通りに動作しない」というご質問を、年に2~3回頂きます。

プラグインのバグもありますが、そうでない場合の原因として最も多いのが「古いバージョンの同じプラグインをpluginsディレクトリに置いている」です。

例えば、Workflowプラグインを購入されたお客様が、プラグインのバージョンアップで、前のバージョンのWorkflowプラグインのディレクトリ名を、"Workflo-backup"のようにリネームして、pluginsディレクトリに置いたままとします。

お客様は、リネームした古いプラグインは動作しないと思われているようですが、リネームしたプラグインは、バージョンアップしたプラグインとは別のプラグインとして登録されてしまいます。

同じ処理が2つ登録されてしまうと、perlとして正常に動作しなくなるようで、これが原因でお問い合わせがくるわけです。

結論から申し上げますと、プラグインのバージョンアップあるいは、プラグインの評価版から製品版に乗り換えられる際、前のプラグインはpluginsディレクトリから必ず削除または移動してください。

2.2つのプラグインが登録されてしまう仕組み

MTのプログラムをトレースして、2つのプラグインが登録されてしまう仕組みを説明します。

ブラウザからMTにアクセスするとinit()が起動します。

lib/MT.pm

sub init {
    my $mt    = shift;
    my %param = @_;
 
    $mt->bootstrap() unless $MT_DIR;
    $mt->{mt_dir}     = $MT_DIR;
    $mt->{config_dir} = $CFG_DIR;
    $mt->{app_dir}    = $APP_DIR;
 
    $mt->init_callbacks();
 
    ## Initialize the language to the default in case any errors occur in
    ## the rest of the initialization process.
    $mt->init_config( \%param ) or return;
    $mt->init_lang_defaults(@_) or return;
    require MT::Plugin;
    $mt->init_addons(@_) or return;
    $mt->init_config_from_db( \%param ) or return;
    $mt->init_debug_mode;
    $mt->init_plugins(@_) or return;★

★でinit_plugins()が起動されます。

sub init_plugins {
    my $mt = shift;
 
    # Load compatibility module for prior version
    # This should always be MT::Compat::v(MAJOR_RELEASE_VERSION - 1).
    if ( MT->config('RequiredCompatibility') < 4.0 ) {
        require MT::Compat::v3;
    }
 
    my $cfg          = $mt->config;
    my $use_plugins  = $cfg->UsePlugins;
    my @PluginPaths  = $cfg->PluginPath;
    my $PluginSwitch = $cfg->PluginSwitch || {};
    my $plugin_sigs  = join ',', sort keys %$PluginSwitch;
    $mt->_init_plugins_core( $PluginSwitch, $use_plugins, \@PluginPaths );★

★で_init_plugins_core()が起動されます。

    sub _init_plugins_core {
        my $mt = shift;
        my ( $PluginSwitch, $use_plugins, $PluginPaths ) = @_;
 
        my $timer;
        if ( $mt->config->PerformanceLogging ) {
            $timer = $mt->get_timer();
        }
 
        foreach my $PluginPath (@$PluginPaths) {
            my $plugin_lastdir = $PluginPath;
            $plugin_lastdir =~ s![\\/]$!!;
            $plugin_lastdir =~ s!^.*[\\/]!!;

if ( opendir my $DH, $PluginPath ) {
my @p = readdir $DH;★

ここが原因の根本(悪い意味ではなく実装上の仕様)ですが、★のreaddirで、指定されたプラグインパス(デフォルトはplugins)配下のディレクトリをすべて読み込みます。

先の例であれば、

$VAR1 = [ '.', '..', 'BlockEditor', 'Comments', 'FacebookCommenters', 'FormattedText', 'FormattedTextForTinyMCE', 'GoogleAnalytics', 'Markdown', 'OpenID', 'spamlookup', 'Textile', 'TinyMCE', 'Trackback', 'WidgetManager', 'Workflow', 'Workflow-backup', 'WXRImporter' ];

のように、'Workflow'と'Workflow-backup'の2つが含まれます。

このあとの処理で、yaml形式のプラグインであれば、

                closedir $DH;
                for my $plugin (@p) {
                    next if ( $plugin =~ /^\.\.?$/ || $plugin =~ /~$/ );
 
                    $plugin_full_path
                        = File::Spec->catfile( $PluginPath, $plugin );
                    if ( -f $plugin_full_path ) {
                        $plugin_envelope = $plugin_lastdir;
                        __load_plugin( $mt, $timer, $PluginSwitch,
                            $use_plugins, $plugin_full_path, $plugin )
                            if $plugin_full_path =~ /\.pl$/;
                        next;
                    }
 
                    my $plugin_dir = $plugin;
                    $plugin_envelope = "$plugin_lastdir/" . $plugin;
 
                    foreach my $lib (qw(lib extlib)) {
                        my $plib
                            = File::Spec->catdir( $plugin_full_path, $lib );
                        unshift @INC, $plib if -d $plib;
                    }
 
                    # handle config.yaml
                    my $yaml = File::Spec->catdir( $plugin_full_path,
                        'config.yaml' );
 
                    if ( -f $yaml ) {
                        __load_plugin_with_yaml( $use_plugins, $PluginSwitch,
                            $plugin_dir );★
                        next;
                    }

★で__load_plugin_with_yaml()が実行され、

    sub __load_plugin_with_yaml {
        my ( $use_plugins, $PluginSwitch, $plugin_dir ) = @_;
        my $pclass
            = $plugin_dir =~ m/\.pack$/
            ? 'MT::Component'
            : 'MT::Plugin';
 
        # Don't process disabled plugin config.yaml files.
        if ($pclass eq 'MT::Plugin'
            && (!$use_plugins
                || ( exists $PluginSwitch->{$plugin_dir}
                    && !$PluginSwitch->{$plugin_dir} )
            )
            )
        {
            $Plugins{$plugin_dir}{full_path} = $plugin_full_path;
            $Plugins{$plugin_dir}{enabled}   = 0;
            return;
        }
        return if exists $Plugins{$plugin_dir};
        my $id = lc $plugin_dir;
        $id =~ s/\.\w+$//;
        my $p = $pclass->new(
            {   id       => $id,
                path     => $plugin_full_path,
                envelope => $plugin_envelope
            }
        );
 
        # rebless? based on config?
        local $plugin_sig = $plugin_dir;
        $PluginSwitch->{$plugin_sig} = 1;
        MT->add_plugin($p);
        $p->init_callbacks();
    }

プラグインとして追加されてしまうようです(これ以降トレースしていないので認識誤りでしたらお許しを)。

なお、システム管理画面のプラグイン一覧には、リネームしたプラグインは新しいプラグインと(コンフィグの設定が)同じなので、2つ表示されることはありません。

ということで、旧プラグインはリネームではなく、別のディレクトリに移動しましょう。

この記事には続きがありますが、時間切れなのでこの辺で。

関連記事
トラックバックURL


コメントする
greeting

*必須

*必須(非表示)


ご質問のコメントの回答については、内容あるいは多忙の場合、1週間以上かかる場合があります。また、すべてのご質問にはお答えできない可能性があります。予めご了承ください。

太字イタリックアンダーラインハイパーリンク引用
[サインインしない場合はここにCAPTCHAを表示します]

コメント投稿後にScript Errorや500エラーが表示された場合は、すぐに再送信せず、ブラウザの「戻る」ボタンで一旦エントリーのページに戻り(プレビュー画面で投稿した場合は、投稿内容をマウスコピーしてからエントリーのページに戻り)、ブラウザをリロードして投稿コメントが反映されていることを確認してください。

コメント欄に(X)HTMLタグやMTタグを記述される場合、「<」は「&lt;」、「>」は「&gt;」と入力してください。例えば「<$MTBlogURL$>」は「&lt;$MTBlogURL$&gt;」となります(全て半角文字)