NewsDigest の短縮URL ndjust.in を経由させない

NewsDigest(株式会社JX通信社運営)という Android/iOS アプリからシェアしたリンクは、 ndjust.in で始まる転送用アドレスを経由するよう変換される。
この ndjust.in には Google Analytics が設定されているのみだが、気持ち悪いので直接元のページに移動するようにしよう。


ここではローカルで Apache を動かしている場合の設定方法を説明する。
まず /etc/hosts (Windows の場合は通常 C:/Windows/System32/drivers/etc/hosts) に次の記述を行い、 ndjust.in へのアクセスをすべてローカルの Apache へ向ける。

127.0.0.1	ndjust.in

次にバーチャルホストの設定を行うが、他のサイトや書籍を参考にしてほしい。その際、次のような設定を行い、mod_rewrite を用いてすべてのアクセスを /index.php へ向け、GET パラメータ _url に短縮 URL の ID を格納するようにする。

RewriteRule ^(.*)$ index.php?_url=$1 [L]

そして移動先ページの URL を取得するスクリプトは次のようになる。自動でのリダイレクトはしないようにした。

<?php
// コンソールでのテスト用
$path = isset($_GET['_url']) ? $_GET['_url'] : '6zMYUOOtsI';

$addresses = array(
	'54.192.233.19',
	'54.192.233.62',
	'54.192.233.93',
	'54.192.233.116',
	'54.192.233.173',
	'54.192.233.187',
	'54.192.233.191',
	'54.192.233.244',
);
$protocol = 'http://';
$host = 'ndjust.in';
$timeout = 5;

$headers = array(
	'Host: ' . $host,
	'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
	'Accept-Encoding: gzip,deflate',
	'Accept-Language: ja,en-us;q=0.7,en;q=0.3',
	'User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:48.0) Gecko/20100101 Firefox/48.0',
);

