伊藤清徳の垂直落下式ムーンサルトプレス

PerlとかPHPとかMySQLとか...がんばっても8割だ。

Author: admin (page 1 of 27)

WordPressでタイトルタグをテンプレートファイル側から指定

今日はかりゆしウェアを着ている伊藤です。どうもどうも
2018年日本は災害レベルで暑いですね。沖縄県はすずしいですよ。

さて最近連日WordPressの話題を書いています。
今日はWPのタイトルタグをテンプレート側から指定できるといいよね~ってはなしをします。

おともだちのKさんの記事を参考にして書いていきます。

前提条件

  • 包括的に全部を書き換えるのではなく、必要なタイミングでテンプレートごとに指定したい
  • テンプレートファイルからは、get_header();の直前に関数で指定したい

たぶんあるあるだと思います。SEO考えるとね。結構いじりたいところですからね。

document_title_partsフィルターを利用する

Kさんの記事によると、document_title_partsに紐づけした関数に、引数としてタイトル構造を持つ配列が渡されて、それを編集してreturnすればいいぜ。ワイルドだろー?って書いてあるます。なるほど。しかし僕のやろうとしていることだと、必要なタイミングで呼びたいという前提と、テンプレート側から指定したという条件が入るので、そのまんまは使えません。
そこで登場するのがクロージャーです。

クロージャーを使って実装

もうめんどいからソースを晒します。functions.phpにでも書いてください。

/**
 * タイトル変更
 * @param string $title
 * @param string $tagLine
 * @param string $siteTitle
 */
function setTitle( $title, $tagLine = null, $sitTitlte = null ){
    add_filter(
        'doment_title_parts',
        function( $return ) use ( $title, $tagLine, $sitTitlte ) {
            $return['title'] = $title;
            if(!is_null($tagLine)){
                $return['tagline'] = $tagLine;
            }
            if(!is_null($sitTitlte)){
                $return['site'] = $sitTitlte;
            }
            return $return;
        }
    );
}

こんかんじ。で、テンプレからは

setTitle( 'たいとるだよーん' );
get_header();

もう少しちゃんとするならクラスにして静的メッドにするのが筋でしょうねー。
クロージャーをつかっています。あんまりWPでは使われないかとおもいます。

意外に使うTipsになるのでは?
現場からは以上です。

WordPressにORMを組み込む

どうもどうも。2年近く連絡をしていなかったら育ての親である叔父が心配してきた伊藤です。

さて、WordPressにはデータベースを操作する機能というのは一応揃っています。
wpdbとかそのへんですね。ただ、基本的にはSQLを書くか、あるいは、WPのもともとのテーブル構造に対するクエリを前提としていることが多く、遠回りだったりとかしますわね。できればActiveRecordやORMがはいっているといいなーと思うわけです。

特に、従前のシステムがあって、WPでリニューアルをかけたい。というときは、従前のシステムのテーブルはそのままにリニューアルをしたいということは、多々あるわけです。

まさにそんな事を考えていたので、いっちょWordPressにORMを入れてみた。というはなしをします。

PHPのActiveRecord ORMライブラリ

PHPのActiveRecordやORMの単体ライブラリというのが、実はそもそもあまりないです。最近のPHPのフルスタックWebアプリケーションの場合、Doctrineがベースに利用されていたりとかするわけで、車輪の再開発を嫌うのか、あまりないのです。

僕の場合は、PHP5.3系の頃は、php-activerecordをよく使っていましたが、ちょっと古くなったのと、リレーションのリレーションが追えないなど、ちょっと足らないところもあります。

今回探していていい感じだなーとおもったのが、RedBeanPHPです。MySQL/MariaDBのプラグインとPHPライブラリを提供しており、かなり高速で、わかりやすいコード体系のORMになっています。しかし、MySQLのプラグインということで、共有ホスティングなどでは可搬性がさがりますので、今回は見送りました。

今回採用することにしたのがSpotORMです。Doctrine DBALをベースに作られたORMラッパーです。Relationの定義などがしやすく、僕がよく使うフレームワークYiiのModelの構造に似ているのも、採用の理由です。

ではインストール

composerでインストールします。

{
    "require": {
        "vlucas/spot2": "~2.0"
    }
}

とかですかね。composerの使い方はぐぐれ。

WPの場合は、functions.phpにコードを書く感じになるので、テンプレートディレクトリで実行すればよいかと思います。で、functions.phpの冒頭くらいに

require __DIR__.'/vendor/autoload.php';

とでも書いておけば良し。インストールが簡単です。

つかってみましょう

まず接続設定をします。

$cfg = new \Spot\Config();
$cfg->addConnection( 'mysql', 'mysql://ユーザー:パスワード@ホスト名/DB名' );
$spot = new \Spot\Locator($cfg);

DSNで書くとこんなかんじ。連想配列での指定ももちろん可能です。
設定はWPの接続情報を使い回すとかでもいいでしょう。

モデルを用意する

ORMなのでモデルクラスを用意します。
僕の場合は、functions.phpの並びに「Entity」フォルダを用意して、その中にモデルクラスを配置しました。オートローダーをとかを作るほどでもないので functions.phpに

$files =  glob( __DIR__ .'/../Entity/*.php') ;
if(!is_array($files)){ $files = array(); }
foreach( $files as $one ){
    require_once $one;
}

を追記します。
Entityの中身を一気にロードしちゃっています。もちろん必要時に読むというやり方のが正しいとは思います。

Modelファイルの中身は

 ['type' => 'integer', 'primary' => true, 'autoincrement' => true],
        ];
    }
}

仮にこんなかんじです。fieldsになにかないと怒られることがある?っぽいので、primaryのIDだけ定義しておきました。

呼び出しに使ってみる

最後に実際にDBにクエリしてみましょう。
呼び出しをするfuncitonの中で

function hoge(){
    global $spot;
    $postMapper = $spot->mapper('Entity\Products');
    $row = $postMapper->where([
        'item_id' => '品番'
    ])->first();
    return $row;
}

