【WordPress】
ヘッダーに Google カスタム検索を
埋め込むプラグインを作った

関連記事

1. Googleカスタム検索を追加するプラグイン

WordPress テーマ GeneratePress を使ったサイトで、ヘッダー中央に Google Programmable Search Engine(以下 Google CSE)の検索ボックスを常設しました1

実装自体は小さなプラグイン一本で済んだのですが、Google CSE についている HTML の装飾をカスタマイズする作業が予想以上に長引きました。

1.1. 構成の概要

PC ではヘッダーのサイトタイトルとナビメニューの間に入力欄を置きます。

モバイルは 1024px 以下でその欄を非表示にし、代わりに検索結果ページへ飛ぶリンクボタンを出します。
検索結果は別ページに gcse-searchresults-only を置く二画面構成です2

プラグイン側でやることは三つありました。
generate_before_navigation フックで gcse-searchbox-only の HTML を出力する、管理画面から cx と結果ページ URL を設定できるようにする、そして Google CSE のデフォルト装飾を CSS で上書きする。

1.2. フックの選択

最初は generate_inside_navigation に差し込んでいました3

絶対配置で中央に持っていく構成になり、GeneratePress 標準の検索アイコンや検索モーダルと UI が二重になりやすかったです。

generate_before_navigation に変えて inside-header をフレックスコンテナとして整えると、検索欄がタイトルとメニューの間に自然に入るようになりました4
GeneratePress 標準の検索 UI はそのまま残し、競合を気にしない構成に落ち着いています。

2. Google CSE のスタイルが厄介な理由

gcse-searchbox-only を埋め込むと、Google のスクリプトが実行時に DOM を書き換え、おおむね以下の構造を生成します5

<div class="gsc-search-box">
  <table class="gsc-search-box">
    <tbody>
      <tr>
        <td class="gsc-input">
          <div class="gsc-input-box">
            <table class="gsc-input">  <!-- ← ここが曲者 -->
              <tbody><tr>
                <td><input class="gsc-input" type="text" /></td>
              </tr></tbody>
            </table>
          </div>
        </td>
        <td class="gsc-search-button">...</td>
        <td class="gsc-clear-button">...</td>
      </tr>
    </tbody>
  </table>
</div>Code language: HTML, XML (xml)

table の中に table が入っており、それぞれに Google 側の borderpaddingbackground-image が付いています。
こちらのスタイルと競合しやすく、一箇所を潰しても別の場所から枠線や余白が滲み出てきます。

2.1. 外側の table を潰しても内側が残る

最初に table.gsc-search-boxborderbackground をゼロにしました。

見た目はだいぶ改善しましたが、薄い枠線がまだ残りました。
原因は内側の table.gsc-input を直接対象にしていなかったことです。
外側と内側は別セレクターで個別に上書きする必要があります6

table.gsc-search-box,
table.gsc-input {
  border: none !important;
  margin: 0 !important;
  padding: 0 !important;
  background: transparent !important;
}Code language: CSS (css)

2.2. td.gsc-input の border が消えない

table.gsc-input を潰した後も、td.gsc-input に Google が付けた border が残るケースがありました。
td 側も明示的に指定しないと上書きが届きません7

td.gsc-input {
  border: none !important;
  padding: 0 !important;
}Code language: CSS (css)

2.3. input 自体の margin と line-height がずれを生む

外枠は整ったのに、入力欄が枠の内側でわずかにずれて見えました。
input.gsc-input に Google が付ける marginline-height.gsc-input-box ベースの枠と競合していたためです8

input.gsc-input {
  margin: 0 !important;
  line-height: normal !important;
}Code language: CSS (css)

2.4. gsc-clear-button の幅が余白を作る

クリアボタン用の td.gsc-clear-button がデフォルトでそれなりの幅を持っており、検索欄全体が右に詰まって見えました。
使わないなら幅をゼロにするか非表示にします。

td.gsc-clear-button {
  width: 0 !important;
  padding: 0 !important;
}Code language: CSS (css)

2.5. margin-bottom が下余白を作る

.gsc-input 系の要素に margin-bottom が残ると、ヘッダー内で検索欄だけ下にはみ出して見えます。
まとめてゼロにしました。

.gsc-input,
.gsc-input-box,
.gsc-search-box {
  margin-bottom: 0 !important;
}Code language: CSS (css)

3. 最終的な CSS の構成

調整の結果、上書き対象のセレクターは以下の範囲に落ち着きました。

/* 外側・内側 table の装飾を除去 */
table.gsc-search-box,
table.gsc-input {
  border: none !important;
  margin: 0 !important;
  padding: 0 !important;
  background: transparent !important;
}

/* td 側の border を除去 */
td.gsc-input {
  border: none !important;
  padding: 0 !important;
}

/* input 自体のずれを修正 */
input.gsc-input {
  margin: 0 !important;
  line-height: normal !important;
}

/* クリアボタン領域を潰す */
td.gsc-clear-button {
  width: 0 !important;
  padding: 0 !important;
}

/* 下余白を除去 */
.gsc-input,
.gsc-input-box,
.gsc-search-box {
  margin-bottom: 0 !important;
}

