tohokuaikiのチラシの裏

技術的ネタとか。

Google AnalyticsのデータをAPI経由で取り出すとか

Google AnalyticsのデータをAPI経由で取り出すとかできるらしい。しかも随分と昔から…Version4ってどんだけ昔からだったんだという…

これに従ってやっていきます。

Google Cloud Platformでプロジェクトを作る

最近のGoogleはみんなGoogle Cloud Platformですわー。

f:id:tohokuaiki:20200114181217p:plain

アカウントの追加

でこのプロジェクトを操作できるアカウントを追加する。

f:id:tohokuaiki:20200114181604p:plain

ときて

f:id:tohokuaiki:20200114181703p:plain

サービス アカウント名とサービス アカウントの説明を入力して作成。

f:id:tohokuaiki:20200114181855p:plain

で、このアカウントにロールを付与。とりあえずオーナー入れておけばいいんじゃないのっていう。

f:id:tohokuaiki:20200114181943p:plain

で、キーの作成→JSONでキーファイルがダウンロードされる。

f:id:tohokuaiki:20200114182035p:plain

これでアカウント追加完了。

Google Analyticsの管理画面で先ほど作成したアカウントに権限を与える

GoogleAnalyticsの管理画面に行き、ビューを選び「管理」の所から「ユーザー管理を表示」を選択。

f:id:tohokuaiki:20200114182451p:plain

右上の「+」から先ほど作ったアカウントのメールアドレスを入れる。権限は、「表示と分析」だけでいいでしょう。

f:id:tohokuaiki:20200114182634p:plain

PHPのライブラリをインストールして、サンプルを動かす

ファイルの設置

PHPが使えるサーバーに入って、コマンドラインからcomposerを使ってGoogleが提供してるPHPライブラリを

composer require google/apiclient:^2.0

とインストールする。

既にGoogleがサンプルを用意してくれてるので、ここから取得。HelloAnalytics.phpというファイルが得られるのでサーバーに送る。

アカウントのセットアップ

先ほどの「キーを作成」で取得したjsonファイルを service-account-credentials.json としてHelloAnalytics.phpと同じところにアップする。

HelloAnalytics.phpの44行目当たりにある、

