0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WordPress採用サイトで募集終了とJobPostingを同時に止める設計チェック

0
Posted at

WordPressで求人ページを作ると、「募集中」の表示だけを消して安心しがちです。しかし実際には、応募ボタン、求人一覧、JobPosting構造化データも同じ募集状態に連動させる必要があります。

とくにJobPostingは、画面上の表示とは別のテンプレートやフックから出力されることがあります。募集終了後もJSON-LDだけが残る、というズレを防ぐには、各所で個別に判定するのではなく、1つの状態判定を共有するのが安全です。

この記事では、製品固有のコードに依存しない最小例で、次の4つを同じ状態から制御します。

  • 求人詳細を表示するか
  • 求人一覧へ載せるか
  • 応募導線を出すか
  • 求人詳細へJobPostingを出すか

なぜ表示と構造化データを一緒に止めるのか

Googleの求人情報ガイドでは、JobPostingは1件の求人を詳しく掲載するページへ実装し、ページ上で求職者が読める内容と一致させる必要があります。求人一覧や検索結果ページへ追加するものではありません。

また、応募を受け付けていない求人は、期限切れを示す、ページを削除する、またはJobPostingを削除するなどの対応が必要です。期限切れの求人に応募フローが残ることや、終了後もJobPostingが残ることは避けなければなりません。

ここで重要なのは、Google向けだけ別条件にしないことです。

募集状態
  ├─ 詳細ページ
  ├─ 求人一覧
  ├─ 応募導線
  └─ JobPosting

4つの出力が同じ判定結果を見るようにします。

最初に状態表を決める

この例では、編集者が入力する状態をopenpausedclosedの3つに絞り、expiredは期限から自動判定します。

状態 詳細 一覧 応募導線 JobPosting
open 表示 表示 表示 出力
paused 表示 表示 非表示 非出力
closed 表示 非表示 非表示 非出力
expired 表示 非表示 非表示 非出力
非公開投稿 非表示 非表示 非表示 非出力

closedexpiredでも詳細を残すのは、「この募集は終了しました」と案内できる設計例です。pausedを一覧へ残す場合も、一時停止中で応募できないことを画面上で明示します。終了時に404または410へする運用を選ぶ場合でも、応募導線とJobPostingを止めるゲートは共通化できます。

状態resolverをWordPressから分離する

まず、WordPress関数を呼ばない純粋なresolverを作ります。入力が同じなら出力も同じなので、境界条件を小さくテストできます。

<?php

function example_resolve_job_state(array $job, DateTimeImmutable $now): array
{
    if (!$job['published']) {
        return [
            'state'       => 'not-public',
            'detail'      => false,
            'list'        => false,
            'application' => false,
            'jobposting'  => false,
        ];
    }

    $state = in_array($job['state'], ['open', 'paused', 'closed'], true)
        ? $job['state']
        : 'closed';

    if ($state === 'open' && $job['valid_through'] !== '') {
        try {
            $valid_through = new DateTimeImmutable($job['valid_through']);

            // 期限と現在時刻が同じ瞬間も終了として扱う。
            if ($valid_through <= $now) {
                $state = 'expired';
            }
        } catch (Throwable $error) {
            // 壊れた期限で募集を継続しない。安全側へ閉じる。
            $state = 'closed';
        }
    }

    return [
        'state'       => $state,
        'detail'      => true,
        'list'        => in_array($state, ['open', 'paused'], true),
        'application' => $state === 'open' && $job['application_ready'],
        'jobposting'  => $state === 'open' && $job['schema_ready'],
    ];
}

不明な状態や壊れた期限をopenへ寄せないのがポイントです。求人情報が不完全なときは、出し続けるより止めて確認できる方が安全です。

WordPressの値をresolverへ渡す

次に、投稿とカスタムフィールドから入力を組み立てます。キー名や投稿タイプ名はサイトに合わせて変更してください。

<?php

function example_job_context(int $post_id): array
{
    $application_url = trim((string) get_post_meta(
        $post_id,
        'job_application_url',
        true
    ));

    // 必須情報を検査し、完全なときだけ配列を返すサイト固有関数。
    $payload = example_build_validated_jobposting_payload($post_id);

    $gate = example_resolve_job_state(
        [
            'published'         => get_post_status($post_id) === 'publish',
            'state'             => (string) get_post_meta($post_id, 'job_state', true),
            'valid_through'     => (string) get_post_meta($post_id, 'job_valid_through', true),
            'application_ready' => $application_url !== '',
            'schema_ready'      => is_array($payload),
        ],
        current_datetime()
    );

    return [
        'gate'            => $gate,
        'application_url' => $application_url,
        'payload'         => $payload,
    ];
}

current_datetime()を使うと、WordPressに設定されたタイムゾーンの現在時刻をDateTimeImmutableとして扱えます。保存する期限もISO 8601形式へ統一し、日付だけでなく「その時刻を過ぎたらどうなるか」まで決めておきます。

example_build_validated_jobposting_payload()は、少なくともGoogleが必須としているdatePosteddescriptionhiringOrganizationjobLocationまたは適切なリモート勤務情報、titleを確認し、有効期限がある場合はvalidThroughも含める関数です。画面で見える求人内容とJSON-LDの内容が一致しない場合は、配列を返さずnullで止めます。

応募導線も同じgateを見る

応募URLがあるだけでは表示しません。募集状態とURLの両方が有効なときだけ出力します。

<?php