/* ヘッダー用の入力欄スタイル */
.gsc-input-box {
  border: 1px solid #ccc;
  border-radius: 4px;
  background: #fff;
}Code language: CSS (css)

Google CSE のスクリプトのスタイルを上書きするために入れた !important に無理やり感があります9

4. コードを入れる設定画面

初期実装では cx と検索結果ページ URL をコードに直書きしていました。

サイトをまたいで使いたくなったタイミングで管理画面に設定ページを追加し、未設定時はフロント側に何も出力しない設計に変えました10
設定画面には検索ボックスと検索結果ページのコード例もあわせて表示しています。

WordPress 管理画面の「設定」から「Header Google CSE」を開き、cx と結果ページ URL を入力します。

4.1. PHPUnit とテストの位置づけ

スタイル調整とは別に、PHPUnit と Brain Monkey を使ったユニットテスト基盤も整えました。

Brain Monkey は WordPress 関数のモックライブラリで、add_action などに依存する箇所を実際の WordPress 環境なしに検証できます11
sanitize_options の分岐や bootstrap の動作を確認しています。
WordPress の統合テスト環境は別途用意が必要なため、現時点ではユニットテストの範囲に留めています12

5. まとめ

Google CSE のスタイル上書きで分かったのは、外側の table を対象にしても内側の table・td・input がそれぞれ独自のスタイルを持っているという点です。
一箇所潰すたびに別の場所から枠線や余白が出てくる構造なので、DevTools で DOM を確認しながら上書き対象を一段ずつ広げていくのが確実でした。
GeneratePress のヘッダーに Google CSE を入れたい場合、フックは generate_before_navigation を使い、inside-header をフレックスで整えると自然に収まります。

  1. Google Programmable Search Engine は旧称 Google Custom Search Engine(CSE)。2021年に名称が変更されたが、コード内のクラス名やパラメータ(cx など)は旧称由来のものが引き続き使われている。
  2. Google の公式ドキュメントでは、この構成を “two-page layout” と呼んでいる。gcse-searchbox-only を入力側ページに、gcse-searchresults-only を結果側ページに配置し、URL の q パラメータで検索クエリを渡す仕組みになっている。 – Implementing search box | Programmable Search Engine
  3. generate_inside_navigation は GeneratePress のナビゲーションバー内部に HTML を挿入するフック。メニューアイテムの前後に要素を追加したい場合に使われることが多い。フックの位置は GeneratePress 公式のビジュアルガイドで確認できる。 – Hooks Visual Guide – Documentation
  4. generate_before_navigation はナビゲーションブロックの直前に出力するフック。inside-header をフレックスコンテナにすることで、このフックで出力した要素がサイトタイトルとメニューの間に自然に配置される。 – Hooks – Documentation
  5. Google CSE はページ読み込み後に JavaScript で DOM を動的に構築する。そのため、HTML ソースには <div class="gcse-searchbox-only"> しか存在せず、実際の table 構造はブラウザの DevTools で確認する必要がある。
  6. CSS の詳細度(Specificity)のルールでは、同じ詳細度なら後から書いたルールが勝つが、Google CSE の場合はスクリプトが生成するスタイルが詳細度の高いセレクターや要素ごとに細かく分かれているため、上位要素だけを上書きしても子孫要素に届かない。 – Specificity – CSS – MDN Web Docs
  7. CSS において、親要素の border を消しても子要素の border は独立したプロパティとして残る。Google CSE では td レベルにも個別のスタイルが付いているため、各要素を個別に対象にする必要がある。
  8. インラインスタイルは外部スタイルシートより詳細度が高いため、通常の CSS ルールでは上書きできない。Google CSE のスクリプトがインラインスタイルを付与している場合、!important を使うのが現実的な対処になる。 – !important CSS keyword – CSS | MDN
  9. MDN のドキュメントでは !important の使用をアンチパターンとしているが、自分でコントロールできない外部ライブラリのスタイルを上書きする場合は許容されるケースとして言及されている。コメントを残して理由を記録しておくことが推奨されている。 – !important CSS keyword – CSS | MDN
  10. WordPress の Settings API では register_setting() の第三引数に sanitize_callback を指定することで、保存前に値を検証・サニタイズできる。未設定チェックはこの仕組みを利用して実装するのが一般的。 – register_setting() – Function | WordPress Developer Resources
  11. Brain Monkey は内部的に Mockery と Patchwork という二つのライブラリに依存している。Patchwork が PHP 関数をランタイムで再定義する機能を持ち、Mockery がそのテスト記述を担う。この組み合わせにより、PHPUnit 単体では難しい WordPress 関数のモックが実現できる。 – Introduction | Brain Monkey
  12. WordPress の統合テストは実際の WordPress コアと DB を必要とする。wp-cli の scaffold コマンドなどで環境を構築する方法が一般的だが、CI での実行コストが高い。ユニットテストを先に整備しておくことで、統合テスト導入前の段階でもロジック検証が可能になる。 – Unit-testing with mocks in WordPress • Yoast