こんな感じにかきます。上記例では商品DBに品番を問い合わせる。的な事をしています。

実際組み込んでいるがWPと融合しているわけではない

ここまで読むとPHPerならおわかりになるとおもいますが、WordPressのオブジェクトの他に、DBに対して接続を一つたてることになります。したがって、メモリ効率などは悪くなります。手軽さと、パフォーマンスどちらを優先するかなどで、選択することになるとおもいます。
しかし、DB操作をして、サイトデザインをWPに合わせるなどを考えると、有用ですし、SQLを書くよりはセキュリティ面でも安心してかけますね。

現場からは以上です。

超簡単なテンプレートエンジン(条件分岐なし)ならCodeigniterからパクれ

どうもどうも。今年はそこそこブログを書いています。体調が戻ってきました。よかったよかった。決して万全ではないのですが、1年近く体調がわるかったので、まぁそのいろいろアレです。

ところで、PHPerのみなさん。
たとえば、管理画面でメールテンプレートを管理するような場合、
そのテンプレートをどう処理するか、結構悩みますよね。

同じようなかんじで、WordPressなんかで、囲みタグ型のショートコードでループをさせたいときとかもかなり悩みます。

よくある例だと、

  1. 管理画面からテンプレート編集
  2. POSTされたデータをDBに保存しつつ、テンプレートエンジン用に、ファイルにも保存
  3. 出力時に保存されたファイルをテンプレートエンジンで処理して出力

というような手順でしょうか。
まぁ悪くはないですが、セキュリティに気をつけなければいけないとか、結構手間取りますし、ファイル出力だと手軽さがなくなります。

今回はWordPressのショートコードでどうしようかなーと思ったときの対処をメモ代わりに。

やりたいこと

前提条件です。

  • WordPressの囲みタグ型ショートコードをテンプレートとして扱いたい
  • テンプレートエンジンとして動かしたいが、PHPを書くのや、Smartyのようにファイル処理を前提としない
  • 条件分岐はとりあえずいらないがループはある

という前提条件で、具体的には

[get_product_categories category="カテゴリ名"]
<ul>
    {categories}
    <li>
        <a href="{category_url}">
           {category_name}
        </a>
    </li>
{/categories} 
</ul>
[/get_product_categories]

こんなショートコードを書いてもらってテンプレート処理をしたいわけです。
囲みタグ型のショートコードについてはみなさまが書いているので、そちらをご参考に

まぁ普通にやれば

このような条件を満たそうと思うと、難しいコードでもないので、自分で簡単なテンプレートエンジンを書いてもいいとは思うのですが、いまいち気乗りがしません。自分で書くとすれば、正規表現を書いて、ゴニョゴニョとするところではありますよね。

そこで!Codeigniterの簡易パーサークラスをぱくってきます

Codeigniterには条件分岐はないですがループに対応して、ファイル処理なしで実行できる簡易テンプレートンジンがついています。これが使えないかと思ってソースを見たところ、Codeigniter本体にはほとんど依存していないことがわかり、少しの書き換えで対応ができることがわかりました。

四の五の言わずにコードを晒せ

はい。

<?php
class CI_Parser {

    /**
     * Left delimiter character for pseudo vars
     *
     * @var string
     */
    public $l_delim = '{';

    /**
     * Right delimiter character for pseudo vars
     *
     * @var string
     */
    public $r_delim = '}';


    // --------------------------------------------------------------------

    /**
     * Class constructor
     *
     * @return void
     */
    public function __construct(){
    }

    // --------------------------------------------------------------------

    /**
     * Parse a template
     *
     * @param  string
     * @param  array
     * @param  bool
     * @return string
     */
    public function parse( $template, $data, $return = FALSE ){

        return $this->_parse($template, $data, $return);
    }

    // --------------------------------------------------------------------

    /**
     * Parse a template
     *
     * Parses pseudo-variables contained in the specified template,
     * replacing them with the data in the second param
     *
     * @param  string
     * @param  array
     * @param  bool
     * @return string
     */
    protected function _parse( $template, $data )
    {
        if ($template === '')
        {
            return FALSE;
        }

        $replace = array();
        foreach ($data as $key => $val)
        {
            $replace = array_merge(
                $replace,
                is_array($val)
                    ? $this->_parse_pair($key, $val, $template)
                    : $this->_parse_single($key, (string) $val, $template)
            );
        }

        unset($data);
        $template = strtr($template, $replace);

        return $template;
    }

    // --------------------------------------------------------------------

    /**
     * Set the left/right variable delimiters
     *
     * @param  string
     * @param  string
     * @return void
     */
    public function set_delimiters($l = '{', $r = '}')
    {
        $this->l_delim = $l;
        $this->r_delim = $r;
    }

    // --------------------------------------------------------------------

    /**
     * Parse a single key/value
     *
     * @param  string
     * @param  string
     * @param  string
     * @return string
     */
    protected function _parse_single($key, $val, $string)
    {
        return array($this->l_delim.$key.$this->r_delim => (string) $val);
    }

    // --------------------------------------------------------------------

    /**
     * Parse a tag pair
     *
     * Parses tag pairs: {some_tag} string... {/some_tag}
     *
     * @param  string
     * @param  array
     * @param  string
     * @return string
     */
    protected function _parse_pair($variable, $data, $string)
    {
        $replace = array();
        preg_match_all(
            '#'.preg_quote($this->l_delim.$variable.$this->r_delim).'(.+?)'.preg_quote($this->l_delim.'/'.$variable.$this->r_delim).'#s',
            $string,
            $matches,
            PREG_SET_ORDER
        );

        foreach ($matches as $match)
        {
            $str = '';
            foreach ($data as $row)
            {
                $temp = array();
                foreach ($row as $key => $val)
                {
                    if (is_array($val))
                    {
                        $pair = $this->_parse_pair($key, $val, $match[1]);
                        if ( ! empty($pair))
                        {
                            $temp = array_merge($temp, $pair);
                        }

                        continue;
                    }

                    $temp[$this->l_delim.$key.$this->r_delim] = $val;
                }

                $str .= strtr($match[1], $temp);
            }

            $replace[$match[0]] = $str;
        }

        return $replace;
    }

}

