「自動修復を準備しています」で焦った

突如PCが起動しなくなり、焦りました。「自動修復を準備しています」っていうのは今までも極まれにありましたが、今までのパターンとは違って今回のはダメかも、と思いました。

作業を一区切りしてPCをシャットダウンしました。ですが、シャットダウンが完了する電源のリレー音が鳴ってからなんと再び起動を始めました。…!!!?って感じです。

再起動を始めて自動修復の画面が表示されるのですが、これが一瞬なのです。そして自動修復の画面ですが、初回から何回かは英語で表示されていたため、この時点でこの画面は何なのかよく分かりませんでした。Autoなんちゃらって表示されてるな…みたいな感じ。(実際に書いてある表示はPreparing automatic repair)

ディスプレイの信号は来ている状態で画面が真っ黒だったり、途中で信号が途絶えて来てなかったり、色んなパターンがありました。最後にフリーズした時は、自動修復の画面が表示された状態でフリーズしました。勿論ローダーは止まってて、ローダーを読み込んだ時に止まったのかローダーの背景が一部赤くおかしかったです。字では表し難いので画像を撮っておけば良かった…。

外出しようと思ってたので、その最後のフリーズで一区切りして、意味ないかもしれないけど電源のスイッチまでオフにして暫く放置しようと思いました。ダメならシステム修復ディスクを作成して回復を試みたり、それでもダメならデータだけ救出してOSをクリーンインストールしようと思いました。

2時間近く経って帰宅後電源のスイッチをオンにして電源ボタンを押して起動すると、自動修復画面は表示されず、何事もなかったかのように正常起動しました!実はこの電源スイッチオフ、何かと有効でRyzenにしてからちょくちょくやってます。

OSもそろそろ入れ替えなきゃいけないですね。Ryzenレビュー時に2年以上入れ替えてないって書いてたけど、それからまた経ってるので3年くらいは入れ替えてないんですね。その間、今のWindows 10は多分クリーンインストールではなく8.1からのアップグレードだと思いますし、CPUがRyzenになってもクリーンインストールすることなく、そのまま使ってるので、ゴミは溜まっているでしょう。昔は1年に1回は入れ替えてたと思うけど、最近は重くなったりする実感もなく面倒さの方が勝り、入れ替えなくなりました。


Acronis True Image 2017 - 1 Computer

Acronis True Image 2017 - 1 Computer

WordPressのデータベース(SQLite)を最適化するVBScript

先日WordPress化した記事を書きました。その時は書いていなかったのですが、実は通常のMySQLは使用せず、SQLiteを使って環境を構築しました。

SQLiteにした理由とメリット


WordPressMySQLを前提にしたシステムなのに、わざわざプラグイン(SQLite Integration)を使ってSQLiteを使う理由は、MySQLが使用不可のプランでもWordPressが使えるためです。当初はプラン変更して普通にMySQLで運用するつもりでしたが、ローカルでの作業中にSQLiteでもやろうと思えば出来ることを知って、SQLiteに移行しました。多くのアクセスがあったり、データベースが大きいサイトなら素直にMySQLが使えるプランに変更すれば良いと思うのですが、そうではないので費用対効果が悪く思いました。


SQLiteのデータベースは単体のファイルですので、バックアップや移植、差し替えが用意なのもメリットです。本番・テスト・ローカルの各環境でささっと移植が出来ます。

データベースの最適化


SQLiteをデータベースにすると、互換性がなく使えないプラグインがあるようです。特にデータベースを扱ったりキャッシュするようなものは、その可能性が高いようです。


ですので、プラグインなしで最適化出来ないかなぁと色々調べました。他にも方法はあるかもしれませんが、2つ見つけました。

リビジョンの削除


デフォルトでは履歴や自動保存が有効になっており、履歴が残る度にデータベースが肥大化します。


リビジョンは記事が保存されているテーブルのフィールド名がpost_typeの列に、「revision」という値で保存されおり判別出来ます。それをSQLコマンドで削除します。


