人気記事を表示するのに GoogleAnalyticsを使ってページビューの順に表示させるという方法(PowerCMSなど)がありますがコメント機能が使われていてコメントが活発にやりとりされているサイトの場合、最近 n日のコメント数を基準にするという方法もありますね。話題になっている記事を抽出するのになかなか適した方法かと思います。

https://github.com/junnama/mt-plugin-lively-discussion-entries

<mt:LivelyDiscussionEntries days="7">
# mt:Entriesと同じタグが利用できる
</mt:LivelyDiscussionEntries>

このような処理の時は MT::Object の count_group_byメソッドを使います。

my $group_iter = MT->model( 'comment' )->count_group_by( {
                                            blog_id => $blog_id,
                                            visible => 1,
                                            created_on => { '>' => $ts } } , {
                                            (),
                                            group => [ 'entry_id' ],} );
my $ids = {};
while ( my ( $count, $entry_id ) = $group_iter->() ) {
    $ids->{ $entry_id } = $count;
}
my $i = 0;
my @entries;
for my $entry_id (sort { $ids->{ $b } <=> $ids->{ $a } } keys %$ids) {
    my $entry = MT->model( 'entry' )->load( $entry_id );
    if ( $entry && $entry->status == MT::Entry::RELEASE() ) {
        push ( @entries, $entry );
        $i++;
        if ( $i >= $lastn ) {
            last;
        }
    }
}

アルファサードでは社内イントラで Xtalk を利用して情報共有を図っています。ま、これは YammerChatter みたいなもんと思ってもらえればいいです。

で、PowerCMSの導入が決まったとか、クライアントのサイトがローンチしたとか大口案件の検収が終ったとか新サービスリリースしたとかのニュースを #goodnews ハッシュタグを付けて投げているのですが、どうもここのところそれが中々定着しない。ただ、これがすごく大切なんだと私は思うのですね。少しその理由を書いてみたいと思います。

会社の空気が悪いニュースに支配されがちな理由

トラブルや問題発生ってのは共有されやすいものです。もちろん組織によるところも多いと思いますが、表立つ、立たないに関わらず「あの件が大変みたいやで」って情報は広がっていきやすい。悪いニュースに頭の中が支配されていると会社の空気がしんどくなるんですね。

良いニュースは共有されにくい

自己矛盾になりますが、ウチのような中小企業の場合、社員に危機感を持たせたり慢心を防ぐ意味から、あまり順調で好業績だよってしょっちゅういわなくなるんですね。どっちかと言えば「まだ目標に到達できていない」「計画までまだ上乗せが必要」というポーズをとることが多くなる。もちろん相当な好業績のときは別ですが、計画到達・未達かどうかはまだ蓋を開けるまでわかんないよ、ってときはそうなりがちです。

#goodnewsは意図的に、少し大げさなくらいに共有したほうがいい