Codeigniterのコントローラーオブジェクトに依存しているところなどを削って純粋にclassとして動くようになっています。

じゃWPで動かしてみよう

functions.phpあたりで、上記ファイルをrequireして

add_shortcode( 'get_product_categories', 'get_product_categories');

functionget_product_categories( $atts, $content = null ){

    //このへんで適当に引数処理してね    
        
    $parser = new CI_Parser();
    $args = array( ここに適当に条件かいてね );
    $the_query = new WP_Query($args);
    if (!$the_query->have_posts()) {
        return '';
    }
    
    $data = array();
    $data['categories'] = array();//ショートコードのループタグと名前を合わせる
    while ( $the_query->have_posts() ) {
      $the_query->the_post();
      $set = array(
        'category_name' => get_the_title(),
        'category_url' => get_the_permalink(),
      );
      $data['categories'][] = $set;
    }
    wp_reset_postdata();
    
    
    $content = $parser->parse( $content, $data );//最後にパースする。
    return $content;
}

とでもしておけば、簡単ね。

多分改造でifくらいはいける

上記の通りCodeigniterのパーサーはシンプルにできてます。
たぶん改造すればifくらいは余裕でかけると思います。
が、他方、毎回実行時にパーサーが走るわけなので、決して軽くはありません。
使い所見極めていきましょう。

なお、Codeigniter3.xはMITライセンスなのでOKですが、2.xまでのコードを使う場合は独自ライセンスなので、扱いに気をつけてください。

現場からは以上です。

楽天PayオーダーAPIをPHPからコールしてみた

どうもこんにちは伊藤です。きよっちです。半年ぶりの更新となりました。真面目に書けよ!と思われるかもしれませんが、まったくそのとおりですね。ちょっといろいろ思うところがございまして、沈んでましたが、それはまたおいおい。

はい。ところで。楽天さんの動きが激しいですね。楽天Payへの移行などなど。やらなくてはいけないことがたくさん。ちょうど今年2018年はヤマト運輸さんの配送料金大改革などなどもありましたし、Instagram上で通販が可能になったりしましたし、環境は大きく揺れ動いています。日本のECがはじまって以来、最も大きな変革期に来ているのかもしれません。ECを主戦場にしている業者さんは、自社の立ち位置、目指す場所、戦い方(戦わないという選択肢も含めて)などを明確にできる機会かもしれません。

ちょっと話がズレてしまいましたが、さて、楽天は改革を迎えているわけで、個人情報の扱い厳格化、決済方法の統一化などがあり、なるべく多くの受注操作をRMS上で行わなければならないようになってきています。

そんなこともあり、最近では、自社開発DBとの連携のために、RMS Web Service APIを利用しなければならないということもかなり増えてきました。今日はそのお話です。

RMSのAPI

まず、もってですね、楽天のRMSのAPIというのは、みなさんご存知でしょうか。2012年ごろからスタートしているRMS用のAPIです。それより以前は、公開情報(つまり商品情報ですね)を取得して、楽天外のwebサイトで活用できるようになっていたわけですが、RMS APIができてからは、RMSを楽天のシステム外で利用できるようになっています。(もちろん自社運営の楽天サイトの情報しかとることはできません) 経緯はこのあたりにくわしいです。

このRMSのAPIというやつがですね、わたしたちwebをメインにしているエンジニア・プログラマには、あまり馴染みのない「SOAP」という規格のAPIを採用しています。こいつがなかなか厄介で、webで多く使われているPHPでは、コードが冗長になりがちなのです。

そのコードについてはこちらのサイトにてサンプルが書かれていますので、参考になります。わたしも最初は参考にしました。

楽天PayOrderのAPI

上記のように、まぁぶっちゃけて言うと大変に分かりづらいSOAPを仕様したRMS APIなんですが、一回実装してしまえば、そうそう変更するものでもないので良しとしていました。

ここからが本題です。

ところがですね、楽天Pay移行により注文情報取得APIを変えなければならなくなりました。2018年以降、楽天各店は決済方法統一のため楽天Payに強制移行となりますので、既存APIを利用していた事業者さんは、いずれ、のりかえが必要ということです。 楽天Payへの移行については、こちらの記事がよくまとめられています

しかし、文句ばかりも言っていられませんので、楽天へ出店しているクライアントさんのために、API改造に着手しました。いつもそうなんですが、楽天さんのエンジニア向けドキュメントは、ぜんぜんまとまってなくて、どこにいけばわかるのかがわからない。たどり着いたとしても、実は全体像はそこだけではわからない…など前途多難なのです。

とりあえずドキュメントにたどり着いたので、従前のOrderAPIを書き換えてみる。

…あれ…なんか変だ…動かない…というよりは、楽天から返ってくる書式が変だ…
…ん???
…あれ??????
(ここまで1時間)

ようやく気付いたのですが、楽天PayOrderAPIだけが、基底となっている技術そのものを変えてきやがったのです!!!もう「やがった」といいますが許してください!

よく読めと言われれば、それまでですが、楽天PayOrderAPIだけは「JSONベースのSOAP」なのです。APIさわったことのあるエンジニアならわかるとおもいますが「お前何いってんだよ!」ってなりました。「SOAP」はもともとXML通信インターフェイスだろうよ!!!リクエストもレスポンスもJSONでやるのに、技術そのものはSOAPなのです。この矛盾に気づくのに時間がかかりました。(ここから想像するに、おそらく楽天PayのAPIの基底フレームワークはIBMのCICSだと思われます。もう少し標準的な仕様にしてくれませんかね???)

とっととコードを出せ

はい。。。ごめんなさい。。。取り乱しました。

class RMS {
    
    public static $serviceSecret;
    public static $licenseKey;
    
