指摘は1回、承認まで丸1日でした。
パスキーのプラグインを WordPress.org に申請しました。8月10日0時30分に保留の通知が来て、同日1時50分に修正版を上げて、翌11日に承認。指摘は4件です。
うち1件は、ガイドラインを読んだと自分でチェックを入れたうえで、その禁止事項を出荷していました。残り3件は知らなかっただけです。
順に書きます。
検証環境:【プラグインのバージョンと確認日を入れる】。以下は自分が受け取った審査メールの内容を、要点だけまとめたものです。
審査にAIが入っています
先に、審査そのものの変化から。
通知メールに、この内容は人間とアルゴリズムとAIの組み合わせで生成されていて、人間が確認していない場合もある、と明記されていました。AIが出した箇所には ✨ の絵文字が付きます。そして、その指摘はかなり正確だから注意して読め、とも書いてあります。
実際、自分の場合は関数名まで名指しされていました。どのファイルのどのメソッドが、どの定数でゲートされていて、どこの表示と紐づいているか。人間が目視で拾ったにしては速すぎるし、正確すぎました。
あわせて警告も付いています。同じ性質の問題が次の審査で見つかったら、そのプラグインは二度と審査しない、と。1回目の返信で全部潰しておく必要があります。
余談ですが、返信の作法もかなり具体的に指定されていました。短く、要点だけ。変更点を列挙するな、こちらは差分ではなくコード全体を見直すので不要だ、と。そして、AIが生成した冗長な文章は避けろ、とはっきり書いてあります。長い返信は歓迎されません。
指摘1:トライアルウェア
これが本体です。
管理者のパスキー登録という機能を、無料版のコードに置いていました。ただし rapls_passkey/allow_admin_enrolment というフィルターでゲートしてあって、有料アドオン側がライセンスを確認してからそのフィルターを true にする。無料版だけでは動かない状態です。
ガイドライン5でこれは禁止されています。ライセンスキー、試用期間、利用回数、時間、割り当て。どんな形でも、組み込み済みの機能を制限してはいけない。
読み落としやすいのは、次の一文だと思います。将来アップグレードしたときのために置いてあるだけでも、認められません。コードがそこにあるなら、動かなければならない。
有料機能を出したいなら、そのコードは WordPress.org のパッケージに入れず、自分のサイトなど別の場所で配る。無料版から「こういう機能が別プラグインにあります」と案内するのは構わない。そこまでです。
自分の場合、SettingsPage::render_upsell() とコードコメントとチェンジログが、その機能を有料版に結び付けていました。ゲートを外しても、有料版への言及そのものが証拠として残ります。
直し方は単純で、既定でオンにして、ライセンスを待つ処理を無料版から全部消すことでした。
指摘2:出力のエスケープ
src/Login/SecondFactorScreen.php の1箇所です。
echo $fields; // 中身は ob_get_clean() の戻り値
出力バッファに溜めたHTMLを、そのまま echo していました。自分で書いたマークアップなのでエスケープ済みのつもりでいたのですが、指摘の内容はそこではありませんでした。
この $fields は、2要素認証のプロバイダが描画したHTMLです。プロバイダはフィルター経由で登録できます。**中身を書いているのが自分とは限りません。**サードパーティのプラグインが返したHTMLを、こちらが素通しで出していることになります。
自分が書いた文字列かどうかで判断していたのが間違いでした。フィルターを開けた時点で、その先は自分の管轄ではありません。
指摘3:不要なフォルダ
vendor/doctrine/deprecations/src/PHPUnit が入っていました。
依存パッケージの中のテスト用ディレクトリです。自分で置いたものではなく、Composer が持ってきたものがそのまま同梱されていました。
開発ツール、リリーススクリプト、デモ、ユニットテスト。この手のものは本番パッケージに含めない。ただし composer.json や package.json のような、再構築のための設定ファイルは含めていい、と明記されています。フォークしたい人のために必要だからです。
vendor の中まで自分で見に行くことは、たぶんあまりないと思います。自分は見ていませんでした。
指摘4:翻訳ファイルと load_plugin_textdomain
languages/rapls-passkey-ja.mo と .po を同梱していました。
WordPress.org でホストされるプラグインは、翻訳を translate.wordpress.org で管理します。ロケールごとのファイルは自動生成され、標準の更新システムで配信される。だから同梱は不要です。必要なのは、プラグインが正しく国際化されていることだけ。
同じ流れで load_plugin_textdomain() も指摘されました。WordPress 4.6 以降、スラッグと一致するテキストドメインなら、コアが必要なときに読み込みます。呼び出しは要りません。
古いバージョンを支えるために残すなら、init 以降のフックに入れること。6.7 以降、早すぎる読み込みには Notice が出ます。
このあたりは別に整理しました。
直したあとに、もっと悪いものが出てきました
ここからが学びのほうです。
トライアルウェアの指摘を受けて、フィルターの既定値を全部見直しました。プラグインの中にあるフィルターを1つずつ開いて、既定でオフになっているものがないか確かめる作業です。
そのときに気づきました。**この機能には、テストがありました。**そして通っていました。
テストがフィルターをスタブしていたからです。テストコードの中でフィルターに true を返させて、その状態で機能が動くことを確かめていた。既定値そのものは、一度も検査していません。
ロックが入ったままでもテストは緑になります。実際、緑でした。
いま思うと、これは指摘4件のどれよりも根が深い問題でした。ガイドラインを知らなかったのは調べれば済みます。テストが検査していない場所を検査していると思い込んでいたのは、調べようがない。
直したあとは、既定値そのものに対するテストを1本足しました。フィルターを触らないまま呼んで、機能が動くことを確かめるだけのものです。
申請前に見ておくもの
自分の分をチェックリストにします。
ライセンスや条件で分岐している箇所が、無料版のコードに残っていないか。フィルターやオプションでゲートしている機能があれば、その既定値を確かめること。あわせて、そのゲートをテストがスタブしていないかも見ます。
有料版への言及が、コードコメントやチェンジログや管理画面に残っていないか。ゲートを外しても、言及だけで指摘の材料になります。
echo している変数のうち、フィルター経由で外から入りうるものがないか。自分が書いた文字列かどうかではなく、経路で判断します。
vendor と node_modules の中に、テストや開発用のディレクトリが残っていないか。
languages/ に .po と .mo が入っていないか。load_plugin_textdomain() を呼んでいないか。
Plugin Check と PHPCS+WPCS を通すこと。ただし、これで vendor の中の PHPUnit ディレクトリまで拾えるかは確認していません。
次の自分に渡すメモ
- ゲートしている機能は、既定値そのものをテストする。フィルターをスタブしたテストは、ゲートが閉じたままでも緑になる
- 有料版への言及は、コード・コメント・チェンジログ・管理画面の全部から消す
-
echoの判断基準は「自分が書いたか」ではなく「フィルターを通っていないか」 - 静的解析は配布用のzipを展開して掛ける。手元のソースに掛けたものは検査したうちに入らない
- 返信は短く。変更点の列挙は不要、と向こうから明記されている
審査は1回で通りましたが、通ったこと自体はあまり関係がない気がしています。指摘されなければ、テストが何も見ていなかったことに気づかないままでした。
ふだんはraplsworks.comで、WordPressプラグイン開発やClaude Codeまわりのことを書いています。