なので、些細なことでも良いニュースは意図的、大げさなくらいに共有、拡散していくといいです。個人でも会社でもそうですけど、良いニュースを積極的に共有しているときは会社の雰囲気っていいんですね。ウチの会社では PowerCMSが軌道に乗り始めた頃は、1導入決まるたびに(その頃は #goodnews ではなく「ワショーイ」発言だったw)共有されてました。ただ、慣れてきちゃうんですね。 なので、そういうの、心がけた方が仕事楽しいよ、っていう話しでした。

この記事は、「やはりお前らのMTMLは間違っている」シリーズ連載第n回目の連載です。

12303936_1018612871539819_7627081906549621209_o

MTタグが数千、下記の例では7500個書いてあるテンプレートの話しです。

    <mt:SetVar name="master_table" key="12000" value="JR山手線">
    <mt:SetVar name="master_table" key="19200" value="東京メトロ千代田線">
    ....同じような記述が7500個くらい記述が続く

DB接続するようなMTタグが一切無いにも関わらず、再構築に数十秒かかります(手元環境では30秒から45秒)。MTSpeenMeterタグを使うとビルドにかかっているのはたかだか3〜4秒。

であれば、ビルドに入る前段階で時間を食っていると考えるのが自然。

時間がかかっているのは MT::Builder の sub compile{}です。要するに正規表現でMTタグをパースする処理。MTタグが多ければ多いほどここに時間がかかります。

要するに、PHPでも以下紹介した事象がスタティックでも発生するということです。しかもPerl、スタティックのパース結果はキャッシュされない(同一リクエスト内ではキャッシュされる模様)。

対策は、MTSetVars、MTSetHashVarsを使うか、素直にデータベースを使う

MTタグの数が少なければパース時間は短くなるため、MTSetVars、MTSetHashVarsを使って纏めて記載する。

でも、この手のデータだったらカスタムオブジェクトでも何でも良いけど素直にデータベース管理が良いと思います。

こんなページができていて、いい傾向じゃないの(上から目線w)、と思ってまして。

ツッコミと言うわけではないですが、僕も最近色々思ってたことが触れられてたので。

ま、同じことなんですけど、何でこういうエントリを書いてるかというと、PerlでMTMLやHTMLを突っ込むのについついPerlのヒアドキュメントを使っちゃうことがあるからなのです。ひとつのファイルで完結するからどうしても手を抜いてそういう実装をしがちなのですが。

僕なら、テンプレートを tmpl/include に置いた上でこう書きます。

sub template_param_edit_entry {
    my ( $cb, $app, $param, $tmpl ) = @_;
    $app->{ plugin_template_path } =
        File::Spec->catfile( MT->component( 'AwesomePlugin' )->path, 'tmpl' );

    my ( $node, $attr, $include );

    $node = $tmpl->getElementById( 'header_include' );
    $attr = { name => 'include/edit_entry_header.tmpl' };
    $include = $tmpl->createElement( 'include', $attr );
    $tmpl->insertBefore( $include, $node );

    $node = $tmpl->getElementById( 'title' );
    $attr = { name => 'include/edit_entry_title.tmpl' };
    $include = $tmpl->createElement( 'include', $attr );
    $tmpl->insertBefore( $include, $node );
}

DOMに慣れている方なら直感的かと思うし。どっちがいいということではなくて、自分が理解しやすい方法で書けばいいと思います、ええ。

※どっちかと言えば各MTタグにidが振られてないとかそういうのが問題かと思う(一回バージョンアップでidが変わったことあるぜ!!)

追記:天野さんから指摘あり。

plugintemplatepath を使う方法は、複数のプラグインで同じことをしようとした時に片方が効かなくなる。

さらに追記

    $app->{ plugin_template_path } =
        File::Spec->catfile( MT->component( 'AwesomePlugin' )->path, 'tmpl' );

    # の代わりに

    $attr = { name => 'include/edit_entry_header.tmpl',
              component => 'AwesomePlugin' };

とする。

sort_by sort_order 指定してみたんだけど、効かない。

  • MT::Template::Tags::Blog::_hdlr_blogs

ハードコーディングされてるんだから。。。

$args{'sort'} = 'name';
$args{direction} = 'ascend';
my @blogs = MT::Blog->load( \%terms, \%args );

こういうところを何とかして欲しいものです。

<mt:Websites>
<mt:WebsiteId setvar="website_id">
<mt:SetHashVars name="website_data">
    name=<mt:WebsiteName>
    url=<mt:WebsiteURL>
</mt:SetHashVars>
<mt:SetHashVar name="websites">
    <mt:SetVar name="$website_id" value="$website_data">
</mt:SetHashVar>
</mt:Websites>
<mt:Loop name="websites" sort_by="key" >
    blog_id : <mt:Var name="__key__">
    blog_name : <mt:Var name="__value__" key="name">
    blog_url : <mt:Var name="__value__" key="url">
</mt:Loop>

追記 : blog_id が10超えるときは 別途並び順用の変数にキーのみ入れて MTLoop sort_by="value numeric" というツッコミが入りました。誰か修正版やっといて。。

追記: GetHashVarプラグインを使うか ObjectLoop プラグインを使えば素直に解決できる

<mt:LoopWithSort name="websites" kind="num" scope="key" order="ascend">
</mt:LoopWithSort>

<mt:ObjectLoop model="website" sort_by="id" sort_order="ascend">
</mt:ObjectLoop>

リッチテキストエディタの是非は別にして、リセットしたいなら局所化しとけよと思ったんだけど、これ普通なんか?エンジニアに必要なのは「想像力」でなはいだろうか?ウェブアクセシビリティ然り。自分の書いたコードがどんな使い方をされるのかを想像できない人はこの仕事向いてないと思うんだが。普通なのかこれ?

html, body, div, span, object, iframe /*略*/ {
  margin: 0;
  padding: 0;
  border: 0;
  outline: 0;
  font-size: 100%;
  vertical-align: baseline;
  background: transparent;
}

リセットされたスタイル。構造が視覚的に把握できない

PowerCMS の Pager や PageBute を利用したページ分割は非常に効率が悪いので記事の件数が半端無く多いシチュエーションでは使わない方が絶対にいいです。

記事の更新頻度によって月別、年度別、年別等の time-basedなアーカイブにしたほうがいい。理由は、time-basedなアーカイブは更新対象のアーカイブのみが再構築されるためです。ページングって、1件増えたら、1件削除されたら全アーカイブが1件だけ「ずれて」全再構築されるのです。1件更新されても全アーカイブが再構築対象になります。

というようなことを口を酸っぱくして言ってんのに、結局破綻してしまったというケースに出くわしたので作成しました。

これ、カテゴリとかもニーズあるよね、MT7では標準機能になるよね、と振るだけ振っておいて。

アーカイブマッピングで「手動」を選択

  • https://github.com/alfasado/mt-plugin-monthly-entries-publisher
  • プラグインをインストール
  • ウェブサイト(ブログでも良い)月別アーカイブを作成し、include_blogsで含めるブログを指定する
  • テンプレートマップの再構築オプションは「手動」とする(「再構築しない」を選択しないこと:「再構築しない」だと書き出されない)
  • MTEntriesの__first__ もしくは MTEntriesHeader 内に 「<!--EntriesCount:<mt:EntriesCount>-->」記述を入れる(ファイル出力時にはこの値は削除されます)
  • mt-config.cgi に MonthlyEntriesPublisherTemplateIds テンプレートのIDカンマ区切りを記載
  • mt-config.cgi に MonthlyEntriesPublisherBlogIds として、記事保存時にキューに入れる対象のブログIDをカンマ区切りで記載

再構築はキューで実行されます。ダイナミックパブリッシングには対応していません。

Data APIから1日数千件のエントリを更新、新規追加するというような要件の案件で現場から「スピードがでないんっすよ」ということでヘルプ要請が来たのですが、「CGIの起動がクソ重いんじゃないの?」ということで複数件のJSONを纏めて投げられるエンドポイントを作成して対応しようとしたのですが、それでもまっっっったくと言っていいほどパフォーマンスの改善が見られない。再構築をキューにして凌ごうと思ったのですがそれでも一件 Insert するのに10秒も20秒もかかるのです。

  • Data APIで記事インサート、アップデート
  • 1件10〜20秒を要する

調べたところ、こいつが問題。

  • 記事カスタムフィールドが200個近く!

コードを見たり、SQLのクエリを見たり。犯人見つけましたぜ。

MTのカスタムフィールドは1フィールド1レコードで、1フィールド1SQLを発行するのです

なのです。こんなこと知ってたらカスタムフィールド200個近くも作成しないですよね? 5件単位でインサートだったらSQL1000クエリ、カスタムフィールドのためだけに投げるわけです。

なので、MySQLの Bulk Insertを使うようにしてみたらめっちゃ速くなったよというお話でした。単位は秒。Updateの場合は Insertの倍のスピードになります。SQLの数が半端ないので、1台構成より DBとCMSがわかれている複数台構成の方が効果は顕著です。

カスタムフィールドをBulk Insertにしたら300%以上高速化されたという話し(効果測定結果)

ObjectDriver が DBI::mysql で、mt-config.cgiに「BulkInsertMeta」環境変数を指定しているときのみ有効になります。あくまでも実験的な位置づけなのでノンサポート、自己責任でお願いします。

/mt/lib/MT/Meta/Proxy.pm

Oracle や SQL Serverに対応できていないので、その辺りが課題ですが、いずれにしても MTには足回りの強化をお願いしたいところです(既にお願いはしておいたんですがね)。

ま、回答数が少ないんだけど半数以上があるという回答ですね。「ある」人がこういうのに反応したんだと捉えることもできるけど、これ、僕が目にしたからこういうポストしたんですね。同じ案件で3回。

MTのアーカイブの柔軟なところは評価されていいんですが、デフォルトだとウェブページのアーカイブマッピングは以下のようになっています。

デフォルトのウェブページのアーカイブマッピング

%-c/%-f

フォルダの指定を忘れて、出力ファイル名を「index」にしてしまうと...

メインページを上書きしてしまうではないですか。

ウェブページのパス指定箇所

マニュアルに書いたでしょ? とか言わずにこういうのはシステムの方でミスを防いでやるのがスマートですよね?

plugins/NoOverwriteMainIndex/config.yaml を置いて(フォルダ名は何でもいいけど)、以下の短いコードをぺたっとするだけです。

プラグインでメインページを上書きしないように設定できる

こういう小さな工夫が使い勝手を向上させ、オペレーションミスによる問題を防いでくれる、ひいては顧客からの質問攻めによる負荷を下げてくれるってもんだと思います。

title要素やHTMLの属性値に出力する箇所

属性値やtitle属性中にはタグが使えないため。

<img src="<mt:AssetURL>" alt="<mt:ImageAlt escape="html">" />

または

<img src="<mt:AssetURL>" alt="<mt:ImageAlt remove_html="1" escape="html">" />

H1要素など、HTMLを含めることが可能な箇所

改行を含めたい等のケースがあるので要件、サイトのルールに応じて。

<h1><mt:PageTitle></h1>

URLを入力するカスタムフィールドの場合

trimを指定するのはフィールドへのコピペで前後に空白が入る可能性があるため。

<mt:ImageLinkURL trim="1" encode_url="1">

外部ユーザーが改ざん可能な値を出力する時

XSSに繋がるため

<mt:Var name="request.foo" escape="html">

Facebook

Twitter

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

最近のコメント

Powered by Movable Type 6.0.3