まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

マンホールマップなど、地図や位置情報を使ったアプリを何度か作ったことがあります。地図を扱うためのライブラリはいくつかありますが、leaflet.js というオープンソースのマップクライアントライブラリを使う機会がありました:
2017110901


leaflet.js 自体はオープンソースで提供された JavaScript によるマップ操作クライアントライブラリです。ここで扱うマップは OpenStreetMap だったり、国土地理院のものだったり、(プラグインを使えば)Google MAPs だったりを選ぶことができます。

使い方も簡単で、まずは(CDN などから)leaflet の css と javascript をロードしておきます(以下の例では leaflet 1.2.0 を使っています。また後で使うので jQuery もロードしています):
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.2.0/leaflet.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.2.0/leaflet.js"></script>

HTML 内にマップを表示する部分を定義します。この例ではブラウザ画面全体に広がる <div id="demoMap"></div> を定義しています:
<style>
html, body  {
	width: 100%;
	height: 100%;
	padding: 0px;
	margin: 0px;
}
#demoMap {
	width: 100%;
	height: 100%;
}
</style>
  :
  :
<body>
<div id="demoMap"></div>
</body>

そしてメイン部分。上記の demoMap 内に地図を表示します。以下の例では OpenStreetMap を使って千葉県船橋市役所周辺の地図を表示し、市役所の位置にマーカーを置いて、2秒おきにマーカーをランダムウォークさせる、というものです:
<script>
//. 船橋市役所の緯度経度(初期位置)
var lat = 35.69471100;
var lng = 139.98262100;

var map = null;
var marker = null;

$(function(){
  //. 船橋市役所を中心とした地図を OpenStreetMap データで表示
  map = L.map('demoMap').setView( [ lat, lng ], 15 );
  L.tileLayer(
    'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      attribution: 'Map data © <a href="http://openstreetmap.org/">OpenStreetMap</a>',
      maxZoom: 18
    }
  ).addTo( map );
  
  //. 初期状態で市役所にマーカーを設置
  marker = L.marker( [ lat, lng ] ).addTo( map );

  //. マーカーを2秒おきにランダムウォークさせる
  setInterval( randomWalk, 2000 );
});

function randomWalk(){
  //. マーカー位置をランダムに移動
  lat += Math.random() / 100.0 - 0.005;
  lng += Math.random() / 100.0 - 0.005;
  var latlng = new L.LatLng( lat, lng );
  marker.setLatLng( latlng );
}
</script>


完成品の HTML はこちらに用意しました。全体を確認したい方はこちらから HTML ファイルをダウンロードしてください:
https://raw.githubusercontent.com/dotnsf/samples/master/leafletjs.html


ダウンロードした HTML をウェブブラウザで表示すると、以下のように船橋市役所を中心としたマップが表示され、青いマーカーが2秒おきにその場所を少しずつ変えて移動する様子が確認できると思います:
2017110901


こういったライブラリを使って地図アプリを作っておくと、利用する地図を変える(例えば OpenStreetMap だったり、Google MAPs だったりに変える)のが楽になりますね。

データのマイグレーションなどで WordPress を使う場合、元データのユーザーデータごと移行するケースがあると思います。そんなユーザー情報を WordPress の機能で追加するのではなく、外部アプリケーションから直接データベースを操作して追加する方法を紹介します。

なお、今回紹介するケースでは WordPress 4.8.2 を使いました。また WordPress のテーブルプリフィクス値はデフォルトの 'wp_' であると仮定して以下を紹介します。異なる設定で利用している場合は適宜読み替えてください。加えて、今回紹介する PHP ファイルを MySQL サーバーとは異なるホストから実行する場合は、このファイル内で指定したユーザーが MySQL にリモートログインできるような設定があらかじめされている必要がある点をご注意ください。


まず WordPress を普通にセットアップすると、いくつかのテーブルが自動的に生成されます。その中でユーザー情報を管理するのは wp_users と wp_usermeta の2つです:


wp_users はその名の通り WordPress のユーザーテーブルとなっていて、1レコードが1ユーザーの情報を意味します。詳しくは後述しますが、単に「ユーザーを登録する」だけであればこのテーブルに1レコード追加するだけで事は足ります。