    // ----------------------------------------------------
    /**
     * オーダー取得
     */
    public static function searchOrder(){
        
        ////認証キーを作成
        // oAuthのが1289倍楽なんですけど!!!
        $authkey = "ESA " . base64_encode( self::$serviceSecret . ':' . self::$licenseKey );
        
        ////検索日付
        // そんなもん必須にせずに良きに計らえや!!!!
        // しかもいちいちGMTに変換しなきゃあかんのかい!!!それも良きに計らえよ!!!!
        // 第二引数で日本時間指定できるようにしておいた
        $beginDate = gmdate( 'Y-m-d\TH:i:s+0900', strtotime('2018-07-05 00:00:00'));
        $endDate = gmdate( 'Y-m-d\TH:i:s+0900', strtotime('2018-07-29 00:00:00'));
        
        //// なぜか楽天ペイAPIだけ しかも形の上ではSOAPなのに JSONでリクエスト…
        // 統一してくれんかな!!!ライブラリ書くのめんどくさいんだけど!!!!
        $requestJson = json_encode([
            'dateType' => 1,//期間検索種別
            'startDatetime' => $beginDate,//検索対象期間先頭日時(普通startじゃなくてbeginじゃねぇか??)
            'endDatetime' => $endDate,//検索対象エンド点
            'orderProgressList'=> [ 100, 200, 300, 400 ],//取得したいオーダーステータス
        ]);
        
        
        //// リクエストヘッダ作成
        // うーん なんかこの認証いまいちだなー・・・
        $header = [
            'Authorization: '.$authkey,
            'Content-Type: application/json; charset=utf-8',
        ];
        
        
        //// SOAPリクエスト
        // この辺はREST APIでのPOSTと一緒。ただし、レスポンスコードで結果コードをとるので
        // レスポンスヘッダについては厳重目にとっておくべき。
        $url = 'https://api.rms.rakuten.co.jp/es/2.0/order/searchOrder/';
        $curl = curl_init($url);
        curl_setopt( $curl, CURLOPT_POST, true );
        curl_setopt( $curl, CURLOPT_RETURNTRANSFER, true );
        curl_setopt( $curl, CURLOPT_HTTPHEADER, $header );
        curl_setopt( $curl, CURLOPT_POSTFIELDS, $requestJson );
        curl_setopt( $curl, CURLOPT_HEADER, true);//これを指定するとレスポンスにヘッダがついてくる
        
        // curlセッションを実行 ヘッダが付いているので分解
        $response = curl_exec( $curl );
        
        // ステータスコード取得
        $statusCode = curl_getinfo( $curl, CURLINFO_HTTP_CODE );
        
        // ヘッダサイズを取得して、その分を切り取ってしまう方策
        // もう少しいい方法があるかもしれない??
        $headerSize = curl_getinfo( $curl, CURLINFO_HEADER_SIZE );
        $requestHeader = substr( $response, 0, $headerSize);
        $requestBody = substr( $response, $headerSize );//ここに結果JSONがかえる
        
        
        //// jsonをオブジェクトにー
        $return = json_decode( $requestBody );
        
        curl_close( $curl );
               return $return;
        
    }
    
    // ----------------------------------------------------
    
}

ざっくりとclassにしました。モダンなコードではないですし、何かが漏れていますが気にしないでください。
引数でパラメータを指定できるようにするなどの改造は必要だとおもいますし、いくつかのエンドポイントを実装するにあたってはPOST部分を独立メソッドにすべきでしょう。そこは、皆さんのセンス。

このクラスをロードしておいて

<?php
RMS::$licenseKey = 'ライセンスキー';
RMS::$serviceSecret = 'シークレットキー';
$res = RMS::searchOrder();

とかでだいたい勝てます。

以上、現場からでした。
「室井さん。どうして現場に血が流れるんだ!」(←古い)

楽天の注文フォームに無茶させるver.2

6年ほど前に楽天の注文フォームに無茶をさせるという記事をかきました。

なんかちょいちょい利用されているようでして、、、
こんな記事でも役に立つのかっていうはなしなんで、
ちゃんと最新版をつくっておきます。

何をトチくるったかajaxでカートに入れられるようになりました

以前は、フォームを作成しなければ、
カートに入れることができなかったのですが、
現在は楽天が、ajax経由で、カートにいれることができる仕組みを用意しました。
なかなかトチ狂っています。
最近は遷移なしでカートに入れるUIが流行っているので、
そのために準備したのでしょう。

仕様

以前の方法と同じく、ショップIDと商品番号(RAC番号)を特定しておく必要があります。
詳しくは以前の記事を。

パラメータは、

{
  'shopid': ショップID(整数値),
  'units': 個数(整数値),
  'itemid': 商品番号(整数値),
  'dbasket_choice_select[]': 'オプション文字列',
  'dbasket_choice_select[]': 'オプション文字列2',//オプション文字列は複数可能
}

です。(上記はjavascriptのオブジェクトで表現しています)

これをajaxするだけです。
宛先は
http://direct.step.rakuten.co.jp/rms/mall/cartAdd/
です。

jQuery前提で、書くなら、