function example_application_markup(array $context): string
{
    if (!$context['gate']['application']) {
        return '';
    }

    return sprintf(
        '<a class="job-apply" href="%s">応募する</a>',
        esc_url($context['application_url'])
    );
}

実運用ではURLの形式だけでなく、許可するドメイン、応募フォームの公開状態、送信先の管理責任も別途確認します。

JobPostingは求人詳細だけに出す

JSON-LDは、求人詳細であり、募集状態と必須情報が有効なときだけ出力します。

<?php

function example_jobposting_markup(
    array $context,
    bool $is_singular_job
): string {
    if (
        !$is_singular_job
        || !$context['gate']['jobposting']
        || !is_array($context['payload'])
    ) {
        return '';
    }

    return '<script type="application/ld+json">'
        . wp_json_encode(
            $context['payload'],
            JSON_UNESCAPED_UNICODE
            | JSON_UNESCAPED_SLASHES
            | JSON_HEX_TAG
            | JSON_HEX_AMP
            | JSON_HEX_APOS
            | JSON_HEX_QUOT
        )
        . '</script>';
}

function example_print_jobposting(): void
{
    if (!is_singular('sample_job')) {
        return;
    }

    $post_id = get_queried_object_id();
    $context = example_job_context($post_id);

    echo example_jobposting_markup($context, true);
}

add_action('wp_head', 'example_print_jobposting', 20);

一覧テンプレートではexample_jobposting_markup()を呼ばず、フック側でもis_singular('sample_job')を確認します。二重に境界を置くことで、求人一覧へ複数のJobPostingが混ざる事故を避けます。

JSON-LDは文字列連結で手書きせず、wp_json_encode()でエンコードします。構造化データの完全性は出力直前の真偽値ではなく、payloadを作る段階で検査します。

最低限の状態テスト

resolverはWordPressから分離しているため、少なくとも次を自動テストできます。

<?php

$now = new DateTimeImmutable('2026-08-30T12:00:00+09:00');

$cases = [
    'open' => [
        ['published' => true, 'state' => 'open', 'valid_through' => '2026-08-31T12:00:00+09:00', 'application_ready' => true, 'schema_ready' => true],
        ['detail' => true, 'list' => true, 'application' => true, 'jobposting' => true],
    ],
    'paused' => [
        ['published' => true, 'state' => 'paused', 'valid_through' => '', 'application_ready' => true, 'schema_ready' => true],
        ['detail' => true, 'list' => true, 'application' => false, 'jobposting' => false],
    ],
    'closed' => [
        ['published' => true, 'state' => 'closed', 'valid_through' => '', 'application_ready' => true, 'schema_ready' => true],
        ['detail' => true, 'list' => false, 'application' => false, 'jobposting' => false],
    ],
    'expired-boundary' => [
        ['published' => true, 'state' => 'open', 'valid_through' => '2026-08-30T12:00:00+09:00', 'application_ready' => true, 'schema_ready' => true],
        ['detail' => true, 'list' => false, 'application' => false, 'jobposting' => false],
    ],
];

foreach ($cases as $name => [$input, $expected]) {
    $actual = example_resolve_job_state($input, $now);

    foreach ($expected as $key => $value) {
        if ($actual[$key] !== $value) {
            throw new RuntimeException("{$name}: {$key} failed");
        }
    }
}

これに加えて、WordPressへ接続するテストでは次を確認します。

  • 下書き投稿はすべての出力が止まる
  • 必須schema情報が欠けた募集中投稿は、詳細・一覧・応募導線だけを出し、JobPostingは出さない
  • 期限と現在時刻が等しい境界で、一覧・応募導線・JobPostingが止まる
  • 一覧queryへclosedexpiredを混ぜても、表示対象へ入らない
  • 一覧ページのHTMLにapplication/ld+jsonが出ない

公開前に画面と機械の両方で確認する

コードが正しくても、キャッシュや別プラグインが古いJSON-LDを出す可能性があります。公開前後には、少なくとも次を実URLで確認します。

  1. 募集中の詳細で応募導線が使える
  2. 一時停止・終了・期限切れで応募導線が消える
  3. 終了・期限切れが求人一覧から消える
  4. 求人一覧のソースにJobPostingがない
  5. 募集中の詳細にJobPostingが1件だけある
  6. 終了・期限切れの詳細にJobPostingがない
  7. JSON-LDの内容と画面上の職種・説明・雇用主・勤務地・期限が一致する
  8. リッチリザルト テストでエラーがない

Googleは、終了した求人についてvalidThroughを過去にする、404または410にする、JobPostingを削除する、という選択肢を示しています。どれを採用するかはURL維持方針と運用体制で決めます。ただし、応募終了後も応募導線と有効なJobPostingが残る状態は避けます。

まとめ

求人ページの終了処理は、ボタンを1つ消す作業ではありません。

  • 編集者が入力する募集状態を絞る
  • 期限切れをresolverで自動判定する
  • 詳細、一覧、応募導線、JobPostingを同じ判定結果へ接続する
  • 不明値や不完全なschemaは安全側へ閉じる
  • 一覧ではJobPostingを出さない
  • 公開後の実HTMLでも残存を確認する

この構造にしておくと、「画面では終了しているのに応募できる」「一覧から消したのにJSON-LDだけ残る」といったズレを、状態テストで早めに検出できます。

参考資料

採用サイト案件全体の確認項目は、WordPress採用サイトの制作・引き渡しチェックリストにまとめています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?