08 May 2017

MovableTypeでカテゴリーの重複登録を解消する

MovableTypeで、同一カテゴリーにプライマリー・カテゴリーとサブ・カテゴリーで重複登録されている場合、一括してサブ・カテゴリー側を削除して整理するスクリプト。

大量に対象エントリーが存在する場合は、スクリプトで一気に処理したほうが楽なため…

スクリプト・ファイル20170508-MtCategoryFix.pl.txtをダウンロードする

MtCategoryFix.pl
#!/usr/bin/perl
 
# MovableType で、同一カテゴリーにプライマリー・カテゴリーとサブ・カテゴリーで重複登録
# されている場合、サブ・カテゴリー登録を削除して整理するPerlスクリプト
#
# MovableTypeのデータベースがsqlite形式(utf8)で作成され、このスクリプトをDBファイルと
# 同一のディレクトリに置いて実行すること
# このスクリプト、スクリプトを実行する環境、アクセスするファイルの全てがutf8を仮定している
# ため、あえて「use utf8」など行わず、文字コード変換は省いてコードをすっきりさせている。
#
# ver 1.0   (C) r271-635   2017/05/05
#
 
use strict;    # 変数の宣言を強制する
use warnings;
use FindBin;
use DBI;       # DBI モジュールを利用する
 
# このスクリプトファイルが置かれているディレクトリ名を得る
my $scriptDir = $FindBin::Bin;
 
# sqlite DBファイルのDSN(フルパス名)
my $strDsn = "DBI:SQLite:dbname=" . $scriptDir . "/mtdata.db";
print "DSN =" . $strDsn . "\n";
 
# テーブル名
my $strTablePlacement = "mt_placement";
my $strTableCategory  = "mt_category";
my $strTableEntry     = "mt_entry";
 
main();
 
exit;
 