$.ajax({
  url: 'http://direct.step.rakuten.co.jp/rms/mall/cartAdd/',
  type: 'get',
  dataType: 'jsonp',
  data: {
      'shopid': ショップID(整数値),
      'units': 個数(整数値),
      'itemid': 商品番号(整数値),
      'dbasket_choice_select[]': 'オプション文字列',
      'dbasket_choice_select[]': 'オプション文字列2'
})
.then(function(data){
    //ここで結果を解析
});

という感じです。

ポイントは「json」ではなく「jsonp」にするところ。まぁこれは並のフロントプログラマなら意味がわかると思うので、割愛します。

えっと。。。。なんで「GET」で動いちゃうんですかね!?楽天さん!!!
HTTPの定義では、GETはデータを取得する目的で動くメソッドのはず、、、、
何かしら、データやシステム動作に影響を与える処理の場合は「POST」にしましょうね。。
楽天さん。まぁそんなこといいだしたら、CSSの仕様とか色々アレですかね!!!

というDISはここまでにして、、、、

さて返り値は

{
   addItemMessage: true,
   cartURL: null,
   dialogTitle: "買い物かごに追加されました",
   includedPostage: false,
   includedTax: false,
   itemName: null,
   itemPageUrl: null,
   price: null,
   resultCode: "0",
   resultMessage: ""
}

こんな感じの結果がかえってきます。

なんとなく見ると分かるのですが「dialogTitle」とあるように
本来は、楽天が公式的にサイト内でダイアログを表示するために使うAPIのようです。

で!またこのjsonが何だかなな仕様でして
resultCodeが「0」だと成功です(しかもそして文字列です)
そんでエラーがあればresultMessageに値が入ります。

したがって、resultCodeとresultMessageを見れば、結果がわかります。

$.ajax({
  url: 'http://direct.step.rakuten.co.jp/rms/mall/cartAdd/',
  type: 'get',
  dataType: 'jsonp',
  data: {
      'shopid': ショップID(整数値),
      'units': 個数(整数値),
      'itemid': 商品番号(整数値),
      'dbasket_choice_select[]': 'オプション文字列',
      'dbasket_choice_select[]': 'オプション文字列2'
  }
})
.then(function(data){
  if( data.resultCode == '0'){
    alert('OK');
  }
  else{
    alert( 'error!: ' + data.resultMessage );
  }
});

みたいな感じですかね。あ!そもそもHTTPのエラーが帰った場合の処理は上記コードには入っていませんから注意してください。

実践!!

仮に、楽天でたまたま見つけた
https://item.rakuten.co.jp/kadenshop/4111-snw-1567/?s-id=top_normal_rk_hashist_realtime
こちらのページの商品をカートに入れてみましょう。

$.ajax({
  url: 'http://direct.step.rakuten.co.jp/rms/mall/cartAdd/',
  type: 'get',
  dataType: 'jsonp',
  data: {
      'shopid': 244202,
      'units': 1,
      'itemid': 10248876,
      'dbasket_choice_select[]': 'オプション文字列',
      'dbasket_choice_select[]': 'オプション文字列2'
  }
})
.then(function(data){
  if( data.resultCode == '0'){
    alert('OK');
  }
  else{
    alert( 'error!: ' + data.resultMessage );
  }
});

ですねー!

ショップIDと商品番号は、商品ページのカゴに入れるボタン周辺に
input type=hidden

「shop_bid」と「item_id」という名前で表示されているので、
ソースから引っ張ってこれます。

つまり他人の店でも、カートに入れる処理ができます。
まぁ何の意味もないですが。。。

で、先程の説明どおり、なんでか知らないですけど、
これGETで通っちゃうので、

http://direct.step.rakuten.co.jp/rms/mall/cartAdd/?shopid=244202&units=1&itemid=10248876&dbasket_choice_select[]=オプション文字列&dbasket_choice_select[]=オプション文字列2

このURLをブラウザ直打ちでも動作確認できます。。。おいおい(´・ω・`)

どうですか?

無茶をさせているようですが、結構使える技術です。
見積結果として複数の商品を一気にカートに入れたい場合などにかなり重宝をします。
以前の記事の頃ですと、formをsubmitするなどしなければならず
セキュリティソフトウェアがブロックするなどの問題がありそうでしたが、
これならその危険性はないです。

クルーズ旅行に出てみた!出発まで編


2018年一発目のネタづくりとして、というのはアレですけども、生まれて初めて船での長旅に出てみようと思いました。7泊8日の船の旅、名古屋を出て、那覇、台湾の基隆、宮古島を経て東京へいく旅です。

いまはその旅の最後の夜。船の中でこの記事を書いています。

その感想を書いてみましょう。

なんで乗ろうと思ったのか

クルーズの旅に出ようと思ったのは、テレビ朝日で放送されている『陸海空 地球征服するなんて』という番組で、大西洋を渡るクルーズ船にのって、さまざまな人に会ってみようという企画を見たのがきっかけです。

船旅というのは、時間もかかり不便で、金持ちの道楽か何かだろう。ぶっちゃけるとそんなイメージを持っていました。が、しかし、テレビをみて、こんなにたくさんのひとに会えるのは楽しいかもしれない。そして、これだけ不便な生活を一回やってみよう。そんな風に思ったわけです。

なんとなーく調べてみると、クルーズ旅行は、いくつかのクラスがあり、外国船籍のカジュアルクラスの船ならば、2人1室での旅なら1泊10,000円程度、1人でも1泊18,000円程度で、食事つきの旅に出ることができるということを知りました。これは現実的だ。というわけで申し込んだわけです。

しかし不安は大きい

あるとき旅行代理店から電話がかかり「旅行代が安くなった」と連絡をうけました。そう言われても数千円だと思いますよね。ふつう。「45,000円のお値引きでございます」。えっ??どういうこと??何の割引???特に説明はありませんでしたが、先に振り込んでおいた旅行代金から45,000円が返ってきました。さすがにこの価格となると、安全は大丈夫なのかと不安が出てきます。

2018年に入り、いよいよ出発が近づくと、いろいろ不安も出てきました。さすが、ともいうべきなのか、海外のクルーズ専門会社、しかもカジュアルクラス。乗船券が届かない(笑)代理店に聞いても、発行してくれないんですよ(泣)みたいな反応で、おいおいと思いました。が、しかし、船に乗った後に、クルーズ旅行の慣れているひとによると、よくある話だそうです。ははは

しかし、乗船券が届いてみると、まさかの普通紙にレーザープリンタで印刷された乗船券が届くという、なんとも「カジュアル」な展開。それどころか、荷物を預けるためのタグですら普通紙。びっくりびっくりびっくり。

webのお仕事をしている身からすれば、船内でのインターネット接続については気になるところ。代理店に聞いたところ「あくまでも目安なんですが従量制で250MBあたり10USDです」との返答。ちょっと待ってくださいよ!!従量制なのはいいですけど、価格が目安ってどいうことですか!!?? なんでも、乗る船によって異なるとか…は。。。は。。。

言い知れぬ不安だけが大きくなって来ましたw そもそも、クルーズ旅行は「わざわざ行く」と決心しないかぎりは、現代の長い人生の中でも、一生行かないタイプの旅行です。ほかの旅行とは、準備がちがいますから、仕方ないですね。。。

しかし、こういった不安は、ほとんどが余計な不安に終わるわけですが、後日この不安は、実は別の悪いことへの予感だったのです。後日おはなししましょう。

さぁ出発だ。

2018年1月20日。いよいよ出発の日を迎える。あらかじめ届いた案内には、「名古屋港ガーデンふ頭に来てください。12時くらいから乗れます」とだけ。。。あいかわらず不案内ですな。というかんじで、水族館いくか、花火を見に行くかでもない限りいかない、名古屋港ガーデンふ頭へ。

近づいていくと、oh!見たことのない大きな船が!これに乗るのかとおもうと、ワクワクしてきました。そこまで考えていた不安は飛びました。というか「でっかい山があるから登ってやれ!話はそれからだ!」と言う感じかもしれませんが。

乗船に関して、審査というのは、あまり厳しくはなかったです。金属探知機を通りますが、航空機ほど厳密ではないです。その代わりなのかわかりませんが、パスポートのチェックは何重にもなっていました。港湾局、船会社、船に同上セキュリティの3回のチェックでした。

びっくりしたのが、パスポートは船のスタッフによって人質にとられます。航空機とは本当にいろいろちがうんですね。

船にのってみると



カジュアルクラスの船とはいえ、さすが洋上のホテルというつくりです。船は12フロア構造。わたしが泊まるのが、フロア6「ロンドンフロア」と名付けられたフロアです。部屋にはいるとシングルベッドが2つ。十分な広さの居住空間が保たれており、なかなかに快適そうです。

11フロアにはプールとジャグジーとバー、軽食エリアがあります。なんと開放的!!

ちなみに、わたしは最初迷いましたし、他の多くのお客さんも迷っていたのですが、食事は3食付で、出発日は夜だけという案内なのですが、実は、ブッフェは営業時間内であれば、いつでも好きなだけ食べられます。中途半端な時間に食事を取ることができずに船に乗った場合は、どうぞ最初ぶブッフェへいってみてください。

ゆったりするぞー!と言う感じがひしひしと湧いてきたところで、「ぶぉーーーーー」と汽笛が鳴り、出航となりました。

プライスレスな体験のはじまりを体感しました。マスターカード。
そんなわけで続きをどこかで書きましょうー。

出発まで編のまとめ

  • カジュアルクラス外国船籍のクルーズなら、1人でも1泊18,000円で食事付き。そして、謎の大幅割引がつく
  • わりとざっくりとした案内だが、そういうものだと思うべき
  • 価格の割にはしっかりした居住空間
  • 出発からして、プライスレスな体験
  • 船のセキュリティは航空機と違う。高飛びに使えるかも。

自分がいる業界以外でセミナー登壇した話

2017年

毎年セミナーやらなんやらの登壇は、2から3回をこなしています。例年なら、おおよそ、自信が生業としている、webだったりとか、通信販売の世界でのセミナー登壇となることが多いわけですが、2017年は、関わりがない業界からの、セミナー依頼がいくつもありました。
そのお話を。

ちなみにその業界というのが

  • メガネ業界の勉強会
  • 某市の商工会議所
  • 某旅行会社

などですね。

自分の業界の歴史をひもとくことからやらないといけない

自分の業界向けのセミナーであれば、ひとつひとつの事柄について
「そういうものだ」という共通認識があるし、
「そういうものだ」という共通認識によって教育をされてきているので、
その共通認識のもとに、話を組み立てていけばよいですが、
業界外のセミナーとなると、ほぼすべての事柄について、
なぜそうなのか?という、そもそもの「Why」から紐解いていく必要がある。
そして、聞く側も、それを求めている。

たとえば

  • 「web」ってなによ
  • インターネットとwebの違い
  • googleの役割
  •  ホームページって?ブログって?

みたいな話だ。
webを生業としている人にとっては、ほとんど空気みたいに認識している「感覚」だが、
これを明文化しなければならないことが多い。

それってどういうこと? それって何?という質問攻めにあい、冷や汗の連続となる。
最初はとても戸惑った。

「頭の中の索引」

自分が働く業界外のセミナーでは、こんなふうに「そもそもの質問」攻めにあうので、
こういった場所で登壇するには、自分の知識量が問われる。
これは「物知り」ということではなくて、

  • あーそういえば、あのセミナーでこういうこと言っていたな
  • そういえば、どっかで、この情報見たな

という「自分の頭の中の索引」が重要。

「引き出し」とも言えるが、
ぶっちゃけると、細かく調べることは後でもできちゃう。
必要でなければ覚えることもないでしょう。

他業界でのセミナー登壇というのは、
知識を固定化する上でもとても重要な経験だと断言できる。

この記事をご覧になっている みなさんは上記の質問に明確に回答できますか?
おそらく多くの方は、それとなく回答する結果になるのではと思う。

当たり前は当たり前じゃない

よく言われることですが、自分の常識は他人にとって非常識。
自分の業界では当たり前でも、他業界で当たり前でないことは大量にある。
これは「マーケットギャップ」「市場差」と捉えるとおもしろい。
他業界の方のお困りごとに対して、
「普通に考えたらこうだ」という回答をするだけで、
ゲスい言い方になるが、なかなかにヒーロー扱いをうける。

前述のメガネ業界のセミナーでは、
webを使った集客の基本の、さらに基本の部分の内容だった。
内容としては、
「しっかりターゲッティングして、顧客行動を見ましょうね」
という話をざっくりとした内容。

その中で質問されたことに対する回答として感謝されたことは
「補聴器とサングラスは商材としてかけ離れている。サイトを分けることも検討すべき」
と指摘したこと。

補聴器とサングラスは、webを作る業界にいれば、
入り口も目的も顧客行動も違うのはすぐにでもわかることだが、
彼らにとっては、この2つは
「眼鏡屋に来る客が買うもの」
としてまとめられてしまっていた。
「そういうものだ」という常識にとらわれていた。
だが、指摘をした上で
「想定する顧客の姿は違うものですよね?」
と説明したら
「すげぇ!!!」ってなったわけだ。

マーケットギャップを利用すればヒーローになることはできる。
どうせ、登壇するなら、そのほうが気持ちよくできる。

結果的にいくつか仕事も産んでいる。

そんなセミナーどっから話がくるの?

こんな話を先日友人にしていたら、そんな話どっからくるのさ?といわれた。
別にセミナー登壇の仕事は労力に見合っていない報酬になることも多く
正直やりたくない仕事のひとつだ。
したがって「ほしい」などとはひとこともいっていない。

ただ、ひとついえるのは、いずれも元々のクライアントさんからの紹介だ。
クライアントさんに対して、普段から新しい技術や知識や成果をあたえることが結果的に
他業界での登壇の誘いとなった。
多くのビジネスが、取引先に足しいて、これらの価値提供をすることに終始するといえる。
「商いとは他己中心だ」と、よく言われることだが、まさにそれで、
他己が結果的に自己になるっていうやつかもしれない。

というわけで言えるのは

自分の知識が強烈なインパクトを与える
自分が働く業界ではない別の業界で登壇するのは
いろいろおいしいよってはなし。

ボン・ヴォヤージュ!

CentOS7+PleskOnyxな環境でのはまりごと

CentOS7 + PleskOnyxな環境をはじめて借りてハマったことをメモ

イニシャルではApacheモジュール版PHPがうごいていない

Pleskではすこし前のバージョンから

  • PHPの動作モード
  • PHPのバージョンを選択

できるようになっていますが、PleskOnyxでは、PHPの動作モードが

  • FAST CGI
  • FMP

のみになっており、あれ?Apacheモジュールは???となりました。
サーバーを借りたクララオンラインさんに、サポートをおねがいしたところ
http://faq.clara.jp/index.php?solution_id=1350
をご紹介くださいました。なーるほど。Pleskはもともといろーんなものがモジュール化していますが、
mod_php自体もモジュールなんですね。


CPANモジュールが共通ライブラリディレクトリにインストールできない

sshでログインした際に自動的に環境に

  • PERL5LIB=/ユーザーディレクトリ/perl5/lib/perl5:
  • PERL_MB_OPT=–install_base /ユーザーディレクトリ/perl5
  • PERL_LOCAL_LIB_ROOT=:/ユーザーディレクトリ/perl5
  • PERL_MM_OPT=INSTALL_BASE=/ユーザーディレクトリ/perl5

がセットされる。
これに気づかずに3時間なやんだ。とっとと
perl -V
してれば気づいたのに。。。

これら環境変数を削除した上で、CPANインストールすればよろし。


cgi-binディレクトリが有効になっていない

まぁPerl/CGIとかRuby/CGIとかやんなやって話ですが、昔の枯れた技術というのはとても有用でしてね…
で、イニシャル状態では、公開ディレクトリにcgi-binディレクトリがあって、
そっちが有効になっているんです。昔良くあった構成

/ユーザーディレクトリ/cgi-bin/
/ユーザーディレクトリ/httpdocs/

って言う構成がとれないわけです。
ので、ふるいサイトを移行するとちょっとめんどい。

/ユーザーディレクトリ/httpdocs/cgi-bin/

の中に格納すればいいわけですが。


1個1個はどうってことない話ですけど
勘がない環境だとハマりますね。って言うメモです。

20周年を迎えるECサイトをリニューアルした

先日20周年を迎えるというECサイトのリニューアルを仰せつかりました。
そのいろいろを。ブログに。


てるくにでんき

今回担当したのは「てるくにでんき」というWEBサイト。
開設はなんと1996年。90年代ですよ!90年代!!
 
まず、わたくし2016年現在で36歳です。20年っていうと、僕の人生の半分を越える年月なのです。
20年前僕はまだ高校1年生。当時なんとなく音楽(プレーヤーでなく作る方)で生きていけたらなとか
かなり甘い将来観測で生きており、インターネットなんかに感心なんかない時代。
 
今の20代には信じられないかもしれないけれど、
当時パソコンは、一式揃えれば30万弱。
インターネットはISDN回線。つまり、ネットは全て通信時間による従量課金制制。
フロッピーディスクも普通につかわれていた。
もっと信じられないことに、WEBブラウザが有料だったりなど…。
ECサイト制作やコンサル歴は15年近くになりますが、
20年前には「ECサイト」ということばはなく「電子商店」といわれていたことを
最近初めて知ったくらいです。
 
そんな時代に「インターネットならいける」とECサイトをやろうとする。
ほとんど「クレイジー」です。
 


なんでやることになったのか

てるくにでんきの堂園社長と出会ったのは、たしか4年ほど前。
リーマン・ショックやら、Googleを中心としたインターネット社会の再編など
大きな時代の潮流のなかで、さまざまな施策を考えてらしゃったのでしょう、
10年以上前から協力しているクライアントさんとの、
コラボ企画での出会いでした。
 
自分では、正直、堂園さんが僕の何を気に入ったのかは
未だよくわかってはいませんが、
多分野球好きなところが気に入っていただけたのでしょうと勝手に思っていますw
 
紆余曲折もあり、そこから2年ほどあれやこれやで進まない時間がありましたが、
そんな折、堂園さんが、仙台で、震災復興のためのセミナーをされるということで、
普段のぼくは、あまり何かのセミナーのために遠出をするというのは
めったにないのですが、
 
「よくかんがえると、堂園さんと長く話したことがない」
「なんか、わからないけれど、行かなくてはいけない気がする」
 
という気持がわきおこり、仙台に堂園さんの話を聞きに行きました。
本当にそれだけのために仙台に行き、セミナー会場の周辺1キロくらいの仙台しか知りませんw
 
ありがたいことに当時から、協業の打診はいただいていたものの、
ほんとうにぼくでいいのか?というような漠然とした不安を感じていたのは確かです。
まずは、話を聞いてみて。というところから。
 
話を聞いてみると、
「僕の想像を遥かに越える歴史を刻んでいる」
「堂園さんのやりたいことは、極めてはっきりしている」
という確信を得ました。(不安を払拭できたわけではないですが)
 
その後、徐々にいろいろなお話を重ねて、リニューアルを担当したわけです。


リニューアルの作業

リニューアルは、デザインうんぬんの前にやるべきこととして、

  • これまでご自身で作っていた商品DBを紐解く
  • 堂園さんの伝えたい事はなにか?
  • 堂園さんの苦手なことはなにか?

ということをつきつめるところでした。
 
商品ページ数にして10万ページ。
重ねたコンテンツを合わせれば20万ページ近くのデータ。
  
正直、うちはデザイン性では良いとは言えない業者です。
サイトの持ち主が「何を言いたいのか」「何を伝えたいのか」より多くの表現は苦手です。
ですが、そこを1個1個拾い上げることを得意とする業者でもあります。
 
20万ページの言霊を拾いながら、思ったのは、
「20年というのは、重くもあり軽くもある」
ということ。
 
サイト制作者としてではなく、一人の消費者としてそのデータの前に立ってみると
「へぇ!そうなんだ!」とおもうことが押し寄せてくる反面、
「こういう言葉で書けば、もっとわかりやすいのに」ということも押し寄せてきました。
 
こういうのを見つけるのも、僕たち制作者のしごとだろうなと思ったわけです。
 


未来

さて、かくして、WEBサイトはリニューアルされたわけですが、
正直なところ、成功するかの確信は未だにありません。
なぜかというと「売る」のはあくまでサイトマスターである「てるくにでんき」だからです。
リニューアルはあくまで、
てるくにでんきであることを表現するためのステージを作ったに過ぎない。
まるで責任逃れのような書き方かもしれませんが、厳然たる事実です。
 
多くのクライアントさんがおっしゃることの中で、
電子商取引を失敗するクライアントさんが共通しておっしゃることがあります。
 
「売れるようにしてください」
 
「売れる」のか「売る」のか。はたまた「買わせる」のか「買ってくださる」のか。
ここのところから突き詰めなければ、WEBでの商売は厳しいものがあります。
時流の面からの解剖も必要でしょう。「今は売る」「来月は売れる」みたいな形で。
(さすがに20年選手の堂園さんはそのようなことは仰りませんでした。)
 
てるくにでんきは大量の商品をもった零細企業という日本においては、なかなか無いタイプの企業です。
つまりは「小さな巨人」で、小回りが効くが大きなパワーをもっています。
これは僕の考える「売る」「売れる」の観点から見ると、大変にアドバンテージがあると考えています。
 
一般的に、零細企業がとるべき策は、一点集中一騎打ち戦といわれています。
(いわゆる「ランチェスターの第一法則」ですね)
つまり、小さな企業では、一つの策として「売る」市場をつぶしていき、
次に「売れる」市場をつぶして…ということを繰り返ししていく必要があります。
(これはかなり第一法則を曲げて見た「あえての言い方」です。あしからず)
 
しかし、てるくにでんきでは、数の面で圧倒できる「商品数」と「実績(のデータ)」があります。
こうなってくると、頭脳戦など遠距離戦でもやっていける片鱗があります。
(こちらはいわゆる「ランチェスターの第二法則」ですね)
 
零細企業ならではの一点集中で短期計画を考え、大きな枠では、頭脳を活かせるといわけです。
なんとなく両立できそうにみえますが、正直両立できる企業はめったにないです。
僕の個人的な(妄想的なところも含めた)意見ですが、てるくにでんきではできるだろうなと思っています。
 


なんか話がまとまりませんが

兎にも角にも、やり遂げましたので、みんなお祝いしてよ!という投稿でした。
 
なお、今回のECシステム開発にあたり、ベースCMSをご提供いただいた、sugimoto1981氏には一生頭があがりません。
 
現場からは以上です。

NW.JSのPHP版 nightrain

node.jsでネイティブPCアプリを作ることのできるNW.js(旧Node-Webkit)はご存知の方は多いでしょう。
JavascriptというWEBの言語で、ちょっとしたツールを作ったり、デスクトップガジェットを作ったりできるのは、
WEB寄りのプログラマやエンジニアにとっては、嬉しいツールです。
 
で、そんな話をしていると、大体出てくる要求としては、PHPやPerlやRubyで作れないの?って言う要求ですね。
調べてないですが、多分Rubyはありそうです(文化面で)。PerlはPerl/Tkでがんばりましょうや。
PHPは?って探すと、NW.jsのPHP版とも言うべき、nightrainというのがあります。
 
それの話をば。。。


ディレクトリ構成


アプリケーションパッケージを展開すると、こんなディレクトリ構造になっています。
settings.iniでウインドウ状態の定義や、公開ディレクトリ(でいいのか?)の定義をします。
 
定義ファイルを見ると、Macの場合は、Mac標準で入っているPHPを利用するように定義してあるようです。
ということは、相対パスをつかってパッケージングすれば、バージョン違いのPHPを利用したり、
nightrain専用のphp.iniを持ったPHPを使えるということでしょうか。その辺は、未検証ですが。
 


公開ディレクトリ

公開ディレクトリは「www」内で、とりあえずCodeigniterを入れてみたところ、普通に動きました。
普通のWEBサイトのように動いてくれるようです。
__DIR__などのマジック定数もちゃんと処理してくれますし、composerもいけました。
 
まぁPHPなんでマルチスレッドには対応してなかったりなど、悲しいところは
そのままなんですが、広範なライブラリを持つPHPでちょこーっとアプリを作れるのは、
やはり大きいですねー。
 


ところがですね

こんな便利なnightrainなんですが、開発をやめちゃったようで、
プロジェクトページが閉鎖になっています。
 
どうも公開のリポジトリは残っているようなんですが、ビルドがめんどくせぇ。ということで、
Mac版バイナリが見つかったので、
それを共有するために、この記事を書きました。
 
ダウンロードはこちらから
ライセンスは、MITライセンスです。
 
現場からは以上です。

Older posts