因みにリビジョン機能や自動保存は以下をwp-config.phpやfunctions.phpに追加することでオフに出来ます。個人的にリビジョン機能は必要ないなと思い、最近オフにしました。潔癖症ではありませんが、いつの間にかどんどんIDが増えていってデータベースが大きくなるのが気になるのです。スクリプトを作ったけど、オフにしてしまったので、これに関してはあまり意味なかったかも(笑)

<?php
// リビジョン停止
// (wp-config.phpの「require_once(ABSPATH . ‘wp-settings.php’);」より前に記述)
define('WP_POST_REVISIONS', false);

// 自動保存停止 (functions.php)
function disable_autosave() {
	wp_deregister_script('autosave');
}
add_action( 'wp_print_scripts', 'disable_autosave' );
?>
reindex / vacuumコマンド


reindexとvacuumコマンドを実行して最適化します。SQLiteを採用しているFireFoxでも有効で、これでFireFoxが軽くなるといった情報がたくさんありました。


これらのコマンドはreindexは名前の通りインデックスの再構築、vacuumは空き領域の開放を行うようです。

上の2項目両方を実行することで大幅削減


自分のデータベースでは、リビジョンの削除、若しくはreindex / vacuumコマンドだけだと殆ど容量は減りませんでした。しかし両方を行うことで容量が60%程度になりました。


具体的なことは分かりませんが、データベースの仕様上データを削除しても容量は減らないけど、vacuumしたことで削除したリビジョン分の空き領域が開放されたからなのかなと思いました。


仮想OSの仕様にも似てますね。予め最大容量を確保しておくオプションもありますが、デフォルトでは最大容量を決めておいて、使用に応じて肥大化するようになってます。容量が増えてからデータを削除しても仮想OSの容量は減りません。クリーンアップをすることで容量が削減されます。

スクリプトの仕様・解説


以下スクリプトの内容です。

' wpdb-optimize.vbs
Option Explicit

'-----------------------------------------------------
' データベース名を設定。指定のファイル名以外は処理を行わない。
Const WP_DB_FILE_NAME = ".ht.sqlite"
' セキュリティのためにプレフィックスを変えてる場合は変更。
Const WP_DB_POSTS_TABLE_NAME = "wp_posts"
' 確認用のダイアログで表示するリビジョンレコードの数。
Const DISP_REVI_LIMIT_NUM = 50
'-----------------------------------------------------
Dim dbPath, wssn, reviData
Dim RS, con, fs, oArgs
Dim done, doReviDel, doCmd
Dim crNum, divPosNum, diffNum

done = False
doReviDel = False
doCmd = False
wssn = WScript.ScriptName

Set fs = CreateObject("Scripting.FileSystemObject")

Set oArgs = WScript.Arguments
If oArgs.Count = 0 Then WScript.Quit
dbPath = oArgs(0)
Set oArgs = Nothing

If fs.GetFileName(dbPath) <> WP_DB_FILE_NAME Then
	MsgBox "WP用のDB " & WP_DB_FILE_NAME & " をドラッグしてください。", vbExclamation, wssn
	WScript.Quit
End If

If MsgBox("リビジョンを削除しますか?", vbYesNo, wssn) = vbYes Then doReviDel = True
If MsgBox("reindex & vacuum コマンドを実行しますか?", vbYesNo, wssn) = vbYes Then doCmd = True

If Not doReviDel And Not doCmd Then WScript.Quit

Set con = CreateObject("ADODB.Connection")
con.Open "DRIVER=SQLite3 ODBC Driver;Database=" & dbPath & ";"

' リビジョン削除
If doReviDel Then

	On Error Resume Next
	Set RS = con.Execute("SELECT post_title FROM " & WP_DB_POSTS_TABLE_NAME &" WHERE post_type = 'revision';")
	If Err.Number = -2147467259 Then
		MsgBox "テーブルがありません。定数に正しいテーブル名を入力してください。", vbExclamation, wssn
	Else
		reviData = RS.GetString
		If Err.Number = 3021 Then
			MsgBox "リビジョンのレコードはありませんでした。", , wssn
		Else
			crNum = UBound(Split(reviData, vbCr))
			If crNum > DISP_REVI_LIMIT_NUM Then
				divPosNum = InStrCnt(reviData, vbCr, DISP_REVI_LIMIT_NUM)
				diffNum = crNum - DISP_REVI_LIMIT_NUM
				reviData = Left(reviData, divPosNum) & vbCrLf & "その他 " & diffNum & " 件"
			End If
			If MsgBox("以下の post_title のリビジョンを削除します。よろしいですか?" & vbCrLf & vbCrLf & reviData, vbYesNo, wssn) = vbYes Then
				con.Execute("DELETE FROM " & WP_DB_POSTS_TABLE_NAME &" WHERE post_type = 'revision';")
				done = True
			End If
		End If
	End If
	RS.Close
	Err.Clear
	On Error Goto 0

	Set RS = Nothing