<?php
function getReport($analytics) {

  // Replace with your view ID, for example XXXX.
  $VIEW_ID = "<REPLACE_WITH_VIEW_ID>";

の<REPLACE_WITH_VIEW_ID>を取得する。https://ga-dev-tools.appspot.com/account-explorer/?hl=ja に移動してIDを取得する。

この作業をしたら、HelloAnalytics.phpを動かす。

$ php HelloAnalytics.php
PHP Fatal error:  Uncaught Google_Service_Exception: {
  "error": {
    "code": 403,
    "message": "Analytics Reporting API has not been used in project 32xxxxxxx8237 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/analyticsreporting.googleapis.com/overview?project=329609518237 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.",
    "errors": [
      {
        "message": "Analytics Reporting API has not been used in project 32xxxxxxx8237 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/analyticsreporting.googleapis.com/overview?project=329609518237 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.",
        "domain": "usageLimits",
        "reason": "accessNotConfigured",
        "extendedHelp": "https://console.developers.google.com"
      }
    ],
    "status": "PERMISSION_DENIED"
  }
}

…と、エラーが出る。

URLが表示されるので、アクセスすると

f:id:tohokuaiki:20200114184136p:plain

とでるので、「有効にする」を押す。

…と、ここまでで動くようになったけど、なんかデータ取れなかった。

なんか、サンプルのHelloAnalytics.phpがおかしい

このgetReportが

<?php
function getReport($analytics) {

  // Replace with your view ID, for example XXXX.
  $VIEW_ID = "12345678";

  // Create the DateRange object.
  $dateRange = new Google_Service_AnalyticsReporting_DateRange();
  $dateRange->setStartDate("7daysAgo");
  $dateRange->setEndDate("today");

  // Create the Metrics object.
  $sessions = new Google_Service_AnalyticsReporting_Metric();
  $sessions->setExpression("ga:sessions");
  $sessions->setAlias("sessions");

  // Create the ReportRequest object.
  $request = new Google_Service_AnalyticsReporting_ReportRequest();
  $request->setViewId($VIEW_ID);
  $request->setDateRanges($dateRange);
  $request->setMetrics(array($sessions));

  $body = new Google_Service_AnalyticsReporting_GetReportsRequest();
  $body->setReportRequests( array( $request) );
  return $analytics->reports->batchGet( $body );
}

ってなってるけど、この記事を見ると $dimentionと$orderbyを入れているし…

<?php
function getReport($analytics) {

  // Replace with your view ID, for example XXXX.
  $VIEW_ID = "88097852";
//  $VIEW_ID = "52492899";

  // Create the DateRange object.
  $dateRange = new Google_Service_AnalyticsReporting_DateRange();
  $dateRange->setStartDate("7daysAgo");
  $dateRange->setEndDate("today");

  // Create the Metrics object.
  $sessions = new Google_Service_AnalyticsReporting_Metric();
  $sessions->setExpression("ga:sessions");
  $sessions->setAlias("sessions");

  $dimention = new Google_Service_AnalyticsReporting_Dimension();
  $dimention->setName( 'ga:landingPagePath' );

  $orderby = new Google_Service_AnalyticsReporting_OrderBy();
  $orderby->setFieldName( "ga:sessions" );
  $orderby->setOrderType( "VALUE" );
  $orderby->setSortOrder( "DESCENDING" );

  // Create the ReportRequest object.
  $request = new Google_Service_AnalyticsReporting_ReportRequest();
  $request->setViewId($VIEW_ID);
  $request->setDateRanges($dateRange);
  $request->setMetrics(array($sessions));

  $request->setDimensions( array( $dimention ) );
  $request->setOrderBys( $orderby );    

  $body = new Google_Service_AnalyticsReporting_GetReportsRequest();
  $body->setReportRequests( array( $request) );
  return $analytics->reports->batchGet( $body );
}

これでPHP実行するとパスごとのセッション数が取得できた。

certbotがまたこけてた

certbot renewすると

The requested apache plugin does not appear to be installed. Skipping.

とエラー出して更新してくれない。

ログ /var/log/letsencrypt/letsencrypt.log を見ると

020-01-12 09:32:53,247:WARNING:certbot.renewal:Attempting to renew cert (www.example.jp) from /etc/letsencrypt/renewal/www.example.jp.conf produced an unexpected error: The requested apache plugin does not appear to be installed. Skipping.
2020-01-12 09:32:53,286:DEBUG:certbot.renewal:Traceback was:
Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/certbot/renewal.py", line 452, in handle_renewal_request
    main.renew_cert(lineage_config, plugins, renewal_candidate)
  File "/usr/lib/python3/dist-packages/certbot/main.py", line 1187, in renew_cert
    installer, auth = plug_sel.choose_configurator_plugins(config, plugins, "certonly")
  File "/usr/lib/python3/dist-packages/certbot/plugins/selection.py", line 237, in choose_configurator_plugins
    diagnose_configurator_problem("authenticator", req_auth, plugins)
  File "/usr/lib/python3/dist-packages/certbot/plugins/selection.py", line 341, in diagnose_configurator_problem
    raise errors.PluginSelectionError(msg)
certbot.errors.PluginSelectionError: The requested apache plugin does not appear to be installed

2020-01-12 09:32:53,286:ERROR:certbot.renewal:All renewal attempts failed. The following certs could not be renewed:
2020-01-12 09:32:53,287:ERROR:certbot.renewal:  /etc/letsencrypt/live/www.example.jp/fullchain.pem (failure)
2020-01-12 09:32:53,287:DEBUG:certbot.log:Exiting abnormally:
Traceback (most recent call last):
  File "/usr/bin/certbot", line 11, in <module>
    load_entry_point('certbot==0.31.0', 'console_scripts', 'certbot')()
  File "/usr/lib/python3/dist-packages/certbot/main.py", line 1365, in main
    return config.func(config, plugins)
  File "/usr/lib/python3/dist-packages/certbot/main.py", line 1272, in renew
    renewal.handle_renewal_request(config)
  File "/usr/lib/python3/dist-packages/certbot/renewal.py", line 477, in handle_renewal_request
    len(renew_failures), len(parse_failures)))
certbot.errors.Error: 1 renew failure(s), 0 parse failure(s)

ってなってる。

Apacheのモジュールが必要って、何やねん…って思ったけど、 にあった通り、

apt install python3-certbot-apache

したら直った。

WordPressでの子テーマにおける正しいCSSの読み込み方

なんかイマイチな呼び出し方

子テーマ、便利ですよね。