一方、wp_usermeta はユーザーの属性に関わる値を管理するテーブルです。例えばユーザーのニックネームや権限といった wp_users だけでは管理できない一部の情報や、システムが内部的に利用するセッション情報、最終アクション日時といった情報を wp_usermeta テーブルで管理します。以下では「管理画面にアクセスできる権限を持ったユーザーを追加する」例を紹介しますが、この場合は wp_usermeta も操作する必要があります。


まず wp_users テーブルの属性を desc コマンドで調べた結果がこちらです:
mysql> desc wp_users;
+---------------------+---------------------+------+-----+---------------------+----------------+
| Field               | Type                | Null | Key | Default             | Extra          |
+---------------------+---------------------+------+-----+---------------------+----------------+
| ID                  | bigint(20) unsigned | NO   | PRI | NULL                | auto_increment |
| user_login          | varchar(60)         | NO   | MUL |                     |                |
| user_pass           | varchar(255)        | NO   |     |                     |                |
| user_nicename       | varchar(50)         | NO   | MUL |                     |                |
| user_email          | varchar(100)        | NO   | MUL |                     |                |
| user_url            | varchar(100)        | NO   |     |                     |                |
| user_registered     | datetime            | NO   |     | 0000-00-00 00:00:00 |                |
| user_activation_key | varchar(255)        | NO   |     |                     |                |
| user_status         | int(11)             | NO   |     | 0                   |                |
| display_name        | varchar(250)        | NO   |     |                     |                |
+---------------------+---------------------+------+-----+---------------------+----------------+
10 rows in set (0.01 sec)

プライマリキーは ID(auto_increment)で、他のフィールドは全て Not Null 属性です。ただ user_registered と user_status にはデフォルト値が設定されています。ということはこれらを除いた user_login, user_pass, user_nicename, user_email, user_url, user_activation_key, display_name を指定すればレコードは作れそうです。

というわけで、とりあえずは以下のような関数(createWpUser)およびファイル(wp-user-import.php(改良前))を PHP で作ってみました:
<?php

$wp_db_name = 'wpdb';            //. WordPress DB
$wp_db_host = 'localhost';       //. WordPress ホスト名
$wp_db_username = 'adminuser';   //. WordPress DB の管理者ユーザー名
$wp_db_password = 'adminpass';   //. WordPress DB の管理者パスワード
$wp_table_prefix = 'wp_';        //. WordPress DB のテーブルプリフィクス


function createWpUser( $u_email, $u_password ){
  global $wp_db_name, $wp_db_host, $wp_db_username, $wp_db_password, $wp_table_prefix;

  $pdo = new PDO( 'mysql:dbname='.$wp_db_name.';host='.$wp_db_host.';charset=utf8', $wp_db_username, $wp_db_password );
  if( $pdo != null ){
     $pdo->query( 'SET NAMES utf8' );

     $sql1 = 'insert into ' . $wp_table_prefix . 'users(user_login,user_pass,user_nicename,user_email,user_url,user_activation_key,display_name) values( :user_login, MD5(:user_pass), :user_nicename, :user_email, "", "", :display_name )';
     $stmt1 = $pdo->prepare( $sql1 );
     $stmt1->bindParam( ':user_login', $u_email );
     $stmt1->bindParam( ':user_pass', $u_password );
     $stmt1->bindParam( ':user_nicename', $u_email );
     $stmt1->bindParam( ':user_email', $u_email );
     $stmt1->bindParam( ':display_name', $u_email );
     $r1 = $stmt1->execute();
  }
}

?>

createWpUser 関数のパラメータはメールアドレスとパスワードの2つだけです。メールアドレスは user_login, user_nicename, user_email, display_name に設定します。パスワードは MySQL の MD5 関数を使ってハッシュ化したものを指定します。また user_url および user_activation_key には '' を指定して insert を実行しています。

このファイルを使って、1ユーザーを作成するサンプルがこちらです(test1.php):
<?php

require_once './wp-user-import.php';

$r1 = createWpUser( 'user1@xxx.com', 'P@ssw0rd' );  //. ID: user1@xxx.com , パスワード: P@ssword のユーザーを新規作成する

