2014年4月アーカイブ

2014年4月の記事ランキング

4月、1日1トピって決めてたんですが、最終日の今日1記事書いたところで若干力つきたので、最初にランキングとか書いてお茶濁す。

括弧内は、いいね! の数です。PowerCMSのSocialプラグインを使うと、このあたりがテンプレートで出せるのですね。便利です(宣伝)。

さて、この記事の本題はツールでブログ記事は沢山書けるようになるか、ということなのですが、既にMAUSについては何度か書いたので今回は別の切り口を。

本当は、"DynamicMTMLをクラウド版MTで動かす"というエントリにする予定だったけど、今のところ途中までしか確認できていない。ので題名変えた(><。

動きました。追記した。

現在公開されているDynamicMTMLはMTOSをターゲットとしていたのでGPLv2なので、MT6で動かすためには色々調整(大人の事情も含めて)が必要なのですが、MTクラウド版の仕様やPHPまわりのメモ的に書いておきます。

確認したのはS4100iプラン。確認したのはPowerCMS4.0に同梱のDynamicMTML2.0の最新版で、公開しているGPL版とは少し異なります。

クラウド版のMTでは、addonsが設置できないので、plugins以下にアップする

DynamicMTML.packはplugins以下に設置しても動作自体には問題ないようです。 クラウド版MTにFTPSでアクセスすると、以下のようなディレクトリ構成になっていますので、DynamicMTML.pack を plugins以下にアップします。

クラウド版MTにFTPSでアクセス

追加したプラグインを認識させるには、システム全般設定を保存する

プラグインを追加するだけでは、クラウド版MTはプラグインを認識してくれません。(nginx+PSGIなので常駐している)

追加したプラグインを認識させるには、システム全般設定を保存します。以下の画面ですね。何も設定変更は必要ありません。ただ、この画面を開いて「保存」してください。これでプラグインが認識されるようになります。

システム全般設定

クラウド版のMTでは、mt-config.cgi を編集できない

環境変数の管理

システムスコープで、「クラウドサービス」→「MT環境変数」を選択して必要な環境変数をここに追加する必要があります。DynamicMTMLを動かすのに必要な最小限の設定は、PowerCMSFilesDir環境変数です。これがないと、app/movabletype以下に powercms_filesディレクトリを作成しにいって、ここでコケます(パーミッションがないので)。

このMT環境変数画面で設定できるようにするためには、プラグイン側に修正が必要です。DynamicMTML.pack/config.yaml の config_settings: 以下に、PowerCMSFilesDir、updatable: 1を追加します。

config_settings:
    PowerCMSFilesDir:
        updatable: 1

追加した後は、システム全般設定を保存し直してください。

FTPSでアクセスした時に見えているディレクトリは、/data/file 以下のようです。よって、FTPSで /mt-static/support/powercms_files を作成し、 PowerCMSFilesDirには /data/file/mt-static/support/powercms_files を指定します(plugins以下でもいいのですが。本当は、こういう用途向けに任意のディレクトリを作成できるか、それ向けのディレクトリがあるといいと思うのですがね)。

社長ともっと話そう。

| コメント(0) | トラックバック(0)

そうだよなぁ、って思うのですね。社長がそれ言っちゃおしまいだよ、ってのは無しの方向で。

このブログの読者の方には“いわゆる、大企業”に勤めてる方もいらっしゃると思うので、表題の「社長ともっと話そうぜ。」ってのに、みんながみんなに当てはまるわけじゃないんですけどね。

私のサラリーマン時代のあまり嬉しくない経験談

組織がそこそこの規模になって、勤続年数もそこそこ経過すると「ウチの会社って、○○なんだよなぁ」みたいな発言が出てくるわけです。良い点なら別ですけど、大抵あんまり良い話しじゃなかったり。

私のサラリーマン時代(前職時代)は、かなり真面目に働いてましたけど(自分で言うかこら)一つだけ会社の方針ってやつとうまく付き合えなかったことがある。会社が「あなたに、こういうミッションをやって欲しい」として与えられたことと、「今会社のためには、このミッションよりこっちやろ」みたいなことで、そっちに走っちゃったことがあって。言い訳すると直属の上司にはちゃんと報告入れてたじゃん! ってことになるんだけど、結局のところ「あいつは方針に背いて勝ってなことやってる」みたいなことになりまして。

つまり、会社の方針ってやつと自分の行動が合致していなかった。

当の本人にしてみれば、「会社のため思って考えて行動したやないかい」という思いが拭えず、それは結果として退職・独立のきっかけの一つにもなったわけですが。でもさ、社長=会社に言わせれば「会社のためを思ってお前のミッション決めてんじゃないかい」ってことですよね。

つまり、それって両者とも「会社のためを思って」 動いてんですがな

根っこのことろは両方とも会社のため、現状より何かを良くするためってところで一致してるわけです。でも見ているところが違うから相反したりする。自分の反省を告白すれば、自分はより短期的な視点、会社はより中長期的な目線でもってミッションを定めていた(今から考えれば)。

※あんまり具体的に書いてないから、もうちょっと詳しく話せよって方は、是非ビール奢ってください。

以下、BtoB受託Web制作ビジネス〜ソフトウェアパッケージメーカー、カスタマイズについてはBtoBの受託型Web制作。比率5:5くらい、会社設立10年くらいの私の私見です。

BtoB受託Web制作ビジネスでも時間の経過とともに数字は積み上がって行く

BtoB、受託でWeb制作とかをやってると、人月ベースの見積もりの場合「人が動いた分だけ数字があがる」という構造になりがちです。単純に人を増やして規模を大きくし、動かす工数を増やせば期間あたりの売上額、利益額は比例して大きくなるという理屈は普通に理解できるでしょう。

でも、それ以上の伸びは一見難しいように見えます。人が動いた分の金額をもらうビジネスですから、営業コストは別にして受注してから生産が始まる分この種のビジネスはリスクは少ないように見えます。そして、それは事実でしょう。

それでも、数字は伸びて行きます(というか、伸ばすことは可能だと思う)。劇的にってことはないでしょうが。

理由を羅列します。整理は後で。

  • 認知度の向上
  • メソッドの確立(仕事のやりかたの平準化)
  • ノウハウの蓄積・転用
  • 既存顧客の蓄積
  • 実績による受注確度の向上
  • 規模と継続によるリスク吸収力
  • ブランド力の向上、露出の増加
  • プラットフォーム/外部リソース活用力に向上、人脈の広がり
  • 人材求心力の向上
  • 資産(資本)力

さきほど、“営業コストは別にして”と書きましたが、まさにこの営業コストが下がる。認知度や受注確度が上がったり、既存顧客が増えれば営業コストは下がります。極端な話し、既存顧客のリピート仕事であれば取引基本契約書を作る手間だってなくなる。

以下、駄文。

この一年で新しく覚えたこと、やったことをざっと羅列してみる。 必ずしも新しいビジネスに直結してるわけじゃないけど、新しいこと覚えると可能性が広がるし、自分の仕事効率化できるし、トラブル時に自分を助けてくれる。

  • Microsoft Azure のAPI(主にBlobストレージ)
  • Backlog API(プラグイン作成)
  • CPANへのモジュール登録
  • Mac App StoreへのOS Xアプリの登録(法人のデベロッパー登録)
  • GitHubの基本操作
  • Xojo
  • ColorQuest(バージョンアップ)
  • MTDataAPIDebugger
  • AZBlobClient
  • MAUS
  • MT6のDataAPI(APIクライアント作成、プラグインでの拡張)
  • MarkDown記法(慣れた)
  • SalesforceのAPI(プラグインを大きく機能追加した)
  • 電子書籍の作成(ePub)
  • PowerCMSの機能追加(run-workers-daemon、PluginManager.pack、カスタムダッシュボード、フォーム機能の改訂、等々)
  • PHP/Perl、正規表現などのスキルアップ
  • MTのプラグインあれこれいくつか

もちろんまだまだ精進中。新しいことにチャレンジするのは、楽しいよ。

一見、こうしてみると仕事増やしてるように見えるけど、そんなことはないのです。Azure案件増えてきたら、自分で扱える方が有利だし Salesforce 連携も引き合い増えてるし、標準でできるようになってるほうがビジネスがスピーディーに進むわけだし。

ダウンロード販売とか、何がいいだろうかとか思って調べていた時にたまたま見つけたのですけども。19歳で起業してこの発言。大人だなぁと思います。この考え方はしっくりくる。

EC激変の中、『Gumroad』が日本展開を強化。創業者いわく「事業拡大のカギはtelling story」 - エンジニアtype

たいていの起業家が「世界はこうあるべきだ」という信念を持ってビジネスを始めると思うんだけど、いざサービスを開発し、運営するようになると、多くの人が陥るミステイクがある。

それは、「自分たちのサービスが世界を変える」という考え方だ。

本来は、「世の中がこう変わっていくから、こういうサービスが必要になるよね」と考えるべきなんだけど、自ら手掛けるサービスへの愛着から、どうしても物事を考えるスタート地点がずれていってしまうんだよ。

どうも、世界を変えるとか息巻いている人が僕は苦手です。事業を継続して行くために色々と試行錯誤しているわけですけど、世界を変えることより、自分と自分に関わる人たちの生活がより良くなるようにと考えるようにしています。

もちろん、自分たちが変える変えないに関わらず、世界はどんどん変わって行きます。自分たちが提供しているサービスや製品に求められることも変わっていくわけです。

ビジネスだから、最終的に収益につなげられなければ企業は存続できません。新しい製品やサービスをリリースしても、当たらないこともあるし(むしろその方が多い)。でも、世界は変わって行くのだから、必要なものも変わって行きます。

新しい製品やサービスに関する企画を進めています。

アルファサードでは6月で11期末を迎えますが、今年は全社員に宿題を出しています。サービスや製品を改良するための取り組みや製品開発に関するアイデアや具体的な取り組みなど。

動かない(違うブログのが出てくるねん)ってのが来たのですよね。ずいぶん昔に公開したものなんですけど。気が抜けないっす。

おかしいのは動的にAltTemplatePath(代替テンプレートパス)をセットしているところ。

        my $plugin_tpldir = File::Spec->catdir( $plugin->path, 'tmpl', $tpl_directory );
        $app->config( 'AltTemplatePath', $plugin_tpldir );

何でだろう、と小一時間悩んだのですが、Core.pmを眺めていて、おやっと。

        'AltTemplatePath' => {
            default => 'alt-tmpl',
            path    => 1,
            type    => 'ARRAY',
        },

あれー(いや、ARRAY...)

my @paths = $app->config( 'AltTemplatePath' );
use Data::Dumper;
MT->log( Dumper \@paths );

# $VAR1 = [ '/Applications/MAMP/htdocs/app/alt-tmpl', '/Applications/MAMP/htdocs/app/plugins/CMSContext/tmpl/Blog_1' ];

あ。。。

PHPerのためのMT講座

DynamicMTML編その2。データベース接続を簡単に(ORマッパー)。

PHPからMTのデータベースに接続する方法について、これまでに class MTDatabase を使う方法、class BaseObject を使う方法について紹介しましたが、DynamicMTMLを利用することで、SQLを書かずにMTのデータベースオブジェクトにアクセスすることができます。

これは、MTの心臓部とも言えるPerlの MT::Object をPHP化する目的で作られた(というか、作った)もので、完全な互換性はありませんが、プラグインを書く人にとってはスタティックパブリッシング(Perl)、ダイナミックパブリッシング(PHP)の両方で同じ書き方ができるメリットがあります。

また、「SQLを文字列操作(連結とか)で組み立てるな」って話しもあって、そういう面からもこれを使うメリットがあるかと思います。

プラグインから class DynamicMTMLを取得する

方法は2つ。

global $app;

とするか、

$app = $ctx->stash( 'bootstrapper' );

これで、class DynamicMTMLが取得できます。

Facebookでたまたま見かけたのですが。

国内成長率No.1 ! 1600社超の導入実績

喧嘩は好まないのですけどね。でも、疑問だったので聞いてみたのですね。ウォールへのコメントで。

  • No.1の基準は何でしょうか
  • 売上か、導入数か、シェアか、その他の何かでしょうか?

答えはウォールへのコメントではなくで、ダイレクトなFacebookのメッセージでした。回答は私信ですから、そのまま引用はしません。

ひと言で言えば、

調査会社のデータの、取引社数を確認した

そういうことらしい。でもね、そういう事実は書かれていないのですよ。しかも...

冒頭に書いたのですが、喧嘩をするつもりはないのです。ただ、客観的に、書いてみますね。

  • ECバイヤーズさんのサイトでMovable Typeがいくら売り上げようと、シックス・アパートさんの取引先の会社数は増えないですよね。
  • PowerCMSのパートナー様の販売数が増えても、当社の取引先会社数は増えないですね。

つまり、

  • 直販してる
  • 調査会社の「取引会社数」を適宜更新してる

前提での数字を、No.1と謳ってるわけです。ウチ的には調査会社の取引社数とか、まともに出した記憶がなかったですね。盲点でしたよ。びっくりした。

てのは、そういう前提の、というか、前提を作って謳ったわけですね。

ひと言で、それが間違ってるとか、そういうことを謳うことがあかんとか言う気もないですが、且つ、それでウチよりどうこうと言う気も全くないのですが、ひとつ、いや二つ。

  • ネットは何でも有りなのか? 広告の審査もなけりゃ、言ったもの勝ちか?
  • No1.という、うたい文句が欲しければ、調査にお金かけつつ、根拠をちゃんと示すとか。そのくらい必要でしょ。

だから、このうたい文句は「大阪で一番安い自転車屋」と同じレベルのキャッチなわけですね(それが悪いとは言わないです。買う人が分かってる前提なら、そういう洒落ってありかと思う)。それって、ネットの、Webの未熟さの典型なんじゃないですかね。

まとまらない雑文。

情報商材 - Wikipedia

情報商材は、それ自体に金銭的な価値を設定し、売買されるものであるが、特に「ある目的を達成するための方法」を指すことが多い。ただし、単に画像や動画・文章といったコンテンツ(娯楽媒体や学術的な知識などの集積物)を指しては情報商材の範疇では扱われない。

ePubやKindle形式の書籍を売るとか、有料メディアを運営するのと、情報商材の間の差はなんだろう? いや、そもそも差はないのでしょう。

両者の差は何か?

  • 不真面目な情報商材(スパムを介した宣伝と内容の伴わない=購入者の満足を得られない類いのもの)と、
  • そうでないもの(真面目な情報商材)

の間の差。不真面目な情報商材と(私の脳内で)近いイメージのものとして、スパム生成ソフトウェアがある。

ソフトウェアビジネスにも情報商材ビジネスにも罪は全くないと思うのですが、「不真面目な」がついたのものには罪がある(と私は思う)。

では、逆に「真面目な情報商材」とは何か? それでビジネスができるのか。

何でそんなことを考えたかというと、有償のデジタルコンテンツ、ソフトウェアを流通させる下地は揃いつつあるように思うのです。ただ、ビジネスとしてそれがうまく成立しているかというと、まだまだなんじゃないかと

BtoB と BtoC の「note」は成立するか

本題はここです。noteが話題になってることからふと思った。

また、コンテンツ単位での課金、コンテンツ全体での継続課金も可能(継続課金およびムービーへの課金は今後対応)となっているほか、課金する場合はどこまでを無料で「チラ見せ」するかも指定できる。決済手数料および、販売価格から決済手数料(5%)を引いた額の10%がコンテンツの販売手数料となる。決済手段は現在カードのみだが、今後は各種の決済手段に対応する。

「個人のクリエーターが気持ちよく活動できる場所、ここでビジネスをして、ごはんを食べられるようにするという場所にしたい」——ピースオブケイク代表取締役CEOの加藤貞顕氏はこう語る。

要するに、法人向けの note は作れるだろうか、という話し。

ePublisher(ロゴ)

PHPerのための Movable Type 講座がそこそこの量になったので、何となくePubにしてみようと思いました。

ePubはこちらからダウンロードいただけます。

Movable Typeのプラグインがあるので、それを使いました。このブログにプラグインをインストールするのではなく、別の環境(テスト用、デモ用の環境)が既にあったので、それを使いました。セットアップ済みの環境にブログを一つ作成。

所要時間は約30分。CSSがおかしかったのと、カテゴリ分けを間違えたのでその後ちょっと調整したりしましたが、手順としては以下のような感じです。

  • ePub用のテーマを適用してブログを作成
  • カバー画像を作成、メタ情報を入力
  • 該当の記事をコピペ。ePub用のカテゴリに分類
  • URLリンクになっていたのを、内部リンクに変換(手動と検索・置換)
  • 名並び順を調整(ドラッグ&ドロップ)
  • 再構築
  • ePubCheckでエラーがないことを確認
  • パブリッシュ
iPhoneのiBooksの画面1 iPhoneのiBooksの画面2 iPhoneのiBooksの画面3 iPhoneのiBooksの画面4 iPhoneのiBooksの画面5 iPhoneのiBooksの画面6


PHPerのためのMovable Type

DynamicMTML編。

DynamicMTMLは、Movable Typeのaddonで、PHPによるダイナミックパブリッシングを拡張するフレームワークです。

ドキュメントはかなり詳しく書いたつもりなので詳細はそちらを見ていただくとして、簡単に言うと、以下のようなことが実現できます。

  • スタティックなHTMLファイルの中のMTML(MTタグ)をダイナミックに処理できる
  • ダイナミック処理を前提とした様々なMTタグ(ログイン判別、UA判別)
  • コールバックプラグインを作成することができる(処理の割り込み、カスタマイズ)
  • MT::Object ライクなORマッパ
  • class MTPluginによる、プラグイン作成支援
  • PHPによるタスク、キューの実行

等々。DynamicMTMLは単なるスタティックなHTMLファイルの中のMTML実行エンジンではありません。

ブログをまたがったカテゴリ串刺しアーカイブをよりスマートに実現する

前々回紹介した「ブログをまたがったカテゴリ串刺しアーカイブ」ですが、DynamicMTMLを使うことで、よりスマートに実装できるようになります。

  • 前回は、/category.php/movabletype/ のようなURLでしたが、素直に /movabletype/ 等のパスで動作させられるようになります。
  • /movabletype/template/ などのようにサブカテゴリにも対応します。
  • 最上位のブログ(ウェブサイト)にカテゴリが存在する必要があります。
  • プラグインファイル一つに様々な処理を集約できます(複数のテンプレートタグも、コールバックプラグインもひとまとめにできます)
  • テンプレートをヒアドキュメントに書くのではなく、MTのテンプレートとして管理できるようになります。テンプレートとして管理し、パブリッシュしたファイルを出力テンプレートにする、ということです。

プラグインの作成

実は、ここでやろうしていることを少し前にPowerCMSのブログに書きました。以下の記事です。

また、DynamicMTMLの config.php 形式でのプラグイン作成方法は下記の記事で紹介しています。

今回、コードは以下にUPしました。

構造とポイントのみ下記に書きます。

$MT_DIR/
|__ plugins/
   |__ EntriesFromRSS/
      |__ php/
         |_config.php

$registryプロパティに配列でコールバックやタグを登録します。複数のタグや処理も一つのファイルの中にスッキリと書くことができます。

<?php
require_once( 'MTUtil.php' );
class EntriesFromRSS extends MTPlugin {
    var $registry = array(
        'config_settings' => array(
            'RewriteArchiverRewriteTo' => array( 'default' => '/archiver.html' ),
        ),
        'callbacks' => array(
            'post_init' => 'rewrite_archiver',
        ),
        'tags' => array(
            'block' => array(
                'entriesfromrss' => '_hdlr_entriesfromrss',
            ),
        ),
    );
    function rewrite_archiver ( $mt, &$ctx, &$args ) {
        // 初期化後に割り込み処理を入れる
        // URLを見て、カテゴリベースネームに合致したら$ctxをセットし、
        // URLを騙して(/archiver.html)に騙して処理する
    }
    function _hdlr_entriesfromrss  ( $args, $content, &$ctx, &$repeat ) {
        // ブロックタグの処理を書く
    }
}
?>

We Love Blog

古いMTで、コメント欄を問い合わせフォーム代わりに利用しているサイトがあって(最新版に上げろよ!)、急に大量のスパムコメント(メール通知)が増えたのでログをチェックすると、TypePad AntiSpamプラグインがエラーを吐いていた。

TypePad AntiSpam error: Not Found

まぁ、しょうがないんだけどね。でもこれでスパムが大量に増えたってことは有効に機能してたってことだよなぁ、勿体無い。

でね、昔そういやそういう取り組み的なものをしたなぁと思って対策講じてみたのですよ。

でね、間隔の調整とか、CommentScript 変更で再構築したんだけど一向に減らないのです。User-Agentログをみたけどどうにも規則性が見いだせない。 mt-commentsじゃない別のフォームには来ないので、これは何だろうな、単にinputのname属性をチェックして、ブログコメントと見なせば投げてきてるんだろうな、と。

何となく、captchaは入れたくない。JavaScriptに頼ることに。

自分が嫌だし、一発でちゃんとうまくいかないんだもの。アクセシビリティが良くないと思うし。

今日は東京で何かやってんの? ここ大阪なので、関係ないねー(その10)。 今回は、その他のプラグインについて。

条件タグ、ファンクションタグ、グローバルモディファイア

前回までに、init.foo.php と ブロックタグプラグイン(Smarty形式)の紹介をしました(その8、及びその9)。

その他に、ブロックタグ(条件タグ)、ファンクションタグ、グローバルモディファイアプラグインがあります。

その9まで来ましたね。その10まであとちょっと。今回はブロックタグ・プラグインを取り上げたいと思います。(追記)ちなみに、このサンプル、ダイナミック処理ですが、DBアクセスも(初期化以外は)生じないですし結構軽いですよ。

ブロックタグ MTEntriesFromRSS を作る

せっかくなので、ある程度実用的なものを作ってみたいと思います。例えば、以下のようなケースで利用できるものです。

  1. ブログをまたがって、カテゴリーで串刺しした一覧ページを作りたい
  2. ページ送りに対応させたい
  3. Movable Typeで管理していないRSSや別システムから生成したXMLから一覧を生成したい

1番のケースなんて、時々(要件に)出てきませんか?

準備。まずは atom.xml のテンプレートを調整する

今回は1のケース、ブログをまたがってカテゴリ名によって串刺しの一覧ページを作成するようにしてみます。

ブログテンプレート標準の、atom.xml をそのまま使います。実際に利用する時はRSSはRSSで必要でしょうから、コピーして別のテンプレートにするとよいでしょう。

  • MTEntriesの include_blogs モディファイア、または blog_idsモディファイアで、対象のブログをすべて含める
  • MTEntriesのlastnモディファイアを(デフォルトは15とかになっている筈)大きくして、すべてがちゃんと出力されるようにする
  • 調整が終ったら、再構築する

一覧表示のためなら、summary や contentをテンプレートから削除してしまって構いません。必要なのはURL、タイトル、日付、作成者くらいでしょうか。

タグの仕様

xmlにxmlファイルのパス、もしくはURLを、limit、offset、category(カテゴリ名)を指定できるようにします。テンプレートは以下のような感じです。__first__、__last__、__counter__など、MTのお作法的に指定しておいた方が良い変数についても対応するものとします。

<mt:EntriesFromRSS xml="atom.xml" limit="$limit" offset="$offset" category="$category">
<mt:if name="__first__"><ul></mt:if>
<li><a href="<mt:var name="permalink">"><mt:var name="title"></a></li>
<mt:if name="__last__"></ul></mt:if>
</mt:EntriesFromRSS>

例:すべてのブログから、「報道発表」カテゴリに属する記事を一覧で表示する

category.php を作成し、呼び出す際には、以下のようにカテゴリ名を PATH_INFO で渡せるようにします。

/category.php/報道発表/?offset=20&limit=20

Smartyプラグイン形式のブロックタグの作成

ブロックタグの作成については、第5回($ctx編)で取り上げました。

$ctx->add_container_tag( 'entriesfromrss', '_hdlr_ entriesfromrss' );

上記の例は、動的に登録するものです。今回は、MTのプラグインの仕様に従い、Smartyプラグイン形式で作成することにします。以下のようにファイルを配置します。

$MT_DIR/
|__ plugins/
   |__ EntriesFromRSS/
      |__ php/
         |_block.mtentriesfromrss.php

ソースは、ちょっと長いですが、せっかっくなのでごそっと貼ってみます。 第5回で解説しました。以下の点をおさえておいてください。

  1. 引数は、$args, $content, &$ctx, &$repeat
  2. $argsには、タグに渡されるモディファイアがキーバリューの配列で格納されている
  3. $repeat が TRUEの間、繰り返し呼び出される
  4. ループの1回目は、isset( $content ) はFALSEである(なので、これで分岐して初期化等を行う)
  5. $ctx->localize( $localvars ); でテンプレート変数の初期化
  6. $ctx->restore( $localvars ); で、初期化したテンプレート変数をリストア(戻す)

(追記)関数名(function hoge)は smarty_block_MTタグ名 である必要があります。この場合は smarty_block_mtentriesfromrss 。

も一個追記。ファイル名は小文字オンリーである必要があります。これは、MT4からそうなったんですけどね(それまでは大文字混在可能だった)。

ローカル変数の初期化に、MTUtilの common_loop_vars を使っています。

<?php
require_once( 'MTUtil.php' );
function smarty_block_mtentriesfromrss ( $args, $content, &$ctx, &$repeat ) {
    $localvars = common_loop_vars();
    if (! isset( $content ) ) {
        if ( isset( $args[ 'xml' ] ) ) {
            $xml = $args[ 'xml' ];
        } else {
            $xml = 'atom.xml';
        }
        if ( isset( $args[ 'category' ] ) ) {
            $current_cat = $args[ 'category' ];
        }
        if ( isset( $args[ 'limit' ] ) ) {
            $limit = $args[ 'limit' ];
        }
        if ( isset( $args[ 'offset' ] ) ) {
            $offset = $args[ 'offset' ];
        } else {
            $offset = 0;
        }
        $xml = file_get_contents( $xml );
        $xml_obj = simplexml_load_string( $xml );
        $xml_vars = get_object_vars( $xml_obj );
        $entries = $xml_vars[ 'entry' ];
        $entries_from_xml = array();
        $_count = 0;
        foreach ( $entries as $entry ) {
            if ( $current_cat ) {
                $categories = get_object_vars( $entry->category );
                if ( isset( $categories ) ) {
                    foreach ( $categories as $category ) {
                        $cat_label = $category[ 'term' ];
                        if ( $cat_label == $current_cat ) {
                            if ( $offset <= $_count ) {
                                if (! $limit ) {
                                    $entries_from_xml[] = $entry;
                                } else {
                                    if ( count( $entries_from_xml ) < $limit ) {
                                        $entries_from_xml[] = $entry;
                                    } else {
                                        $last = 1;
                                    }
                                }
                            }
                            $_count++;
                            break;
                        }
                    }
                }
                if ( $last ) {
                    break;
                }
            } else {
                if ( $limit || $offset ) {
                    if ( $offset <= $_count ) {
                        if (! $limit ) {
                            $entries_from_xml[] = $entry;
                        } else {
                            if ( count( $entries_from_xml ) < $limit ) {
                                $entries_from_xml[] = $entry;
                            } else {
                                $last = 1;
                            }
                        }
                    }
                    $_count++;
                } else {
                    $entries_from_xml = $entries;
                    $last = 1;
                }
                if ( $last ) {
                    break;
                }
            }
        }
        if (! count( $entries_from_xml ) ) {
            $repeat = FALSE;
            return '';
        }
        $ctx->localize( $localvars );
        $entries = $entries_from_xml;
        $ctx->__stash[ 'vars' ][ '__counter__' ] = 0;
        $ctx->stash( 'entries', $entries );
    } else {
        $entries = $ctx->stash( 'entries' );
    }
    $counter = $ctx->__stash[ 'vars' ][ '__counter__' ];
    if ( $counter < count( $entries ) ) {
        $entry = $entries[ $counter ];
        $counter++;
        $ctx->__stash[ 'vars' ][ '__counter__' ] = $counter;
        $ctx->__stash[ 'vars' ][ '__odd__' ]     = ( $counter % 2 ) == 1;
        $ctx->__stash[ 'vars' ][ '__even__' ]    = ( $counter % 2 ) == 0;
        $ctx->__stash[ 'vars' ][ '__first__' ]   = $counter == 1;
        $ctx->__stash[ 'vars' ][ '__last__' ]    = ( $counter == count( $entries ) );
        $entry_vars = get_object_vars( $entry );
        foreach ( $entry_vars as $entry_key => $entry_var ) {
            if ( is_string( $entry_var ) ) {
                $ctx->__stash[ 'vars' ][ $entry_key ] = $entry_var;
            } else {
                $entry_var = get_object_vars( $entry_var );
                if ( $entry_key == 'link' ) {
                    $ctx->__stash[ 'vars' ][ 'permalink' ] = $entry_var[ '@attributes' ][ 'href' ];
                } elseif ( $entry_key == 'author' ) {
                    $ctx->__stash[ 'vars' ][ 'author_name' ] = $entry_var[ 'name' ];
                }
            }
        }
        $repeat = TRUE;
    } else {
        $ctx->restore( $localvars );
        $repeat = FALSE;
    }
    return $content;
}
?>

コードをもっとシンプルにしたい? であれば、XMLのテンプレの方を修正するというアプローチがとれますね。permalink とか author_name とか、オブジェクトの入れ子になっているところをルート直下に持って行ってやればいいのです。この目的なら何もRSSリーダーで読める必要ないですから。

ようやくプラグインまで来ましたよ。今回はダイナミックパブリッシング対応プラグインの作成について紹介します。

すでにご紹介した通り、MTのダイナミックパブリッシングのうち、テンプレートエンジンは Smartyを継承したクラスになっています。よって、テンプレート・タグを拡張するプラグインは Smartyのプラグインのお作法に従って書くことになっています。

しかしながら、MTのダイナミックパブリッシングのプラグインはテンプレート・タグの拡張だけではありません。

ダイナミックパブリッシング初期化時に実行される init.foo.php

MTのダイナミックパブリッシングプラグインは、mt/plugins/PluginName/php/ 以下に置きます。 init.foo.phpは、ダイナミックパブリッシングの初期化時に require(include)されるファイルです。init.ではじまり、.phpで終るファイル名であれば、名前は何でも構いません。

$MT_DIR/
|__ plugins/
   |__ MyPlugin/
      |__ php/
         |_init.foo.php

通常、MyPluginディレクトリ直下に、config.yamlや、lib/以下にPerlモジュールを配置しますが、ダイナミックパブリッシング専用であれば、それらも不要です。

init.foo.phpでは様々なことができます。例えばこのシリーズの最初の回で紹介した「生」PHPでの処理も、プラグインとして設置したinit.foo.phpの中に記述すれば、ダイナミックパブリッシングの処理(ファイルが存在しない時(DynamicMTMLではファイルが存在する時も)、mtview.phpに処理が渡され、その後require(include)される)の初期化時に処理が通ります。

一般的に、ここで行われる処理については、MultiBlogプラグインが参考になるでしょう。

$MT_DIR/
|__ plugins/
   |__ MyPlugin/
      |__ multiblog.pl
      |__ lib/
      |__ tmpl/
      |__ php/
         |_init.MultiBlog.php

init.MultiBlog.phpで行われている処理は、主にMTのPHPのコアのテンプレートタグ(MTEntriesなど)をマルチブログ対応のタグにオーバーライドする処理です。タグのオーバーライドは、 $ctx->add_tag、$ctx->add_container_tag など、MTViewerクラスのメソッドを使います。このあたりは、「その5」を参考にしてください。

カスタムアーカイブタイプの動的登録

他に行われることとして、カスタムアーカイブの登録、独自カスタムフィールドの登録などが挙げられます。独自アーカイブタイプの登録方法については、以下のプラグインが参考なるでしょう。

このプラグインは、年度別アーカイブをダイナミックパブリッシング対応にするものです。但し、注意点として、カスタムアーカイブをダイナミックパブリッシング対応にするためには、Perlプラグインを合わせて作成する必要があります。

上記のプラグインは、Hirotaka Ogawaさんが作成されたPerl(スタティックパブリッシング用)の年度別アーカイブを、ダイナミックパブリッシングに対応させる用途で作成したものです。Movable Typeでは、再構築処理(Perl-静的側)によって、mt_fileinfoテーブルにレコードが生成されます。ダイナミックパブリッシングでは、この mt_fileinfo レコードをみて、テンプレートのコンテキストを組み立てるようになっていますので、カスタムアーカイブを作成するためには、Perl側のコードも書かなければならないことに注意する必要があります。

カスタムフィールドの動的登録

これも、初期化時に実行される init.foo.php に記述されるのが一般的です。数値型カスタムフィールドを登録する以下のプラグインが参考になると思います。

init.NumericFields.php

<?php
    global $customfield_types;
    $customfield_types[ 'ninteger' ] = array(
        'field_html' => array (
            'default' => 'customfield_html_text',
            'author'  => 'customfield_html_text',
        ),
        'column_def' => 'vinteger_idx',
    );
    $customfield_types[ 'nfloat' ] = array(
        'field_html' => array (
            'default' => 'customfield_html_text',
            'author'  => 'customfield_html_text',
        ),
        'column_def' => 'vfloat_idx',
    );
?>

このコードですが、ダイナミックパブリッシングにおいては殆どの場合これで十分なのですが、この記事で紹介した、BaseObject::install_meta を行っておく必要があります。この処理を忘れると、MTEntriesでのカスタムフィールドでのフィルタリングができなくなりますので注意が必要です。

カスタムフィールドの初期化時の処理については、 addons/Commercial.pack/php/init.CustomFields.php がそのまま参考になるでしょう。

そして、カスタムアーカイブと同じく、PHPコードを用意しただけでは、管理画面にフィールドが現れませんから、Perl側のコードも書いてやる必要があります。そのあたりがMTのプラグイン開発で面倒なところではあるのですが、管理側は常にPerlで拡張する必要があります。

引き続き、class BaseObject編(abstract class BaseObject extends ADOdb_Active_Record)。

BaseObject(mt/php/lib/class.baseobject.php)は abstract class なので、継承してサブクラスを作り、そこから利用します。お作法的には、MT::Fooオブジェクトを作った場合、 class.mt_foo.php という名前でファイルを作り、以下のように定義。

<?php
require_once("class.baseobject.php");    
class Foo extends BaseObject
{
    public $_table = 'mt_foo';
    protected $_prefix = "foo_";
}
?>

カスタムフィールド対応の場合は、ADODB_Active_Record::ClassHasMany を追加します。

<?php
require_once("class.baseobject.php");    
class Foo extends BaseObject
{
    public $_table = 'mt_foo';
    protected $_prefix = "foo_";
}
ADODB_Active_Record::ClassHasMany('Foo', 'mt_foo_meta','foo_meta_foo_id');  
?>

Load

引数には、WHERE分か、数字(ID)を渡す。戻り値は真偽値(true or false)。ロードされたオブジェクトはそのままクラスインスタンスに代入されます。

require_once( 'class.mt_entry.php' );
$entry = new Entry;
$entry->Load( 1 );
var_dump( $entry );

上記の例では、IDを渡しているが、WHERE文を渡すこともできます。 但し、代入されるのは単一のオブジェクトになることに注意。つまり、以下のWHERE文に該当するmt_entryレコードが複数あっても、単一のオブジェクトしか取得できません。

require_once( 'class.mt_entry.php' );
$entry = new Entry;
$entry->Load( 'entry_status=2' );

オブジェクトの各カラムの値は以下のように取得(例:記事のタイトル)。

echo $entry->title;

Find

複数のオブジェクトを取得するには、LoadではなくFindを使います。以下を実行すると、$entriesにはオブジェクトの配列が格納されます。

require_once( 'class.mt_entry.php' );
$entry = new Entry;
$entries = $entry->Find( 'entry_status=2' );
var_dump( $entries );

Findの第4引数に、orderやlimitなどのオプションを配列で渡すことができます。join、distinctなどの指定も可。

require_once( 'class.mt_entry.php' );
$entry = new Entry;
$entries = $entry->Find( 'entry_status=2', FALSE, FALSE, array( 'limit' => 1 ) );
var_dump( $entries );

もうちょっと複雑な例。テーブルのJOIN。

$tag_id = 1;
$where = 'entry_status=2';
$where .= " AND objecttag_object_id = entry_id ";
$where .= " AND objecttag_tag_id = ${tag_id} ";
$extra = array( 'limit' => $lastn, 'offset' => $offset, );
$join = array();
$join[ 'mt_objecttag' ] = array( 'condition' =>
                                            "objecttag_tag_id=${tag_id}" );
$extra[ 'join' ] = $join;
$entry = new Entry;
$entries = $entry->Find( $where, false, false, $extra );

以下のように、$mt->db()->SelectLimitでも記事のリストが得られますが、得られるのはSQL実行結果がオブジェクトではないことに注意してください。

require_once( 'class.mt_entry.php' );
$entries = $mt->db()->SelectLimit( 'select mt_entry.*
                                    from mt_entry where entry_status=2', 10, 0 );
$count = $entries->RecordCount();
for ( $i = 0; $i < $count; $i++ ) {
    $entries->Move( $i );
    $_entry = $entries->FetchRow();
    $entry = new Entry;
    $entry->Load( $_entry[ 'entry_id' ] );
        // idからオブジェクトを取得
    var_dump( $entry );
}

class BaseObjectの話しと離れてしまいますが、そもそもSQL文をそのまま実行する場合は $mt->db()->execute を使います。

$mt->db()->execute( $sql );

まだ続きます。MTUtil.phpについて

さて、MTUtil.php編です。日付関係とかエンコードデコードとか、いわゆるユーティリティ的な関数群。

require_once( 'MTUtil.php' );

日付関係

datetime_to_timestamp

Unixタイムスタンプへの変換。 2014-04-17 10:08:57 → 1397696937

$epoc = datetime_to_timestamp( $blog->blog_children_modified_on );

start_end_ts

年(YYYY)、年月(YYYYMM)を渡すと、それぞれ年、月の最初と最後のタイムスタンプを返す。DBで範囲指定をする時などに利用。

list( $start, $end ) = start_end_ts( 20140416 );
//->20140416000000
//->20140416235959

start_end_month

月の最初と最後のタイムスタンプを返す。以下、結果的にはstart_end_tsと同じ。

list( $start, $end ) = start_end_month( 20140416 );
//->20140416000000
//->20140416235959

days_in

その月が、何日まであるか。月、年の順に引数を渡す。

$d = days_in( 4, 2014 );
//->30

start_end_day

その日の最初と終わり。

list( $start, $end ) = start_end_day( 20140416 );
//->20140416000000
//->20140416235959

start_end_year

その年の最初と終わり。

list( $start, $end ) = start_end_year( 20140416 );

start_end_week

その週の最初と終わり。週別アーカイブとか、みんな使ってます?

list( $start, $end ) = start_end_week( 20140416 );

is_leap_year

閏年かどうか。

$leap_year = is_leap_year( 2014 );
var_dump( $leap_year ); // false

gmtime

グリニッジ標準時間 (GMT)を返す(配列で)。引数にタイムスタンプを渡せる。省略時は現在時刻。

var_dump( gmtime() );
array(9) { [0]=> int(59) [1]=> int(30) [2]=> int(2) [3]=> int(17) [4]=> int(3) [5]=> int(114) [6]=> int(4) [7]=> int(106) [8]=> int(0) }

返り値の構造については→PHP: localtime - Manual

offset_time_list

ブログ/ウェブサイトのゾーン設定を反映した時刻を返す。

$ts = offset_time_list( time(), $blog );
$timestamp = sprintf( "%04d%02d%02d%02d%02d%02d",
    $ts[5]+1900, $ts[4]+1, $ts[3], $ts[2], $ts[1], $ts[0] );
//->20140417113635

wday_from_ts

今日は何曜日?

$wday = wday_from_ts( 2014, 4, 17 );
//->4(木曜日)

yday_from_ts

今日は今年の何日目?

$wday = yday_from_ts( 2014, 4, 17 );
echo $wday;
//->107(日目、おっと、もう100日以上経過してんのか!)

暖かくなりましたね。続きます。

class MTViewer( extends Smarty) 編。$ctxです。そう、コンテキストはSmartyであるのですが、今日はこれ。テンプレートエンジンの謎を探る(謎というわけでもないが)。PHPでのMTタグの作り方です。

公式ドキュメントは以下。

公式ドキュメントではPerl(スタティック)の解説が前半、後半がPHPになっていますが、基本的にはタグの作成方法についてのドキュメントではPHPについても言及されています。

ベースにするのは昨日の以下のコード。当面「生」PHPで行くことにします。そろそろプラグインに行きたいところであるけれども。

この例では、「生」PHPですが、MTのお作法に従ってプラグインとして作る場合も基本は一緒です。$ctxのメソッドで動的にタグが登録しているという違いがあるだけで、基本は同じです。


    <?php
        $blog_id = 1;
        include('/path/to/mt/php/mt.php');
        $mt = MT::get_instance( $blog_id, '/path/to/mt/mt-config.cgi' );
        $ctx =& $mt->context();
        $ctx->caching = FALSE;
        $blog = $mt->db()->fetch_blog( $blog_id );
        $ctx->stash( 'blog', $blog );
        $ctx->stash( 'blog_id', $blog->id );
        $ctx->__stash[ 'vars' ][ 'foo' ] = 'bar';
        $tmpl = <<< EOM
        <pre>
        <mt:var name="foo">
        <mt:BlogURL setvar="blog_url">
        <MTBlogName>
        <MTEntries include_blogs="children">
        <MTEntryTitle>
        </MTEntries>
EOM;
        require_once( 'MTUtil.php' );
        require_once( 'modifier.mteval.php' );
        $contents = smarty_modifier_mteval( $tmpl, TRUE );
        echo $contents;
        echo $ctx->__stash[ 'vars' ][ 'blog_url' ];
    ?>

動的にテンプレートタグを追加する

add_tag

タグ名と、関数名を渡して、ファンクションタグを登録します。以下の例では、mt:Bar タグで、Barを出力します。

$ctx->add_tag( 'bar', 'bar' );

function bar( $args, &$ctx ) {
    return 'Bar';
}

// <mt:bar> => Bar

add_container_tag

タグ名と、関数名を渡して、ブロックタグを登録します。以下の例では、<mt:buz>〜</mt: buz>で、1から5までの数字を<mt:var name="__counter__">タグで出力します。

$ctx->add_container_tag( 'buz', 'buz' );

function buz( $args, $content, &$ctx, &$repeat ) {
    $localvars = array( '__counter__' );
    if ( isset( $args[ 'max' ] ) ) {
        $max = $args[ 'max' ];
    } else {
        $max = 5;
    }
    if (! isset( $content  ) ) {
        $ctx->localize( $localvars );
        $ctx->__stash[ 'vars' ][ '__counter__' ] = 0;
    }
    $counter = $ctx->__stash[ 'vars' ][ '__counter__' ];
    if ( $counter < $max ) {
        $repeat = TRUE;
        $counter++;
        $ctx->__stash[ 'vars' ][ '__counter__' ] = $counter;
        $ctx->stash( 'counter', $counter );
    } else {
        $ctx->restore( $localvars );
        $repeat = FALSE;
    }
    return $content;
}

ブロックタグの作り方は、ファンクションタグと違って少し複雑ですが、以下のように理解してください。

  1. 引数は、$args, $content, &$ctx, &$repeat
  2. $argsには、タグに渡されるモディファイアがキーバリューの配列で格納されている
  3. $repeat が TRUEの間、繰り返し呼び出される
  4. ループの1回目は、isset( $content ) はFALSEである(なので、これで分岐して初期化等を行う)
  5. $ctx->localize( $localvars ); でテンプレート変数の初期化
  6. $ctx->restore( $localvars ); で、初期化したテンプレート変数をリストア(戻す)

5、6ですが、ループの中でループを使っている時なんかで、__counter__の値が親ループの中の値なのか、子ループの中の値なのかが同じ変数名なのでわかんなくなりますよね。変数のスコープを局所化するという理解で良いと思います。

__stash は、クラスのプロパティ、stashはクラスのメソッドです。ややこしい? けど、要はキャッシュです。値をセットするのに使われます。stashメソッドでは第一引数にキャッシュのキー、第二引数にキャッシュの値(オブジェクトや配列、もしくはスカラ何でも)を指定します。第二引数を省略すると、キャッシュされた値を取得します。

$ctx->stash( 'blog', $blog );

__stashはプロパティで、配列(のキーが) varsに指定した値が、mt:var name='"hoge"で取得できます。

$ctx->__stash[ 'vars' ][ '__counter__' ] = $counter;

add_conditional_tag

タグ名と、関数名を渡して、条件タグ(mt:ifなんとか)を登録します。以下の例では、常に真を返す mt:ifqux条件タグを登録しています(常に真、って意味がないんですけど、サンプルとして)。

$ctx->add_conditional_tag( 'ifqux', 'ifqux' );

function ifqux( $args, $content, &$ctx, &$repeat ) {
    return $ctx->_hdlr_if( $args, $content, $ctx, $repeat, TRUE );
}

返り値 $ctx->_hdlr_if( $args, $content, $ctx, $repeat, TRUE ); のように、第5引数に真偽を指定します。

add_global_filter

モディファイア名と、関数名を渡して、グローバルモディファイアを登録します。以下の例ではモディファイアに渡した文字列を連結して出力するcorgeモディファイアを登録しています。

$ctx->add_global_filter( 'corge', 'corge' );

function corge( $text, $arg ) {
    return $text . $arg;
}

// <mt:bar corge="!!"> => Bar!!

PHPerとひらぱーって発音似てる? (違)

前回までで、Class MTDatabase編が終ったので、続いてテンプレートエンジンの部分的な? 利用方法をご紹介します。

ヒアドキュメントの中にMTのテンプレートを書いて、PHPのコード中からビルドする方法。mtevalモディファイアを使うのが簡単なので、ここではmodifier.mteval.phpをrequire_onceして、そのままテンプレートを渡しています。

参考リンク: MTタグとphpで変数の受け渡しをする。 - Junnama Online

<?php
    $blog_id = 1;
    include('/path/to/mt/php/mt.php');
    $mt = MT::get_instance( $blog_id, '/path/to/mt/mt-config.cgi' );
    $ctx =& $mt->context();
    $ctx->caching = FALSE;
    $blog = $mt->db()->fetch_blog( $blog_id );
    $ctx->stash( 'blog', $blog );
    $ctx->stash( 'blog_id', $blog->id );
    $ctx->__stash[ 'vars' ][ 'foo' ] = 'bar';
    $tmpl = <<< EOM
    <pre>
    <mt:var name="foo">
    <mt:BlogURL setvar="blog_url">
    <MTBlogName>
    <MTEntries include_blogs="children">
    <MTEntryTitle>
    </MTEntries>
EOM;
    require_once( 'MTUtil.php' );
    require_once( 'modifier.mteval.php' );
    $contents = smarty_modifier_mteval( $tmpl, TRUE );
    echo $contents;
    echo $ctx->__stash[ 'vars' ][ 'blog_url' ];
?>

実際に生PHPのコードの中にテンプレートを書く必要があるのかといえば疑わしいというか、意味がないように見えますが、.htaccessなんかで、URLリライトしてファイルへのリクエストをこのphpに渡して、ファイル中のMTタグを解釈するようにすれば、簡易DynamicMTMLというか、そういう使い方もできますね。MTタグがかかれたテンプレートがView、php が Controllerです。

以前に書いた、MTCakeや、WordPressプラグインなんかは、要はこういうことでPHPでデータベースアクセスしてコンテキスト($ctx)にセットして、MTタグでそれを取り出すような例ですね。

以下、引用

もちろんベンダーの業態や収益モデルによって状況は異なるので、このことは十把一絡げに論じるべきではない。だが、先行投資してムーブメントに乗ろうと意気込んでサービス事業を立ち上げたのはいいが、期待したほど収入が伸びず、黒字転換できないといったベンダーの失敗例は後を絶たないのが実態だ。クラウドサービスで儲かっているベンダーは一握りに過ぎない。

こうした話をすると、「これからはクラウドだ」と息巻く業界関係者に怪訝そうな顔をされたものだった。2010年前後は特にそうであった。しかし、徐々に市場マインドは変わってきたように思われる。実際にベンダーの中には、周辺サービスに軸足を移したり、競争力のある外部プロバイダーと提携する戦略に切り替えたりする企業も出てきた。「クラウドは儲からない」は、いまや定説といっていいだろう。

/引用ここまで。

記事を読んでふと思ったことです。でも、以下の内容はフィクションです(とか)。

請負の業務用アプリなんかの世界を想定しています。ゲームとか個人向けのサービスなんかはちょっと構造が違うでしょう。

ITの世界

顧客

ソフトウェア(サービス)

プラットフォーム

ハードウェア

IaaSとかPaaSとかSaaSとかで、上記のどこまでを誰が担うかってのは違う訳ですが、このうちクラウド化によってハードウェアベンダーやハードウエアベンダーに近いSIerさんから「ハードウェア」部分の売上がなくなります(もしくはごく小額のマージン商売になります)。

PaaSなんかになると、さらにプラットフォーム部分がクラウドサービスて提供者に委ねられます。

そうなると残るのはソフトウェア(サービス)の部分だけ、ということになります。

広告(主に出版や印刷物)の世界

ちょっと似ているところがあるな、とふと思ったのが広告・出版の世界です。以下のような構図の中で、「印刷」「流通」がなくなったと考えてみてください。

顧客

企画・デザイン・コンテンツ制作

印刷

流通

広告代理店や企画制作会社、編集会社などが印刷や流通などをマルッと請け負う構造がこれまでのITのハードウェア一貫型のビジネスと似ているような気がします。

通常、上流の広告代理店がこれらを一貫して受ける見積もりにおいては、印刷以下の工程において相応のマージンを載せるわけです。

そのかわりに、小さなデザイン会社が印刷物込みで受注したものを納品後に顧客が倒産してデザイン会社の女の子(女の子というか、多分社長なんでしょう)が債権者集会で「ふざけるな、どこ逃げた、許されるのか!」みたいに取り乱している現場に遭遇したことがあります。印刷は当然外注でしょうから、自分たちの仕事以上のお金を回収できなくなったのですから気持ちはわからないでもないですが、そもそもそういう部分に利益を載せてワンストップで受けているのだからそういうこともあり得ます。

さて、上記の未回収の例は本題からちょっとずれます。この、広告出版の世界において「印刷」「流通」がなくなったとします。電子出版やWeb化など、そういう流れも起こっているでしょう。

ITの世界における「ソフトウェア(サービス)」は下の広告の例では「企画・デザイン・コンテンツ制作」にあたります。ソフトウェア(サービス)には別途「保守・運用」という仕事があるので少し性質は違いますが、「案件」という単位でみれば概ね同じようなことがおこっていると考えられなくもないでしょう。

今まで、制作からの一貫受注において下流工程のマージンを込みで利益を上げていた時点から考えると(前述のような未回収リスクがなくなることは別にして)、同じ仕事で利益が上げられなくなります。私が大阪人であることから付け加えると、「企画」などのクリエイティブな部分にはお金を払わない、印刷物などの物理的な「モノ」に対してはしょうがないけど、みたいな広告主の元では、見積もり上は上流の金額を低く見せ、下流工程のマージンで帳尻をあわせるようなことも行われていたのではないでしょうか(私自身が今の会社を始める前のサラリーマン時代にみていた世界で起こっていたことでもあります)。

私は、マスや電波系の広告営業は未経験なのでそっちの世界については明るくないですが、メディアがWeb化することによって、これと似たようなことはあちこちで起こっているのだと思います。クラウド化やWeb化によって、世界は(これらの図式の中では)下流部分を担う企業が牛耳ろうとしているのです。

ここまできたら、後少しなので、とっとと書き切ってしまいたいじゃん!

続き(class MTDatabase 完結編)。

escape

文字列をエスケープします。MTのダイナミックパブリッシングでは、スタティックパブリッシング(Perl)のMT::Objectにあたるものがないので、ADODBを利用してSQLを実行するケースが多くあります(DynamicMTMLには(完全ではないですが)MT::Object互換のメソッドがあります)。

WHERE文に渡す文字列などはこのようにエスケープを行います。ユーザーが入力した文字列で処理するようなケースは稀だと思いますが、テンプレートタグから渡されるモディファイアの値などが主に対象になります。

$str = $mt->db()->escape( $str );

fetch_placements

カテゴリIDのリストを渡して、Placementレコードの配列を得ます。Placement(mt_placement)とは、記事とカテゴリ、ページとフォルダの関連づけを保存するテーブルです。id、entry_id、category_id、blog_id、is_praimary(主カテゴリかどうか)のみで構成されるシンプルな構造になっています。

$cat_ids = array( 1, 2, 3 );
$args = array( 'category_id' => $cat_ids );
fetch_placements( $args );

fetch_objecttags

Objecttag(mt_objecttag)レコードの配列を返します。Objecttagとは、記事、ページやアイテム等と「タグ」の関連づけを保存するテーブルです。

$tag_list = array( 1, 2 );
$ot = $mt->db()->fetch_objecttags( array(
        'tag_id' => $tag_list,
        'datasource' => 'entry',
        'blog_id' => $blog_id));

fetch_comments

Comment(コメント=mt_comment)オブジェクトの配列を返します。 $argsには、以下の例のように記事IDを渡すほか、MTCommentsテンプレートタグで利用できるモディファイア(limit、offset、sort_orderなど)が指定可能です。

$args = array( 'entry_id' => $entry_id );
$comments = $mt->db()->fetch_comments( $args );

fetch_comment_replies

コメントIDを指定して、そのコメントへの返信(Reply)コメントの配列を返します。$argsには sort_order が指定できますが、ソートキーは指定できません(日付順となります)。

$args = array( 'comment_id' => 1 );
$comments = $mt->db()->fetch_comment_replies( $args );

cache_categories

記事IDのリストを渡して、カテゴリ情報をキャッシュします。 他のキャッシュ系のメソッドでもそうですが(cache_permalinks等)、キャッシュが有効なのはリクエストが終了するまでの間です(Memcachedのようにリクエストをまたがったキャッシュは保持されない)。 MTのテンプレートによって、記事リストが複数の箇所に配置されていたりすると、都度SQLを発行することになるため、一度表示に使ったらキャッシュされる、というようになっています。

$mt->db()->cache_categories( array( 1, 2, 3 ) );

あーしんど(爆)

※続き書いた。

その2。

fetch_author

IDを指定するとユーザー(Authorオブジェクト)を返します。

$author = $mt->db()->fetch_author( 1 );

fetch_author_by_name

IDではなく、ユーザー名(ログイン名)を指定してユーザーをロードします。

$author = $mt->db()->fetch_author_by_name( 'junnama' );

fetch_authors

ユーザー(Authorオブジェクト)の配列を返します。 以下の例は、ブログ(ID1)に記事投稿のあるユーザーの配列を返します。その他$argsに指定できるのは、MTAuthorsテンプレートタグに指定できるモディファイアと同じと考えて良いです。

$args = array( 'blog_id' => $blog_id, 'need_entry' => 1 );
$authors = $mt->db()->fetch_authors( $args );

MTはもうVersion6だよねDataAPIだよねJavaScriptだよねJSONだよね、な皆さんこんにちは。

何言ってんだよPHPだろ時代はMTで作られてるサイトのカスタマイズ依頼きたんだけどPHPだよPHPでやらなきゃならんのだよこっちは何がDataAPIだよ、というあなたに送る記事第一段です。その1と書きましたが、続かないかもしれません。いっぱい「いいね!」がついたら続くかも!

今回はDynamicMTMLやプラグインではなく、「生」PHPからMTをいじる前提で、class MTDatabase(/mt/php/lib/mtdb.base.php)のメソッドについて紹介したいと思います。

初期化

<?php
$blog_id = 1;
include('/path/to/mt/php/mt.php');
$mt = MT::get_instance( $blog_id, '/path/to/mt/mt-config.cgi' );
$ctx =& $mt->context();
$ctx->caching = FALSE;
$blog = $mt->db()->fetch_blog( $blog_id );
$ctx->stash( 'blog', $blog );
$ctx->stash( 'blog_id', $blog->id );

まずは初期化から。ウェブサーバーに test.php とかを置き、最後に echo $blog->name とかしてブログ(ウェブサイト)名が返ってきたらOK、成功です。

class MTDatabase はここでは、$mt->db()です。class MTDatabase のメソッドは、$mt->db()->foo とすることで呼び出せます。以下、だいたいソース上の上から順番に紹介します。「だいたい」ですので。

メソッド

メソッドによってパラメタや戻り値は様々ですが、基本的に以下の認識で間違いありません。

  • 単数が前提のもの、IDを渡してオブジェクトを得るもの(fetch_entryなど)の戻り値は単一のオブジェクト
  • 複数の結果が返ることがあるもの(fetch_entriesなど)の戻り値はオブジェクトの配列
  • その他は文字列、数字、配列等

fetch_blog(fetch_website)

すでに初期化のところで出てきました。ブログIDを渡すと、Blogオブジェクトが返ります。fetch_websiteはウェブサイト版です。ちなみに、fetch_blogにウェブサイトのIDを渡しても返ってきますが、ウェブサイトではfetch_websiteを使った方が良いでしょう(記事とページ、カテゴリやフォルダでもそうですがclass Website extends Blog、なのでカスタムフィールドとかそのあたりが異なる可能性があるので)。

parse_blog_ids

ブログIDの配列を返します。第二引数はウェブサイトを含めるかどうか。デフォルトはFALSE(含めない)です。

$blog_ids = $mt->db()->parse_blog_ids( 'children', TRUE );
var_dump( $blog_ids );
// => array(2) { [0]=> string(1) "2" [1]=> string(1) "1" }

include_exclude_blogs

これ、昔はpublic functionじゃなかった気がする(何でやねんって言った記憶がある)けど、public functionになってます。MTのPHPはSQLのWHERE文を組み立てることがよくあるんですが、このメソッドはオブジェクトのblog_id に指定する文字列を返します。

$args = array( 'include_with_website' => 1, 'include_blogs' => 'children' );
$include_exclude_blogs = $mt->db()->include_exclude_blogs( $args );
echo $include_exclude_blogs;
// => in (2,1 )

fetch_entry(fetch_page)

$entry_id = 1;
$entry = $mt->db()->fetch_entry( $entry_id );

記事(ウェブページ)のIDを渡すと、記事(ウェブページ)オブジェクトを返します。

db2ts(ts2db)

YYYYMMDDhhmmss形式と、データベースが扱うタイムスタンプ文字列(YYYY-MM-DD hh:mm:ss 等)を相互に変換します。

$entry_id = 1;
$entry = $mt->db()->fetch_entry( $entry_id );
$authored_on = $entry->authored_on;
echo $authored_on;
// => 2014-04-08 12:46:24
$authored_on = $mt->db()->db2ts( $authored_on );
echo $authored_on;
// => 20140408124624
$authored_on = $mt->db()->ts2db( $authored_on );
echo $authored_on;
// => 2014-04-08 12:46:24

resolve_url

リクエストのパスとブログIDを渡すと、該当するFileinfoレコード(mt_fileinfo)を返します。

$blog_id = 1;
$fi = $mt->db()->resolve_url( '/path/to/foo.html', $blog_id, 3 );

3つめの引数(デフォルトは3)は、テンプレートのbuild_typeで、3はダイナミックパブリッシングです。DynamicMTML等ではbuild_type が1(スタティック)なものを使うことがありますが、スタティックなファイルがPHPから見えてはまずいことがあるので(昔、そういう脆弱性が存在したとかそういう経緯でしょうw)、3がデフォルトになってます。

ちょっと前に話題になってた記事。

で、たまたまさっき、このエントリを読んだのです。

そうです。いつも思うのです。やれなかったのは時間がなかったからではなく、優先度が低かったのです。自分の中で。本当はやらなくても構わないようなことなんですね、結局できなかったことってのは。

よくあるのは主に年一回の全体Meetingとかの時に思うケースですね。だいたいが年一回の会議の時に、「今年はこれこれを目標に掲げたができなかった。次年度こそは、これに取り組む。」とか言うやつは大抵できません、これ。

ありがちなのは以下のようなもの。

  • 受託のWeb制作してる会社で、自社サイトのリニューアル
  • 社内向け勉強会、情報共有のためのミーティング
  • スキルアップ、Git使えるようにする、JavaScriptマスタする

まぁ、これは僕の業界だからこその例で、違う業界なら違う「ありがちな」何かがあるのよきっと。で、こういうことが「時間がなかったからできなかった」てのが2年も続いたら方法は2つしかない。2度あることは3度ある。3年目もきっとできないです。

  • それ、そもそも優先度低いんだから、もうやらない
  • あるいは、やりたいこと1つに対して、やらないことを1つ決める

いいですか、やらないこと決めるのです。捨てること。例えば、断る仕事を決めるとかね。何となく、この自分のやりたいことを阻害している、「優先度が高いっぽく見える何か」を捨てる、やらない。これを決めることが大事だと思うのですよ。

以下、あんまり読まなくていいです、言いたいことは表題のみ。

ちらほらと、古いまま放置されてるMTでhogeされたとか見聞きするようになってきたので。最新版に上げてね、ってのがメーカとしての見解になるのはしょうがないんですけど、上がられないよ、とか、上げられるけど申請するのに時間かかるんだよとか、色々あるだろうから。

インストールが終了したら削除するファイルはありますか | Movable Type FAQ

以下の CGI ファイルについては、Movable Type のインストール作業の終了後、実行することはありません。

セキュリティ上必要な場合には、パーミッションを変更して外部からの実行を行えないようにするか、あるいはウェブサーバーから削除してください。

  • mt-check.cgi
  • mt-wizard.cgi
  • mt-testbg.cgi
  • mt-upgrade.cgi

付け加えるなら、使わない mt-foo.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

不要なプラグイン、サポートされてないものも今一度見直した方がいいかもしれません。

先週(4月3日)のことですが、PowerCMS設計・構築相談会を開催しました。直前に告知開始したのと、当日のキャンセルがあったりして参加いただいたのは5名の方でしたが、開催してよかったと思える場でした。

相談会やりました。

今回は、できるだけ具体的に、深い内容にしたかったため、事前に質問内容や困っていることなどを送ってもらいました。で、その内容は他のPowerCMSユーザー、構築者の方もおそらく同じようなケースがあるんじゃないかと思えるような内容だったので、その場で回答するだけではなくPowerCMSブログに記事を書いて公開したり、次期バージョンアップの際に対応できるかな? ということでトライしてみたり。

以下の記事は相談会前にいただいた質問にお答えする形で書いたものです。

その他に出た質問について、

ContactFormの仕様について

確認画面・エラー画面では、エラーの箇所以外は入力データの編集ができない。(編集できるのが理想的) 確認画面・エラー画面には「戻る」ボタンがない。 →確認時に修正したい箇所があった場合、ブラウザの戻るボタンを押す必要がある。

こちらについては、現状ではできないため、次期バージョンでの対応を(緩く、ですが)お約束して、既に私の方で対応版を作成して現在社内レビュー中となっています。また、

自動メール送信で、fromをフォームごとに設定したい

あわせてこの件についても対応中です。次期バージョンではできるようになっているかと思います。

カスタムオブジェクトなどの(カスタムフィールドを用いた)関連づけ機能で、ブログ/ウェブサイトをまたがって関連づけしたい

これは、環境変数(mt-config.cgi)の設定でできます。

記事の関連づけなら、EntryFieldScope。デフォルトは blog になっていますが、 website、systemが指定可能で、この指定をすることでダイアログに表示されるリストをブログまたがりにすることができます。

PowerCMSブログに記事を書きました。

PowerCMSのサポートに来るメール、チケットを眺めていると、構築後半やローンチ(運用開始)直前になって要件が追加になったり変更になったことによる実現可否/実現方法のお問い合わせがよくあります。私たちも、この「仕様変更」「追加要件」に遭遇しますけど、「仕様変更」「追加要件」がなく最後まで進められる受託・構築案件ってそんなに多くないのではないでしょうか?

それならば、我々のとるべき手は一つ。「事前に手を打っておくこと」ですよね。

事前に手を打つと聞いて考えられることって何でしょう?

  • 見積もり・作業範囲を明確にし、ドキュメントにする
  • 追加要件は別途お見積りになることを明確にしておく、あるいはオーダーの際に営業担当がきちんと説明する
  • 要件が固まるまで実装に着手しない
  • 要件追加を見越した見積もりを出しておく

なんてことが考えられると思います。でも、でも、ですよ。

そんなことはわかっている!

分かってんですよ。そんなことは。それでもなくならないのが「仕様変更」「追加要件」と「増税」と「エイプリールフール」ではないですか? ないですか? ないですかっっ!!

桜、満開ですね。 1日1トピって決めたのです。

1日1トピ(ただし平日に限るが)の対象はブログ、会社サイトの新着情報、PowerCMSブログ、セミナー/勉強会の登壇数などの合計。

第一四半期(会社的には第三四半期)の振り返り

PowerCMSブログについては、昨年末あたりから自分よりむしろ社員に書いてもらうことを進めているので、自分が書いたもの発信したものとは限りませんが。

平日数をカウントしたら58。51/58=87.9%。ちょっと足りてませんね。トピックとしては、Mac/Windows用デスクトップアプリの公開を3つ、うち2つをMac App Storeで公開というのがあります。

Facebook

Twitter

このアーカイブについて

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

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

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

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

Powered by Movable Type 6.2.6