End If

' reindex & vacuum コマンド
If doCmd Then
	con.Execute("vacuum;")
	con.Execute("reindex;")
	done = True
End If

con.Close
Set con = Nothing
Set fs = Nothing

If done Then MsgBox "最適化が完了しました。", , wssn

Function InStrCnt(ByVal strString, ByVal strSearch, ByVal lngCount)
	Dim num, i
	num = InStr(1, strString, strSearch)
	Do While num > 0
		i = i + 1
		If i = lngCount Then Exit Do
		num = InStr(num + 1, strString, strSearch)
	Loop
	InStrCnt = num
End Function


データベースをVBSファイルにドラッグしたり、コンテキストメニューの「送る」に登録しておいて、そこに投げます。ファイルは1ファイルのみです。


リビジョン削除とreindex / vacuumコマンドをそれぞれリクエストに応じて行います。初めにダイアログで処理を行うか確認し、Yesだったものだけ処理します。両方Noなら何も処理しません。


もし使用される場合は、バックアップの上自己責任でお願いします。

定数について


定数で任意の値を設定するようにしています。特に、テーブル名はセキュリティの関係上プレフィックスを変えていることがままあると思うので、変えている場合は変更します。


データベースのファイル名は、一応一意の名前でないと動作しないようにしました。


確認用のダイアログで表示するリビジョンレコードの数は、スクリプトでダイアログでしか表示出来ない仕様上、表示するレコード数を制限することにしました。件数が多いと、ディスプレイをはみ出してボタンが押せなくなってしまうのです。


もっとも、マウス位置でウィンドウ移動のようなツールを使用していたら一応移動して押せますが(笑) 自分はテスト中ボタンが押せなくなって、これでウィンドウを上の方に移動してボタンを押しました。


改行を全角スペースに変換してもっと表示出来るようにしようとも考えましたが、見辛かったのでやめました。自分のWUXGAのディスプレイでは50〜60件程度がちょうど良いように思いました。


ここで設定した数以上のレコードは省略して、その他◯件と表示するようにしました。

SQLite3 ODBC Driver


上記スクリプトはSQLite3 ODBC Driverというドライバーを使っており、これをインストールする必要があります。以下よりダウンロード出来ます。


http://www.ch-werner.de/sqliteodbc/

On Error Resume Next


リビジョンのレコードがなかった時に、処理が止まってしまうのでOn Error Resume Nextで先に進むようにしました。この時とテーブル名が間違っていた時のエラー処理を書きましたが、もし他にも何か起こった場合、On Error Resume Nextなのでエラーが表示されません。




SQLite ポケットリファレンス

SQLite ポケットリファレンス

静的サイトをWordPress化

今更感はあるのですが、管理サイトの一部をWordPress化しました。


タイトルは一応静的サイトとなってますが、厳密には静的サイトではありません。PHPSmartyを使って動的に処理して、WordPressでも分離されているようにヘッダーとかフッターなどは分けたりしてました。ただデータベースはなくて、大規模なシステムではなく、一応動的サイトではあるものの静的サイト寄りでした。今後サイト内部でページを増やそうと思ったのですが、そうなると自作の簡易システムでは難しいため、重い腰を上げてWordPressを導入してみました。


WordPressの知識が全くない0からのスタートだったので、固定ページという概念があることも知らなかったのですが、これがあるならさっさと導入すれば良かったと思ったりしました。ブログじゃないっぽいサイトでもソースを見るとWordPressで生成されてたりするので、一応そういったことも出来るんだろうとは思いつつも、どうしてもWordPressはブログのためのシステムというイメージがあったのです。