?>

試しにこの test1.php を実行すると、WordPress に user1@xxx.com という ID のユーザーが登録されます:
$ php -f test1.php


管理者権限でログインして管理ページを開くと、たしかに指定したユーザーが追加されていることが確認できます(この画面で「権限グループ」が「なし」になっていることがわかります):
2017103001


この時点で作成したユーザーは有効なので、指定した ID とパスワードでログインすることはできます:
2017103002


ただ管理機能については権限を一切持っていないユーザーなので、無理やり URL を指定して管理ページにアクセスしてもエラー画面となります:
2017103003


とりあえずはここまで。wp_users に1レコード追加するだけで(管理や投稿の権限を持たない)ユーザーを作ることができることがわかりました。とりあえずこの状態になれば、後から管理者がこれらのユーザーの権限を(手動で)変える、ということも可能ではあります。


次に管理者や投稿者の権限を持ったユーザーを同様に外部から作成する方法を紹介します。先程と同様に wp_usermeta テーブルの属性を同様に調べた結果がこちらです:
mysql> desc wp_usermeta;
+------------+---------------------+------+-----+---------+----------------+
| Field      | Type                | Null | Key | Default | Extra          |
+------------+---------------------+------+-----+---------+----------------+
| umeta_id   | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| user_id    | bigint(20) unsigned | NO   | MUL | 0       |                |
| meta_key   | varchar(255)        | YES  | MUL | NULL    |                |
| meta_value | longtext            | YES  |     | NULL    |                |
+------------+---------------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)

umeta_id がプライマリキー(auto_increment)で、user_id は wp_users.ID を示す外部キーです。そしてこの user_id で指定したユーザーに対して、meta_key と meta_value で属性を加えることができるようになっています。

ユーザーの権限に関わる設定をするには、meta_key の値が 'wp_user_level' 及び 'wp_capabilities' となる2つのレコードを追加して指定することになります。具体的には対象のユーザーの権限に応じて、以下のような値を設定します:
ユーザー権限wp_user_level の設定値wp_capabilities の設定値
購読者0a:1:{s:10:"subscriber";b:1;}
寄稿者1a:1:{s:11:"contributor";s:1:"1";}
投稿者2~4a:1:{s:6:"author";b:1;}
編集者5~7a:1:{s:6:"editor";b:1;}
管理者8~10a:1:{s:13:"administrator";b:1;}


というわけで wp-user-import.php を少し改良します。createWpUser 関数のパラメータを2つから3つに変更し、3つ目のパラメータで上記の wp_user_level を指定するものとします:
<?php

$wp_db_name = 'wpdb';            //. WordPress DB
$wp_db_host = 'localhost';       //. WordPress ホスト名
$wp_db_username = 'adminuser';   //. WordPress DB の管理者ユーザー名
$wp_db_password = 'adminpass';   //. WordPress DB の管理者パスワード
$wp_table_prefix = 'wp_';        //. WordPress DB のテーブルプリフィクス