で、ちょっと上書きしたいCSSを定義したいなーって思って「子テーマ CSS WordPress」あたりで検索するとこんな感じで出てきます。
このエントリで否定する例なので出してしまって申し訳ないのですが…

でも、これを例えばpenmanというテーマの子テーマを作って、子テーマのfunctions.phpに書いてやると、

<?php
add_action( 'wp_enqueue_scripts', function() {
    wp_enqueue_style( 'parent-style', get_template_directory_uri() . '/style.css' );
    wp_enqueue_style( 'child-style', get_stylesheet_directory_uri() . '/style.css', array('parent-style'));

<link rel='stylesheet' id='parent-style-css'  href='https://example.jp/wp-content/themes/penman/style.css?ver=5.3.2' type='text/css' media='all' />
<link rel='stylesheet' id='child-style-css'  href='https://example.jp/wp-content/themes/penman-child/style.css?ver=5.3.2' type='text/css' media='all' />
<link rel='stylesheet' id='penman-style-css'  href='https://example.jp/wp-content/themes/penman-child/style.css?ver=5.3.2' type='text/css' media='all' />

って、子テーマのCSSを2回読んでしまうんです。

何がイマイチかっていうと…

それくらいいじゃんっていう感じなのですが、CSSをアップデートしてキャッシュ対策をしたい場合

<?php
add_action( 'wp_enqueue_scripts', function() {
    wp_enqueue_style( 'parent-style', get_template_directory_uri() . '/style.css' );
    wp_enqueue_style( 'child-style', get_stylesheet_directory_uri() . '/style.css', array('parent-style'), '20200109');

とかしますよね。でも、出てくるHTMLは

<link rel='stylesheet' id='parent-style-css'  href='https://example.jp/wp-content/themes/penman/style.css?ver=5.3.2' type='text/css' media='all' />
<link rel='stylesheet' id='child-style-css'  href='https://example.jp/wp-content/themes/penman-child/style.css?ver=20200109' type='text/css' media='all' />
<link rel='stylesheet' id='penman-style-css'  href='https://example.jp/wp-content/themes/penman-child/style.css?ver=5.3.2' type='text/css' media='all' />

って、「それ、古いCSSのキャッシュで上書きしちゃってるじゃん!!!」って感じなんですよね。バージョンをアップしてもひたすら意味ないです。

ということで、正しい子テーマのCSS読み方は

<?php
add_action( 'wp_enqueue_scripts', function() {
    wp_enqueue_style( 'parent-penman-style', get_template_directory_uri() . '/style.css' , array(), '20200109-p');
    wp_enqueue_style( 'penman-style', get_stylesheet_directory_uri() . '/style.css', array('parent-penman-style'), '2020010p-c');

ってします。このwp_enqueue_styleの2行は前後どっちでもいいですが、2行目の依存関係が分かりやすいようにしています。

解説

1行目のwp_enqueue_styleで親テーマのCSSを読みこみます。この時、名称は親テーマのCSS名称と同じにしてはいけません。

2行目のwp_enqueue_styleで子テーマのCSSを読みこみます。この時、名称は親テーマのCSS名称と同じにします。更に、依存性で1行目の親テーマのCSSを先に指定することで親テーマのCSSの後でロードされます。

問題があるとすると

親テーマを更新した際に、この子テーマのfunctions.phpのバージョン設定「20200109-p」を新しい文字にしないといけない所ですね。

まあ、面倒なら、親テーマのstyle.cssのタイムスタンプかmd5でも付けちゃえばいいのでは。

Debitn10でCertbotがこける

どう考えてもPythonが悪い。しかし、今気づいてよかった。

# /usr/local/bin/certbot-auto
Upgrading certbot-auto 0.31.0 to 1.0.0...
Replacing certbot-auto...
Error: couldn't get currently installed version for /opt/eff.org/certbot/venv/bin/letsencrypt:
Traceback (most recent call last):
  File "/opt/eff.org/certbot/venv/bin/letsencrypt", line 7, in <module>
    from certbot.main import main
  File "/opt/eff.org/certbot/venv/local/lib/python2.7/site-packages/certbot/main.py", line 5, in <module>
    import logging.handlers
  File "/usr/lib/python2.7/logging/__init__.py", line 26, in <module>
    import sys, os, time, cStringIO, traceback, warnings, weakref, collections
  File "/usr/lib/python2.7/weakref.py", line 14, in <module>
    from _weakref import (
ImportError: cannot import name _remove_dead_weakref

そもそも最近までDebian7だったのでもう思い当たる節がありまくり。

パッケージインストールでは無かったと思うのが、debian10にはパッケージがあるようだ。ということで

# apt install certbot

で、何か入ったっぽい。

…が、which certbox-autoしても /usr/local/bin/certbot-auto という以前入れたのしか出ない。

当然これを実行しても同じエラーが出る… ということで、検索

# find / -type f -name "certbot-auto"

…何も出ない。

おそらく、/usr/local/bin/certbot-autoがあったままだと正常にcertbot-autoがインストールされないのでは…と思い、これを削除して再インストール。とりあえず動いた…。SSL更新できないみたいなエラー出てるけど、エラーメッセージ出てるだけ相当マシ。

WordPressのXMLRPCで自動投稿をPHPスクリプトから行う

なんか、昔に死ぬほどやったけど忘れてしまった…自分でAPIのメソッドを作ったりしたんだけどな。

そもそも時代も変わったので生のPHPのXMLRPCとか触りたくないのでライブラリを使う。よさげなのがあった。

とりあえずパッケージインストール

composer.pharは使えるとして…

./composer.phar init
./composer.phar require hieu-le/wordpress-xmlrpc-client
./composer.phar require monolog/monolog

使い方

APIドキュメントがあったんだけど、これがもう全然正しくなくて…ってかこれ、ソースのコメントから作ってるんだろうけど引数が全然違うしね。
http://letrunghieu.github.io/wordpress-xmlrpc-client/api/index.html

ということで、ソースコードWordPressAPIレファレンス見て頑張った。
https://codex.wordpress.org/XML-RPC_WordPress_API

で、import.phpとかで

<?php
require_once 'vendor/autoload.php';
$endpoint = 'http://your-wordpress-site/xmlrpc.php';
$username = 'your_name';
$password = 'your_password'; 
$client = new \HieuLe\WordpressXmlrpcClient\WordpressClient();
$client->setCredentials($endpoint, $username, $password);
$wpLog = new Monolog\Logger('wp-xmlrpc');
$client->onError(function($error, $event) use ($wpLog){
    $wpLog->error($error, $event);
});

ここまでは共通

新しい投稿

<?php
$client->newPost('新しいタイトル ',  "内容をここに書く",
                 array(
                     'post_type' => 'post',
                     'post_status' => 'publish',
                     'terms_names' => array(
                         'category' => array('お知らせ','カテゴリー2'),
                         ),
                     'post_status' => 'private',
                     'post_author' => 1,
                     'post_date' => date('c', time() - 86400 * 8), //
                     'comment_status' => 'closed',
                     'ping_status' => 'closed',
                     ));

こんな感じ。

カテゴリーの設定が難しかったけど、WordPressのclass-wp-xmlrpc-server.phpを覗いて理解した。

                     'terms_names' => array(
                         'category' => array('お知らせ','カテゴリ―2'),
                         'post_tag' => array('タグ1', 'タグ2'),
                         'post_format' => // WordPressのpost_format使ったことないのでよくわからない。
                         ),

という感じ。IDを使いたかったら、terms_namesの代わりにtermsで。

うーん、これは楽だ。

画像のアップロード

よーし、画像のアップロードも…

<?php
$upfile = '/home/itoh/photos/1270.jpg';
$client->uploadFile('写真.jpg',
                    mime_content_type($upfile),
                    file_get_contents($upfile),
                    true,
                    80
                    );

最初、ファイル名に拡張子の.jpgを付けずに「写真」とだけしてたらアップロードできずになんでだーとなってた。

と、なんか、上書きできないなーって思ったら…
https://codex.wordpress.org/XML-RPC_WordPress_API/Media#wp.uploadFile

bool overwrite: Optional. Has no effect (see 17604).

ん~~と、リンク先チケットを見ると…私の4年前の書き込みが…直ってないのか…使われないんだろうな…

しかし、これキャプションを付けられないものかな…
…とそこで気付いた。WordPressは写真も1つのPOSTとして保存しているのである。

post_content => 説明の欄  
post_excerpt => キャプションの欄  
_wp_attachment_image_alt => ALTテキスト  

となっている。

ちなみにパーマリンクになる日付は、所属している投稿の時刻である。意味付けとしてはこれを写真の時刻とすればいい。