tohokuaikiのチラシの裏

技術的ネタとか。

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でも付けちゃえばいいのでは。

Debian10でCertbotがこける

先月、サーバーをdebian10に変更した。

しばらく何の問題も無かったが、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がインストールされないのでは…と思い、これを削除(rm /usr/local/bin/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テキスト  

となっている。

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

Apacheのmod_cacheが効かなかったので色々と調べた件

WordPressの負荷軽減にmod_cacheを使おうと思った。

で、こんな感じ

# a2enmod cache_disk
# systemctl start apache-htcacheclean
# systemctl restart apache2

で、/etc/apache2/site-availables/vhosts.confに

CacheRoot /tmp/apache/cache
CacheIgnoreCacheControl On
CacheIgnoreHeaders Set-Cookie
CacheEnable disk /
CacheDefaultExpire 300

を加える。/tmp/apache/cacheは、Apacheユーザーにするか777に。

ところが、効かない…

キャッシュされる感じが全くない。/tmp/apache/cacheにもファイルができない。

ん~~~~~~と、ログ見たりapacheの mod_cache の設定を復習した - うまいぼうぶろぐ見たりして気付いた。

Cacheが有効になるのは以下の条件がある。 http://httpd.apache.org/docs/2.4/caching.html

What Can be Cached?
The full definition of which responses can be cached by an HTTP cache is defined in RFC2616 Section 13.4 Response Cacheability, and can be summed up as follows:

  • Caching must be enabled for this URL. See the CacheEnable and CacheDisable directives.
  • If the response has an HTTP status code other than 200, 203, 300, 301 or 410 it must also specify an "Expires" or "Cache-Control" header.
  • The request must be a HTTP GET request.
  • If the response contains an "Authorization:" header, it must also contain an "s-maxage", "must-revalidate" or "public" option in the "Cache-Control:" header, or it won't be cached.
  • If the URL included a query string (e.g. from a HTML form GET method) it will not be cached unless the response specifies an explicit expiration by including an "Expires:" header or the max-age or s-maxage directive of the "Cache-Control:" header, as per RFC2616 sections 13.9 and 13.2.1.
  • If the response has a status of 200 (OK), the response must also include at least one of the "Etag", "Last-Modified" or the "Expires" headers, or the max-age or s-maxage directive of the "Cache-Control:" header, unless the CacheIgnoreNoLastMod directive has been used to require otherwise.
  • If the response includes the "private" option in a "Cache-Control:" header, it will not be stored unless the CacheStorePrivate has been used to require otherwise.
  • Likewise, if the response includes the "no-store" option in a "Cache-Control:" header, it will not be stored unless the CacheStoreNoStore has been used.
  • A response will not be stored if it includes a "Vary:" header containing the match-all "*".
  • CacheEnableとCacheDisable の指定
  • HTTPステータスが200/203/300/410以外なら"Expires" or "Cache-Control"で指定する
  • GETメソッドであること
  • Authorizationヘッダーが含まれる場合(Basic認証など)、Cache-Controlヘッダーにs-maxage/must-revalidate/publicが入っていること
  • Queryパラメータがある場合は、Expiresヘッダがで指定するかCache-Controlヘッダでs-maxageかmax-ageが入っていること
  • "Etag"か"Last-Modified"か"Expires"が入っていること。または、 max-age or s-maxageが入っているCache-Controlヘッダがあること。ただし、CacheIgnoreNoLastMod を使っている場合は除く
  • Cache-Controlでprivateを指定している場合、CacheStorePrivate オプションが有効になっていること。
  • Cache-Controlでno-storeを指定している場合、CacheStoreNoStore オプションが有効になっていること。
  • Vary: *があるとキャッシュされない

キャッシュされなかった理由は

Basic認証のページを見ていたから。

正確には、

  1. Basic認証のページを開く
  2. httpd.confでBasic認証を解除
  3. Apache再起動
  4. ブラウザでアクセス

としてたんだけど、ブラウザはBasic認証の情報を自動送信するのでブラウザを再起動させなければRequestHeaderにAuthorizationがそのまま記憶されて送られてしまうんだな。ハマった。

上記には、「 If the response contains an "Authorization:" header,」ってあるけど、これ「If the request」の間違いじゃないかな?文中には

Requests with an "Authorization" header (for example, HTTP Basic Authentication) are neither cacheable nor served from the cache when mod_cache is running in this phase.

ともあって、Basic認証を掛けている場合はキャッシュされない模様。

なんか、全部同じページになるんだけど…

なんだこれは…違うURLにアクセスしても全部同じコンテンツになっとる…

これか。

RewriteRule . /index.php [L]

RewriteRule . /index.php?cache_key=%{REQUEST_URI} [QSA,L]

にする。

しかし、なんかまだchromeでアクセスしたときにDeveloper Toolsを開けてるとキャッシュを取りに行き、そうでないとキャッシュ使わないとか挙動が不穏…

ということで、とりあえず現状で

.htaccess

RewriteRule . /index.php?cache_key=%{REQUEST_URI} [QSA,L]
#RewriteRule . /index.php [L]

テーマのfunctions.php

<?php
/* mod_cacheのための */
unset($_GET['cache_key']);
unset($_REQUEST['cache_key']);

// キャッシュ制御のためのLast-Modifiedヘッダ
add_action("template_redirect", function (){
    if( (is_home() || is_singular()) && !$_GET) {
        $span = 300;
        header('Expires: '. date('r', time() + $span));
        header('Cache-Control: public, max-age='.$span.', s-maxage='.$span);
        header(sprintf("Last-Modified: %s", get_the_modified_time("r")) );
    }
});

httpd.conf

    <IfModule mod_cache.c>
    <IfModule mod_cache_disk.c>
          CacheEnable disk /
          CacheRoot /tmp/var/apache2/cache
          CacheIgnoreHeaders Set-Cookie
          CacheIgnoreCacheControl On
          CacheIgnoreNoLastMod On
          CacheMaxExpire 600
          CacheDetailHeader On
    </IfModule>
    </IfModule>

うーん、CacheIgnoreCacheControl とかCacheIgnoreNoLastMod あたりはOffにしたいなぁ…とは思うけど、ブラウザ側の挙動に依存させたくないのでこれでいいか。

あと、CacheDetailHeader On これは良い。HTTPヘッダにキャッシュを使ったかどうかが分かる。そもそもサーバー側のキャッシュポリシーはCache-Controlヘッダで相手にはわかっているのでこれがユーザーに漏れたところで実害は無いと思う。

Debian10でApache2.4+PHP-fpmを動かす

PHP5.6を使いたい。参考にしたサイト

aptでパッケージ先を追加

普通にapt installできないので、https://packages.sury.org/php/ をパッケージ先に追加する。

GPG認証するパッケージを入れて、キーを追加して、設定ファイルを作る。

# apt install ca-certificates apt-transport-https 
# wget -q https://packages.sury.org/php/apt.gpg -O- | apt-key add -
# echo "deb https://packages.sury.org/php/ stretch main" | tee /etc/apt/sources.list.d/php.list

で、パッケージ情報を追加

# apt update
# apt upgrade

適当にPHP5.6周りをインストール

dpkg -l |grep php5.6 で出てきたのから適当に必要そうなのをインストール

#  apt install -y php5.6-mysql php5.6-mbstring php5.6-xml php5.6-zip php5.6-cli

PHP5.6がApacheからモジュールとして実行されないようにphp5.6はインストールしない。

次にphp5.6-fpmをインストール。さっきのと同時でもいいのだけど。

# apt install -y php5.6-fpm

次にPHPモジュールも入れる。PHPモジュールはFastCGIApache SAPI も同じである。当たり前か。

# apt install -y php5.6 php5.6-mysql php5.6-mbstring php5.6-xml php5.6-zip php5.6-cli

VirtualHostごとに実行するPHPを変更する。

php-fpm5.6がserviceとして起動しているのを確認。

# systemctl status php5.6-fpm.service

/etc/php/5.6/fpm/pool.d/www.conf を見ると、

listen = /run/php/php5.6-fpm.sock

Unixソケットで接続するようになっているので、sites-available/hogehoge.conf で

<VirtualHost *:80>
        <Directory /path/to/document/root>
            require all granted
        </Directory>
        <FilesMatch \.(html|php)$>
            SetHandler "proxy:unix:/run/php/php5.6-fpm.sock|fcgi://localhost"

としておく。HTMLとPHP拡張子で動く。

require all granted は、いつものように「あー、AH01797: client denied by server configuration」で403を何度も繰り返すため…何度設定しても1度はミスる。

Apacheのモジュールを適宜IN/OUTしておく

これやり忘れてて「SetHandlerのproxyが効いてくれない・・・」とハマりました。

 a2dismod mpm_prefork
 a2enmod mpm_event
 a2enmod proxy_fcgi

.htaccessphp_value, php_flagの書き方を変える。

php_value auto_prepend_file /path/to/document/root/auto_prepend.php
php_flag error_log Off

とあるのを

SetEnv PHP_VALUE "auto_prepend_file=/path/to/document/root/auto_prepend.php"
SetEnv PHP_FLAG "error_log=Off"

とかする。

またAccess Denied

ってエラーがindex.htmlにアクセスすると出る。

 AH01071: Got error 'Access to the script '/var/www/htdocs/index.html' has been denied (see security.limit_extensions)\n'

/etc/php/5.6/fpm/pool.d/www.conf で

security.limit_extensions = .php .html

として、/etc/init.d/php5.6-fpm restart で動いた。

で、またハマる

VirtualHostにして、他のサイトでinclude_pathやauto_prepend_fileを設定すると、他のサイトでもこの指定が残る。

んー、なんやねんこれは。

結局、

<VirtualHost>
SetEnv PHP_VALUE "auto_prepend_file=/path/to/document/root/auto_prepend.php"
</VirtualHost>

<VirtualHost>
SetEnv PHP_VALUE "auto_prepend_file="
</VirtualHost>

みたいにして逐一書き直す羽目になった…なんやねんこれは…