function createWpUser( $u_email, $u_password, $wp_user_level = 0 ){
  global $wp_db_name, $wp_db_host, $wp_db_username, $wp_db_password, $wp_table_prefix;

  $pdo = new PDO( 'mysql:dbname='.$wp_db_name.';host='.$wp_db_host.';charset=utf8', $wp_db_username, $wp_db_password );
  if( $pdo != null ){
    $pdo->query( 'SET NAMES utf8' );

    $sql1 = 'insert into ' . $wp_table_prefix . 'users(user_login,user_pass,user_nicename,user_email,user_url,user_activation_key,display_name) values( :user_login, MD5(:user_pass), :user_nicename, :user_email, "", "", :display_name )';
    $stmt1 = $pdo->prepare( $sql1 );
    $stmt1->bindParam( ':user_login', $u_email );
    $stmt1->bindParam( ':user_pass', $u_password );
    $stmt1->bindParam( ':user_nicename', $u_email );
    $stmt1->bindParam( ':user_email', $u_email );
    $stmt1->bindParam( ':display_name', $u_email );
    $r1 = $stmt1->execute();

    $user_id = null;
    $sql2 = 'select last_insert_id() as user_id from ' . $wp_table_prefix . 'users';
    $stmt2 = $pdo->query( $sql2 );
    if( $row = $stmt2->fetch( PDO::FETCH_ASSOC ) ){
      $user_id = $row['user_id'];
    }

    if( $user_id ){
      //. wp_user_level
      $sql3 = 'insert into ' . $wp_table_prefix . 'usermeta(user_id,meta_key,meta_value) values( :user_id, "wp_user_level", :wp_user_level )';
      $stmt3 = $pdo->prepare( $sql3 );
      $stmt3->bindParam( ':user_id', $user_id );
      $stmt3->bindParam( ':wp_user_level', $wp_user_level );
      $r3 = $stmt3->execute();

      //. wp_capabilities
      $wp_capabilities = null;
      if( $wp_user_level == 0 ){
        $wp_capabilities = 'a:1:{s:10:"subscriber";b:1;}';
      }else if( $wp_user_level == 1 ){
        $wp_capabilities = 'a:1:{s:11:"contributor";s:1:"1";}';
      }else if( 2 <= $wp_user_level && $wp_user_level <= 4 ){
        $wp_capabilities = 'a:1:{s:6:"author";b:1;}';
      }else if( 5 <= $wp_user_level && $wp_user_level <= 7 ){
        $wp_capabilities = 'a:1:{s:6:"editor";b:1;}';
      }else if( 8 <= $wp_user_level && $wp_user_level <= 10 ){
        $wp_capabilities = 'a:1:{s:13:"administrator";b:1;}';
      }
      if( $wp_capabilities != null ){
        $sql4 = 'insert into ' . $wp_table_prefix . 'usermeta(user_id,meta_key,meta_value) values( :user_id, "wp_capabilities", :wp_capabilities )';
        $stmt4 = $pdo->prepare( $sql4 );
        $stmt4->bindParam( ':user_id', $user_id );
        $stmt4->bindParam( ':wp_capabilities', $wp_capabilities );
        $r4 = $stmt4->execute();
      }
    }
  }
}
?>

createWpUser の3つ目のパラメータ(無指定時は 0)の値を上記の wp_user_level と見なして、wp_usermeta テーブルにユーザー権限に合わせた値を作成するように変更しました。また wp_usermeta テーブルにレコードを作成する際に wp_users.ID の値が必要になるので、MySQL の last_insert_id() 関数を使って作成したユーザーの ID を取得しています。

そして、例えば管理者権限を持ったユーザーを作成したい場合は以下(test2.php)のようなコードを用意します:
<?php

require_once './wp-user-import.php';

$r2 = createWpUser( 'user2@xxx.com', 'P@ssw0rd', 10 );  //. ID: user2@xxx.com , パスワード: P@ssword のユーザーを管理者権限で新規作成する

?>

試しにこの test2.php を実行すると、WordPress に user2@xxx.com という ID の管理者ユーザーが登録されます:
$ php -f test2.php

2017103004


このユーザーの ID とパスワードでログインすると、管理者ダッシュボードにアクセスして、管理者権限でのオペレーションが可能です:
2017103005



というわけで、PHP から WordPress の MySQL データベースを直接操作してユーザーを登録することができました!


(参考) WordPress Codex 日本語版:ユーザーの種類と権限

とにかく色々突っ込んでブラックホール化していた自宅のストレージ内を調べていたら、2000~2003 年頃に自分が作っていたアプリケーションソースコードを見つけました!
2017103100


その中に、今でもある程度動くものがありました。当時は github とかなかった(よね?)ので普通に自宅内で管理してたのですが、改めて公開することにしました:
https://github.com/dotnsf/dtopo


以下でその使い方を紹介しますが、まず前提条件として IBM Notes の環境が必要です(というか、公開しているのはノーツ用のアプリケーションです)。またアプリケーションは Eclipse 上で実行するつもりで作られているので、Eclipse が導入されている必要があります。これらはあらかじめインストールされているものとして以下を紹介します。