shuffle($addresses);
$url = $protocol . $addresses[0] . '/' . $path;

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
curl_setopt($ch, CURLINFO_HEADER_OUT, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_NOBODY, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$data = curl_exec($ch);
curl_close($ch);

$redirectTo = null;
if (preg_match('{<a href="(https?://.*?)" id="destination">}', $data, $matches)) {
	$redirectTo = $matches[1];
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>NewsDigest 経由のリダイレクト警告</title>
</head>
<body>
<?php if ($redirectTo) : ?>
<p>次の URL へリダイレクトしようとしています</p>
<p><a href="<?= htmlspecialchars($redirectTo) ?>"><?= htmlspecialchars($redirectTo) ?></a></p>
<?php else : ?>
<p>リダイレクト先が見つかりません</p>
<?php endif; ?>
</body>
</html>

以上で NewsDigest からシェアされたリンクに安心してアクセスできる。


Windows で PNG 画像を最適化するドロップレットを作る

PNG 画像の画質をあまり落とさずにファイルサイズを最適化するサービスとして TinyPNG というものがある。画像形式や内容によって最適な圧縮をかけてくれるところは便利だが、いちいちダウンロードして再配置する必要があるので手間がかかる。
私が主に TinyPNG を使用したい場面は、アルファチャンネルを持つ PNG 画像を PNG-24 から PNG-8 にしたい場合だ (現在の Photoshop ではアルファチャンネルを持つ PNG-8 の出力に対応していないため)。そこでアルファチャンネルを持つ PNG-8 の出力のみに対応したドロップレットを作ることにした。

ドロップレットの作成

TinyPNG では内部的に pngquant というツールが使われているようなので、これを用いる。

まずこのページから「Binary for Windows (v2.7.2)」をダウンロードし、適当なディレクトリに展開する。
そして pngquant.exe と同じディレクトリに Optimize PNG.bat という名前でファイルを作成する。内容は付属の Drag PNG here to reduce palette automatically.bat を参考にした。

@echo off

set path=%~d0%~p0

:start

"%path%pngquant.exe" --force --verbose --ordered --speed=1 --quality=50-90 --ext "-new.png" %1
rename %1 "%~n1.orig%~x1"
rename "%~d1%~p1%~n1-new%~x1" "%~n1%~x1"

shift
if NOT x%1==x goto start

最後に使いやすい場所、たとえばデスクトップに、このバッチファイルへのショートカットを作成する。アイコンや名前を変えておくと分かりやすい。

ドロップレットの使い方

PNG 画像を最適化するには、ファイルをドロップレットにドラッグ & ドロップする。
すると元のファイルは 〜.orig.png という名前に変更され、最適化されたファイルが元のファイル名となる。
ファイルは複数選択できるが、ディレクトリ内のすべての画像を処理する、といった使い方はできない。また PNG 以外のファイルを指定した場合、もとのファイルの名前を変更してしまう。今後の課題としたい。

オープンソースの家計簿 Economizzer をレンタルサーバにインストールして使う

Economizzer は PHP + MySQL で動作する、オープンソースの家計簿 Web アプリ。集計のグラフがちょっとかっこいい。

機能はいたってシンプルで次のことができる。

  • 収入・支出項目の追加・変更・削除・検索
  • 費目カテゴリの管理
    カテゴリは必ず2層にする必要がある(たとえば「教養娯楽費 > 旅行代」というカテゴリを設定して、費目には必ず下層の「旅行代」を設定する)
  • ログイン認証
  • ユーザの追加
    複数のユーザを作成し、1ユーザにつき1家計簿のみ管理できる
  • レスポンシブデザインで、スマートフォンタブレット、PC どの端末からでも使いやすい
  • CSV, TSV, Excel 形式でのエクスポート

現金や銀行口座といった資産の管理機能はないので、月々のお金の流れだけ分かればいいという方におすすめ。

Economizzer のインストール

パッケージは用意されていないので Github から開発版をダウンロードする。現在でも更新が続けられているようで、以下の内容は変更されている可能性がある。
作者によるドキュメントに従い、次のコマンドを実行する。

git clone https://github.com/gugoan/economizzer.git
cd economizzer
composer global require "fxp/composer-asset-plugin:~1.1.1"
composer install

このままだとエラーが出たので少し調べてみた。--no-plugins オプションを試してみるとうまくいくようだ。

composer global require "fxp/composer-asset-plugin:~1.1.1" --no-plugins
composer install

次に MySQL のユーザとテータベースを作成する。

mysql> CREATE USER economizzer_user@localhost;
mysql> SET PASSWORD FOR economizzer_user@localhost=PASSWORD('パスワード');
mysql> CREATE DATABASE economizzer DEFAULT CHARACTER SET utf8;
mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER ON economizzer.* TO economizzer_user@localhost;
mysql> FLUSH PRIVILEGES;

続いて economizzer/config/db.php を編集し、データベースの設定を行う。

<?php
return [
    'class' => 'yii\db\Connection',
    'dsn' => 'mysql:host=127.0.0.1;dbname=economizzer',
    'username' => 'economizzer_user',
    'password' => 'パスワード',
    'charset' => 'utf8',
    'enableSchemaCache' => true,
];

データベース定義と初期データを設定するため、次のコマンドを実行する。

./yii migrate

なお、このスクリプトで初期化できる RDBMSMySQL のみなので注意。
ここまでの手順が完了すれば Economizzer が使えるようになる。設定した URL を開き、User: joe / Password: 123456 でログインするとテストデータを見ることができる。

レンタルサーバに設置する

Economizzer は Github からクローンしてきた状態ではデバッグデータを残すようになっている。サーバに設置する際には economizzer/web/index.php の次の2行をコメントアウトする。

defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');

日本語化する

見慣れた日本語の方が良かったので、次のファイルを変更して部分的に翻訳した。興味がある方は全てのメッセージを完全に訳して本家にコミットしてほしい。

  • config/web.php(選択できる言語を追加する)
  • messages/ja/app.php(他の言語からコピー)
  • messages/ja/user.php(他の言語からコピー)
  • views/cashbook/index.php(日付フォーマットを Yii::t() に通す)
  • views/cashbook/view.php(日付フォーマットを Yii::t() に通す)
  • views/user/register.php(選択できる言語を追加する)

views/cashbook/index.php, view.php において日付をローカライズするヒント。「’16 7/6」という表示にしたくて DataView::widget() のパラメータで format を ['date', '’y n/j'] のようにすると、年の値が想定している下二桁ではなく4桁で表示されてしまう。スマートではないが次の書き方で対処した。

<?php
...
[
    'value' => Yii::$app->formatter->asDate($model->date, 'php:'.Yii::t('app', 'F j, Y')),
],
...

SQLite3 で動かす

レンタルサーバでは MySQL のデータベースをバックアップすることに不安があるので、代わりに SQLite3 を使い、定期的にデータベースファイルをバックアップすることにした。
MySQL から SQLite3 へのデータ移行は次のページを参考にした。

また、Economizzer では SQL 文を手動で生成している部分があるが、ここに MySQL 独自の関数が含まれているため、一部うまく動作しないところがあった。ここはデータベースシステムによって振り分けることにする。
まずは PHP で SQLite3 を使っているかどうかを判定する関数を用意する。

function UU_isSQLite3() {
	global $config;
	static $isSQLite3;
	if (!isset($isSQLite3)) {
		$isSQLite3 = strpos($config['components']['db']['dsn'], 'sqlite:') === 0;
	}
	return $isSQLite3;
}

そして、MySQL 独自の関数を使用している controllers/DashboardController.php において、判定関数の結果によりクエリを振り分ける。
関数の書き換えは次のようにした。日付関数はデータベースシステムによりまちまちだが、IF 関数に関しては SQL99 標準の CASE 文に置き換えることで MySQL にも対応できる。

MONTH(date) = $thismonth AND YEAR(date) = $thisyear
→ STRFTIME('%Y-%m', date) = '$thisyear-$thismonth'

MONTHNAME(date)
→ STRFTIME('%m', date)

YEAR(date) = $thisyear
→ STRFTIME('%Y', date)

IF(cashbook.type_id = 1, value, 0)
→ CASE WHEN cashbook.type_id = 1 THEN value ELSE 0 END

リバースアダプタと標準ズームで超マクロの世界

6月に EOS M シリーズ用のマクロレンズ EF-M28mm F3.5 マクロ IS STM が発売される。小型で130gと軽量ながら、レンズ単体でマクロモードに切り替えれば1.2倍までの高倍率撮影ができる優れもの。

ちなみに EOS M3 は以前購入を検討したものの、詳しく調べてみると電子シャッターがない。楽器演奏中の撮影を想定していたため購入を見送った。というわけで魅力的なレンズながらカメラ本体がない。

しかしながら、ほとんどのマクロレンズにはだかる「等倍」の壁を破り1.2倍の倍率を実現したことは賞賛したく、また等倍より先の世界を見たくもある。

リバースアダプタ

そこで使ったのが、リバースアダプタと呼ばれる、フィルタ枠を利用してレンズを逆向きに取り付けるための器具。専門的にはレトロフォーカスというタイプのレンズを反転させることで高倍率撮影が行えることが知られているが、それを簡単にカメラに装着することができるようにしたものだ。

リバースアダプタはケンコーのもの。レンズは初めて買った EOS Digital のキットとして付いてきた EF-S18-55mm F3.5-5.6 USM を使用した。なかなか良いらしい。

左がリバースアダプタ、右が装着するレンズ
リバースアダプタを装着したところ
試しに広角端の 18mm にズームリングを回し、定規を撮影して倍率を求めてみた。3.3倍。

参考にしたページでは4.6倍とあったので、もしかしてと思い、フォーカスリングを回してレンズの全長が最も長くなるように設定した。すると、このページとほぼ同じ4.5倍で撮影することができた。

また、中間リング(キヤノンではエクステンションチューブという長ったらしい名前で呼ぶ)を 12mm, 25mm と接続することで、6.8倍までの高倍率撮影をすることができた。

フックシーというエアプランツの葉

絞り込む

露出はマニュアルまたは絞り優先モードで行うのだが、何枚か撮影すると被写界深度が浅すぎて扱いづらいことに気付いた。しかし電子接点がないと絞れないこのレンズでは絞ることができない……と思いきや、絞りを固定する方法があるそうな。

  1. レンズを普通に装着する
  2. 任意の絞りを設定し、絞り込みボタンを押す
  3. 絞り込みボタンを押したままレンズを外す(カメラの電源は入れたまま!)

この方法で絞り込むことができた。実絞りなのでファインダー像が暗くなるのが扱いにくいので、そのうち電子接点に配線してカメラ側から絞れるようにしたい。

ミモザの花:つぼみ
ミモザの花:開花
ミモザの花:全開
ミモザの葉

ライティングについて

ちなみに高倍率撮影では光量が極端に不足するので、ストロボを使って撮影している。ストロボはびっくりするほど近づける。

すべてテーブルに肘や手をつきながらの手持ちだが、400mm のレンズをつけた場合ほどブレは激しくない。ISO 100, 1/100 秒でシャッターを切れる。

対象がとってもちっこいので試行錯誤も偶然の産物も楽しい。ゴミ袋なんかをディフューザーとして使っている。

Google Analytics API を PHP から使う

はじめに

この記事は、次の記事を現在(2016年4月)の Google Analytics およびその API の実情に合わせて書き直したものである。

人気記事ランキングを作成する処理等、ユーザが操作を介することのないバックエンドのプログラムが Google Analytics API にアクセスする方法について説明している。

1. API を利用可能にする

前準備として、Google Developer Console にアクセスして API を有効にする必要がある。

Google Developer Console を開き、プロジェクトを選択するか、無ければ作成する。

API の検索ボックスから「Analytics API」を検索し、名前をクリックすると API の詳細が表示されるので、その画面から有効にする。

2. API アカウントを作成し、キーを取得する

プロジェクトを作成・選択したら、「認証情報」をクリックして API にアクセスするための「サービス アカウント」を作成する。

認証情報が作成されていない場合は次のような画面になるので、「認証情報を作成」→「サービス アカウント キー」を選択する。

サービス アカウント キーの作成という画面になるので、「Service account name」にサービス名を入力する。サービス名を入力すると「サービス アカウント ID」は自動入力されるので、変更が必要なければそのままでよい。そして「キーのタイプ」は必ず「P12」を選択するGoogle APIs Client Library for PHP が P12 形式にしか対応していないためだ。

サービス アカウントを作成すると、自動的にキーのダウンロードが始まる。この *.p12 ファイルを保存しておく。
次にサービス アカウント ID を確認する。サービス アカウント ID はメールアドレス形式の ID で、「サービス アカウントの管理」から確認できる。

3. Analytics へのアクセス権限を API アカウントに付与する

前項で作成したサービス アカウントが Analytics のデータにアクセスできるよう、権限を付与する必要がある。

Google Analytics にアクセスし「アナリティクス設定」を開く。

アクセスを許可したいアカウントを選択し、「ユーザー管理」を開く。そこに「権限を付与するユーザー」という項目があるので前項で作成した「サービス アカウント ID」を入力する。

権限は「表示と分析」のままでよい。ここで「追加」を行うと、API から Analytics のデータを取得することができるようになる。

4. Google APIs Client Library for PHP を設定する

PHP から API を利用するために、Google APIs Client Library for PHP をインストールする。

PHP のパッケージマネージャ Composer を使用する場合は、ライブラリを灰位置するディレクトリに次の内容の composer.json を作成し、composer install コマンドを実行する。

{
    "require": {
        "google/apiclient": "1.1"
    }
}

5. API を使い Analytics データを取得する

準備が整ったら API からデータを取得してみよう。次のコードは過去 30 日分を対象に、サイト内(ビュー)でよくアクセスされているページのタイトル、URL、セッション数を取得するものである。

その他、取得できる主なデータは次項で説明している。

<?php
require_once 'my_google_analytics.php';

$ga = MyGoogleAnalytics::get();
$result = $ga->data_ga->get(
	'ga:XXXXX', // XXXXX の部分は Analytics のビュー ID
	'30daysAgo', // 開始日 ("2016-04-04" という指定もできる)
	'today', // 終了日
	'ga:sessions', // 主要指標 (metrics)
	[
		'dimensions' => 'ga:pageTitle,ga:pagePath', // 副指標
		'sort' => '-ga:sessions', // - を付けると降順ソート
		'max-results' => 10, // 取得件数
	]);

// 2次元配列として取得できるのでこれを加工する
print_r($result->getRows());

ここで読み込んでいる my_google_analytics.php の内容は次の通り。サービス アカウント ID と秘密鍵 (P12) ファイルを指定するだけですぐに使えるようになる。

<?php
// Composer により作成された verndor/autoload.php を読み込む
require_once 'path/to/vendor/autoload.php';

class MyGoogleAnalytics {

	// サービス アカウント ID
	const SERVICE_ACCOUNT_ID = 'XXXXXXXXXXXXXXXX@XXXXXXXXXXX.iam.gserviceaccount.com';

	// 秘密鍵のファイル名
	const KEY_FILE = 'path/to/XXXXXXXX.p12';
	
	private static $client;
	private static $service;

	public static function get() {
		if (!isset(self::$client)) {
			self::createClient();
		}
		if (!isset(self::$service)) {
			self::createService();
		}
		return self::$service;
	}

	private static function createClient() {
		$client = new Google_Client();

		// もしサーバの /var/tmp に書き込めないなどの事情がある場合は
		// ここでキャッシュファイルを生成するディレクトリを指定する
		//$client->setClassConfig('Google_Cache_File', ['directory' => __DIR__ . '/tmp']);

		// アプリ名。どこで使うのかは不明
		$client->setApplicationName('My Access Analysis');

		$cred = new Google_Auth_AssertionCredentials(
			self::SERVICE_ACCOUNT_ID,
			[Google_Service_Analytics::ANALYTICS_READONLY],
			file_get_contents(self::KEY_FILE)
		);
		$client->setAssertionCredentials($cred);
		if ($client->getAuth()->isAccessTokenExpired()) {
			$client->getAuth()->refreshTokenWithAssertion($cred);
		}
		
		self::$client = $client;
	}

	private static function createService() {
		self::$service = new Google_Service_Analytics(self::$client);
	}
}

6. 取得できるデータの種類

指標 metrics および dimension で指定できる項目はこのページで参照できる。

主な指標を挙げておく。

  • Metrics
    • ga:sessions セッション数
    • ga:users ユーザ数
    • ga:pageviews ページビュー
  • Dimensions
    • ga:userType 新規訪問/リピート訪問 (例: New Visitor, Returning Visitor)
    • ga:medium 参照元の種類 (例: organic, referral, (none))
    • ga:source 参照元 (例: google, yahoo, (direct), example.com)