下位ディレクトリにWordPressをインストールしてこれまでのシステムと併用ということも手っ取り早いので考えましたが、この際一新しようと思いました。結果、そのようにして良かったと思います。結果的にWordPressの関数を多用して依存してるところがあり、これまでのシステムの部分でこれが使えないと辛いな、と思ったので。


WordPress化にあたって色々躓きましたが、その一部をメモ書きしておきたいと思います。

ショートコードでのテンプレート読み込み


静的サイトでは記事内でバナーなど定型的なサブコンテンツを挟んでました。WordPressにはget_template_partというテンプレートを読み込む関数があるので、それをショートコード経由で実行しようと思いました。記事内、つまり投稿画面なのでショートコードで実行する必要があります。


ところがそのようなショートコードを書いても、指定位置に来なくて記事内のトップに来ます。多分、記事内で順に処理をしていくのではなく、the_contentが実行されてから最初に全てのショートコードの処理を行い、その際にget_template_partが実行されているのではと思いました。ただWordPressの内部を見てないので、違っているかもしれません。


なので、元の簡易システムの時と同様、サブコンテンツのHTMLをヒアドキュメントの変数に格納することにしました。変数なら問題なく、指定位置に書き出されます。[insert_template template="sub_contents_A"]とかみたいにしてテンプレートを判別する文字列を渡して、functions.phpの方で条件分岐してHTMLを返します。

srcなどでショートコードが効かない


直感的にセキュリティで効かないようになっているのかなと思いましたが、やはりそのようです。add_filterでwp_kses_allowed_htmlという関数をフックして設定すると除外設定が出来るようです。以下のような感じです。

<?php
add_filter('wp_kses_allowed_html', 'my_wp_kses_allowed_html', 10, 2);
function my_wp_kses_allowed_html( $tags, $context ) {
	$tags['img']['src'] = true;
	return $tags;
}
?>


ただ自分の場合、上記でもうまく動作しない場合がありました。記事内で元々Smartyのif文でPCとモバイルで画像を分けている部分があったのですが、ショートコードではif文が使えません。そのため以下のサイトのコードを使用しました。


http://unguis.cre8or.jp/web/3518


それでsrc内でモバイルだったらそれ用の画像が読み込まれるようにファイル名の文字列を足す処理を書いてましたが、これがどうも弾かれてしまうようです。


だけど発想の転換というか、src内でif文が使えたらスマートだけど、ifは外に出してPCとモバイル用のimgタグ2つ書いて条件分岐すれば弾かれないんじゃないか、と思い浮かびました。やってみると、正解で無事動作しました。

挟む系のショートコードを使うとpタグのペアが崩れる


今のデザインではhタグの中にタグはありませんが、今後デザインの変更でspanタグなどを入れたい場合を考えて、ショートコードで書き出すようにしようと思いました。こうしておけば、デザイン変更などでspanタグが増えても、投稿画面での入力は[h3][/h3]などの記述のままで良く、楽です。そして一括で変更出来ます。直書きだと過去記事を置換処理しなければいけません。


編集しててある時ソース見て気付いたのですが、pタグがどうも閉じ忘れ状態になっているのです。pタグはWordPressの自動生成です。原因特定に悩みましたが、閉じ忘れの場所はhタグの付近であることから、ショートコードが原因かもしれないと思いました。


やはりショートコードが原因で、以下のサイトのコードで解決しました。


https://elearn.jp/wpman/column/c20141022_01.html


pタグが自動生成されるのはWordPressのwpautop関数によるものですが、これが実行される前に上記のようなショートコードを実行してしまおうというものです。wpautopが実行されてからショートコードが実行される場合、wpautopは[h3][/h3]をタグではなく単なる文字列として解釈する訳ですので、内部で辻褄が合わなくなるのも想像出来ます。


もしかしたら前記のテンプレートの読み込みも、この処理順操作の方法で何とかなりそうな気もするけど、どうなんでしょうか。

wpcf7mailsentが効かない


これまでSmartyに対応したあるメールシステムを利用させて貰っていましたが、WordPress用に切り替える必要がありました。そこでWordPressでは王道のContact Form 7を使うことにしました。