このアプリケーションはノーツ環境を設定するドミノディレクトリ内のメールルーティング情報および複製ルール情報を元に各サーバー間の関係を視覚化するものです。基本的にはこれらの情報が記述されたドミノディレクトリファイル(サーバーの names.nsf)があれば動かすことができます。試しに今回はこのようなサーバー接続文書情報を持ったドミノディレクトリ(dev/names.nsf)が手元にあるものとして、この内容を視覚化するケースを紹介します:
2017103101
(クリックすると拡大します)


まず上記 github の URL からプロジェクトをダウンロード&展開するか、git clone します。そしてそのプロジェクトを Eclipse のプロジェクトとして開きます。またこのプロジェクトの JRE は Notes にインストールされている Java に切り替える必要があります(詳しくはこちら)。加えてこのプロジェクトに外部 JAR ファイルとしてノーツフォルダ内にある Notes.jar および websvc.jar を追加設定する必要があります:
2017103102


次にプロジェクト内の dtopo.ini をテキストエディタで開きます。そして用意したドミノディレクトリファイルのファイルパスを n: で始まる行に指定してください(以下の例ではドミノデータディレクトリ以下の dev\names.nsf ファイルを指定しています)。ic, nic, cic はアイコン画像ファイルを指定しています。特に変更する必要はありません:
2017103103


そして同プロジェクトソース内の me.juge.dtopo.dtopo.java ファイルを右クリックし、"Run As ..." -> "Java Application" を選んで実行します:
2017103104


実行すると2つのウィンドウフレームが起動します。指定したドミノディレクトリファイルから読み取ったメールルーティング情報と複製ルール情報が別々のウィンドウフレームで表示されています:
2017103105
(↑図は複製ルール(フレーム上部のタイトルに注目))


この図の矢印の視点および終点付近はドラッグ&ドロップで位置を初期位置から調整することができます。またウィンドウフレーム自体のサイズも変更することができます。例えば今回の例では以下のようになりました(上:複製ルール、下:メールルーティング):
2017103101


2017103102


ソースコードは・・・さすがに当時の自分のスキル不足もあって決して見やすいとは思えません(苦笑)。ただ GUI アプリなのでマウスイベントに対するハンドリングを各所でしています。肝となるドミノディレクトリから複製/ルーティング情報を取り出すのは dtopo.java の 555 行目あたりからになります。ノスタルジックな意味合いもあって当時の汚いコードを残したままにしているのですが、興味のある方はこの辺りを参考にしてください(そういえば当時はノーツには  Mac クライアントも Linux クライアントもなかったので、これらの環境で動くかどうかは未検証です):
           :
           :
        Session s = Session.newInstance();
        String dbpath;
        String sserv, dserv, reptype, enabled, tasks;
        Vector sservs;
        String sdom, ddom;

        dbpath = nab;
        if( base.length() > 0 ){
          dbpath = base + "\\" + dbpath;
        }
        Database db = s.getDatabase( server, dbpath );
        if( !db.isOpen() ){
        	db.open();
        }

        //. 接続文書を検索する
        DocumentCollection docs = db.search( "Type = \"Connection\"" );
        for( int dcnt = 0; dcnt < docs.getCount(); dcnt ++ ){
            Document doc = docs.getNthDocument( dcnt + 1 );
           :
           :


ノーツデータを扱う Java アプリケーションの開発を考えている人の参考になれば嬉しいです。


ところで、このアプリケーションを作ったのは(ファイルのタイムスタンプによると)2002 年頃で、ギリギリ Java がクライアントアプリケーション開発にも使われていた頃です(標準の awt に加えて、swing や swt といった GUI のライブラリが出はじめていた頃でした。ちなみに今回紹介しているツールは awt で作っています)。アプリケーションだけでなくフレームワークにも時代を感じさせるものがありますが、その頃のノーツ R5 向けに作ったアプリケーションが 15 年以上の時を経て最新のノーツ9(のドミノディレクトリ)でもちゃんと動くという点がノーツらしいし、まさに Java らしい "Write once, Run anywhere!" だと感じました。

そういえばオラクル資本になってから、あまり "Write once, Run anywhere." って耳にしなくなりましたね。。。

 

このページのトップヘ