# メイン サブルーチン
sub main {
    my @arrayPlacementId;    # 消去配列(placement_idを格納)
 
    find_overlap_placement_id( \@arrayPlacementId );
 
    print "削除対象 "
      . ( $#arrayPlacementId + 1 )
      . " 件, placement_id = ";
    foreach my $placement_id (@arrayPlacementId) {
        print $placement_id . ", ";
    }
    print
"\n重複したこれらのカテゴリー指定を削除しますか (y) :";
    $_ = <STDIN>;
    chomp;
    if ( uc($_) ne 'Y' ) {
        die("\nユーザによりキャンセルされました。\n");
    }
 
    delete_placement_record( \@arrayPlacementId );
    print "\n削除完了\n";
 
    return;
}
 
# プライマリー・カテゴリーと、サブ・カテゴリーで、同一のカテゴリー番号が指定されているIDを取得
sub find_overlap_placement_id {
    my ($refarrayPlacementId) = @_;
 
    my $dbh              = undef;
    my $sth              = undef;
    my $strQuery         = "";
    my @row              = ();      # クエリ結果を受ける配列
    my $foundPlacementId = 0;       # 発見されたmt_placementのID番号
 
    eval {
        # DBに接続
        $dbh =
          DBI->connect( $strDsn, "", "", { PrintError => 1, AutoCommit => 1 } );
        if ( !$dbh ) { die("DBI : error open $strDsn\n"); }
 
        # 「サブ・カテゴリー」エントリーを抽出する
        $strQuery =
            "SELECT placement_id, placement_entry_id, placement_category_id "
          . " FROM " . $strTablePlacement
          . " WHERE placement_is_primary = 0";
        $sth = $dbh->prepare($strQuery);
        $sth->execute() or die("DBI : execute error\n");
        while ( my $hashref = $sth->fetchrow_hashref() ) {
            $foundPlacementId = get_primary_placement_id(
                $hashref->{'placement_entry_id'},
                $hashref->{'placement_category_id'}
            );
            if ( $foundPlacementId != 0 ) {
 
                # 消去対象のplacement_idを消去配列に格納
                push( @$refarrayPlacementId, $hashref->{'placement_id'} );
 
                # 消去対象のmt_placementデータ行を画面表示
                print "  ** delete ** id=" . $hashref->{'placement_id'} . ", ";
                print "entry_id=" . $hashref->{'placement_entry_id'} . ", ";
                print "category="
                  . $hashref->{'placement_category_id'}
                  . ", primary=0\n";
            }
        }
        $sth->finish() or die(DBI::errstr);
        $dbh->disconnect();
    };
    if ($@) {
        if ($dbh) { $dbh->disconnect(); }
        print( "DBI error : " . $@ . "\n" );
    }
 
    return;
}
 
# 指定されたエントリーIDとカテゴリーIDで、プライマリー・カテゴリーに入っている場合は、そのplacement_id番号を返す
sub get_primary_placement_id {
    my ( $entry_id, $category_id ) = @_;
 
    my $dbh              = undef;
    my $sth              = undef;
    my $strQuery         = "";
    my @row              = ();      # クエリ結果を受ける配列
    my $foundPlacementId = 0;       # 発見されたmt_placementのID番号
 
    eval {
        # DBに接続
        $dbh =
          DBI->connect( $strDsn, "", "", { PrintError => 1, AutoCommit => 1 } );
        if ( !$dbh ) { die("DBI : error open $strDsn\n"); }
 
        $strQuery =
            "SELECT mt_placement.placement_id, mt_placement.placement_entry_id,"
          . " mt_placement.placement_category_id, mt_placement.placement_is_primary,"
          . " mt_entry.entry_title, mt_category.category_label "
          . " FROM " . $strTablePlacement
          . " INNER JOIN " . $strTableCategory . ", " . $strTableEntry
          . " ON mt_placement.placement_entry_id = mt_entry.entry_id AND "
          . " mt_placement.placement_category_id = mt_category.category_id "
          . " WHERE mt_placement.placement_entry_id = ? AND mt_placement.placement_category_id = ? AND"
          . " mt_placement.placement_is_primary = 1";
        $sth = $dbh->prepare($strQuery);
        $sth->execute( $entry_id, $category_id )
          or die("DBI : execute error\n");
        while ( my $hashref = $sth->fetchrow_hashref() ) {
            $foundPlacementId = $hashref->{'placement_id'};
            print "id=" . $hashref->{'placement_id'} . ", ";
            print "entry_id=" . $hashref->{'placement_entry_id'} . ", ";
            print "category=("
              . $hashref->{'placement_category_id'} . ") "
              . $hashref->{'category_label'} . ", ";
            print "primary=" . $hashref->{'placement_is_primary'} . ",";
            print "title=" . $hashref->{'entry_title'} . "\n";
        }
        $sth->finish() or die(DBI::errstr);
        $dbh->disconnect();
    };
    if ($@) {
        if ($dbh) { $dbh->disconnect(); }
        print( "DBI error : " . $@ . "\n" );
    }
 
    return ($foundPlacementId);
}
 
# mt_placementテーブルより、引数で指定された配列の各要素で示されるplacement_id行を全て削除する
sub delete_placement_record {
    my ($refarrayPlacementId) = @_;
 
    print "削除要素数 : " . @$refarrayPlacementId . "個 削除中 ...\n";
 
    my $dbh      = undef;
    my $sth      = undef;
    my $strQuery = "";
 
    eval {
        # DBに接続
        $dbh =
          DBI->connect( $strDsn, "", "", { PrintError => 1, AutoCommit => 0 } );
        if ( !$dbh ) { die("DBI : error open $strDsn\n"); }
 
        $strQuery =
          "DELETE FROM " . $strTablePlacement . " WHERE placement_id = ?";
        $sth = $dbh->prepare($strQuery);
        foreach my $placement_id (@$refarrayPlacementId) {
            my $result = $sth->execute($placement_id)
              or die("DBI : execute error\n");
            if ( $result == 1 ) {
                print ", " . $placement_id;
            }
            else {
                print ", ERROR[" . $placement_id . "]";
            }
        }
        $sth->finish() or die(DBI::errstr);
        $dbh->commit();
        $dbh->disconnect();
    };
    if ($@) {
        if ($dbh) { $dbh->disconnect(); }
        print( "\nDBI error : " . $@ . "\n" );
    }
 
    return;
}