しかしどうも自分の環境ではイベントのwpcf7mailsentが発火せず困ってました。wpcf7mailsentにより完了ページへ遷移させたかったのです。すごく悩みましたが、以下のサイトのlengthで要素の有無により判定しページ遷移させる方法で解決しました。


https://stackoverflow.com/questions/17921032/contact-form-7-redirect-with-on-sent-ok-doesnt-work


これも発想の転換というか、やってることは当たり前で大したことではないんだけど、こういった回避策が思い浮かぶのは大事だなと思いました。

index.phpやsingle.phpで記事が取得できない


一通り固定ページの編集が終わって、index.phpやsingle.phpを編集しようと思った時、記事が取得できない症状に悩みました。コード的には問題ないと思われるのに、何故と悩んでましたが、WordPressの設定が原因でした。


自分みたいに固定ページ(静的サイト)があって、その一部にブログシステムがある構成にしたい場合、フロントページを設定する画面の「投稿ページ」という項目で、任意の固定ページを設定する必要があるのでした。この固定ページはダミーのようなもので、タイトルとスラッグ名だけ設定する感じになります。固定ページ一覧にはあるものの、このスラッグ名のページ(例えばhoge.com/blog)でis_page関数の戻り値を見てみると、falseになります。つまり固定ページとしては扱われていません。


また、パーマリンク設定のカスタム構造で/blog/%category%/%postname%/などにします。

投稿ページトップのスラッグ名が取得できない


よくよく考えれば以下の関数では取得できなくて当然なのですが、index.phpを編集し始めた時にスラッグ名が取得出来ないことに躓きました。主に固定ページで、スラッグ名を取得してそれを元に何らかの処理をするよう記述していました。

<?php
function get_slug_name() {
	$page = get_post(get_the_ID());
	return $page->post_name;
}
?>


この記述の場合、トップや検索ページ等だと最初に表示されてる記事のスラッグ名が返ることになります。トップは記事単体ではないのでこれではスラッグ名が取得できないのは当たり前なんですが、編集中この関数でスラッグ名を取得していることを忘れてて嵌まりました。結局、投稿ページのトップは、スラッグ名による判別ではなくis_homeを使うことで解決しました。


また、念の為上記の関数を以下のようにして単体の記事以外だったら空の文字列が返るようコードを変えました。この関数にis_homeの場合を書いてダミーの固定ページのスラッグ名が返るような処理を追加しても良いかもしれません。

<?php
function get_slug_name() {
	if (is_single() || is_page()) {
		$page = get_post(get_the_ID());
		return $page->post_name;
	}
	return '';
}
?>

Yoast SEOのメタの画像URLにショートコードが入る


SEO用のプラグインとしてYoast SEOを使うことにしました。


そこで困ったのが、メタの画像URLにショートコードが入ってしまう点です。記事内の画像URLは絶対的な直書きではなく、特定のディレクトリを返すショートコードを書いてどの環境(ローカル・テスト・本番)でも問題なく表示出来るようにしています。


投稿記事を参照したい場合、$post->post_contentの場合ショートコード実行前の生の状態で返ってしまうので、以下の記述が望ましいとされています。

<?php
apply_filters('the_content', $post->post_content);
?>


ですが、Yoast SEOは生のデータの方を参照しているようです。


以下のような感じでフックを使うことで解決しました。

<?php
function wpseo_fix_twitter_image($img) {
	if (strpos($img, '[get_template_directory_uri]') !== false) {
		return str_replace('[get_template_directory_uri]', get_template_directory_uri(), $img);
	}
	return $img;
}
add_filter( 'wpseo_twitter_image', 'wpseo_fix_twitter_image');
?>

reCaptcha等が読み込まれない問題


今回WordPress化の序にSSL化もやっておこうと思いました。去年くらいから無料でLet's EncryptというSSLが使えるようになりましたが、それが最近もっとお手軽に使えるようになったのです。


URLがhttpsになってからiOS Simulatorでサイトを見ると、外部サーバーから読み込まれるjsなどがSSL証明書が信用出来ないと全部弾かれてしまっている状態になりました。ちなみに自サイトだけでなくYahoo!とかも同じようでした。以前はiOS Simulatorでこんなこと起きなかったんだけどなぁ…。。


全てではないのですが、実機のiPad miniAndroidで確認しても弾かれるものがあったので、上記からSSL化したことが原因かと勘違いしました。これが原因かと思い、丸一日調べましたが全く情報がなく、解決せず。しかし結局原因は恐らく広告ブロックでした。調べても情報がないのは当然です。


ただ広告ブロックを解除すると読み込めるものの、再度広告ブロックをしても読み込めるので何とも言い難いところもあるのですが、再度広告ブロックしても読み込めるのはキャッシュかもしれませんね。読み込めなかった時に広告ブロックを解除すると確実に読み込めるので、これが原因と考えるのが無難でしょうか。


何にせよreCaptchaで認証して貰わないと次のステップへ進めないので、DOM解析してreCaptchaが読み込まれてなかったら警告表示するようにしました。広告ブロックが原因であろうことを伝えることが出来れば、ユーザー側も何故先に進めないのかと悩むことがなくなり良いかと思います。


iOS SimulatorでSSL証明書のエラーが出る原因は未だに分かりません。


追記


SSL証明書のエラーが出るのは、以前通信を見るためにインストールしたCharlesの設定が原因でした。

https・www無し→https・www有りのリダイレクトがうまくいかない


ネットに色んな正規化の記述がありますが、どれを試してもhttps・www無しからhttps・www有りへのリダイレクトがうまくいかなくて悩みました。


しかし原因は前項と同じような単純なことで、www無しのドメインSSLの申請をしてなかったのが原因です。www無しではhttpsは存在しないのだから、当然のことです。使っているのはwww有りなので、そちらだけ申請すれば良いかと直感的に思ってしまったことと、ロリポップの表記がwww無しが一番上にあり、これはサブドメインを含めて全てにまとめてチェックをするようなものかと勘違いしてしまったためです。


文字では分かりづらいですが、以下のような感じになってます。



リダイレクトがうまくいかない時、404がWordPressの404ではなくロリポップ側のものが表示されてたため、.htaccessの設定云々ではなく根本的に何か間違ってるかも、と思ったのが解決のきっかけでした。

アクセスしてくるボットの変化


ここからは躓いたことではありませんが、WordPress化して気付いたことです。


アクセスしてくるボットに変化がありました。例えばREQUEST_URIの値が「/ads.txt」となっているアクセスがあったり。これまでこのようなアクセスはなかったし、今もトップにads.txtなんて設置してないんですけどね。


結構このアクセスがあるので、ads.txtとは何なのかと調べると、簡単に言えばなりすましサイトに広告料が渡らないよう正規のサイトに予め設置しておくテキストファイルのようです。今年の5月に発表された規格のようで、かなり最近の話ですね。WordPressにして何故突然このようなアクセスが増えてきたのかは不明です。


以前にもちらっと書いた、国内のISPやモバイル回線を使ったボットであろうアクセスにも変化がありました。アクセスがあってすぐに以下のようなUAで「/apple-touch-icon-120x120-precomposed.png」「/apple-touch-icon-120x120.png」にアクセスするようになりました。

MobileSafari/604.1 CFNetwork/887 Darwin/17.0.0
MobileSafari/602.1 CFNetwork/811.4.18 Darwin/16.5.0


スマホ用のアイコンは設置してますが、多数あってごちゃごちゃしてるので特定の画像フォルダにまとめています。当然そのフォルダに読みに行くようメタを記述しています。ですが、ads.txtと同様トップにアクセスしてくるようです。


それと、同じく上記のモバイル回線ですが、以下のようなUAでのアクセスもありました。

Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/601.2.4 (KHTML, like Gecko) Version/9.0.1 Safari/601.2.4 facebookexternalhit/1.1 Facebot Twitterbot/1.0


auの回線ですが、明らかに一般ユーザーではないと思います。一般ユーザーがUAにわざわざFaceboookやTwitterのボットを表すUAを組み込まないですよね。この1秒前にiPhoneUAでアクセス、この1秒後に上記のアイコンへアクセス、といった動作です。やはりこれらはボットなのであろうと思いました。しかし目的は未だによく分かりません。