wordpressにワンプッシュ通知の機能をつけました。
プラグインを使わずに、Firebase Cloud Messagingを使って投稿画面にチェックを入れた時、プッシュ通知を送ります。その時に、新規投稿、更新、予約投稿の時に1回ずつ送るという挙動がうまく行かず、はまってしまいました。
うまく行っていたんですけれど、functions.phpから管理画面よりプッシュ通知かどうかを設定するチェックボックスのパーツをechoさせているのですが、そこからうまく値が取れていない気がします。
**「チェックボックスを echo で出してはいるけど、保存時(save_post など)に $_POST にその値が来ていない」**パターンが一番多いです。WordPress管理画面の投稿編集は「フォームの中にある input だけが送信される」ので、出力場所や書き方が少しズレると値が取れないそうでした。
1) チェックボックスは「OFFだと $_POST に来ない」
HTMLの仕様で、チェックが外れているとその input は送信されません。
- ✅ ON →
$_POST['push_notify']が来る - ❌ OFF →
$_POST['push_notify']自体が存在しない
なので保存ロジックは必ずこうします:
$enabled = isset($_POST['push_notify']) ? 1 : 0;
update_post_meta($post_id, '_push_notify', $enabled);
または OFF のときは削除でもOK:
if (isset($_POST['push_notify'])) update_post_meta(..., 1);
else delete_post_meta(...);
2) name 属性がない/違う(これが多い)
表示はできているのに取れない場合、name="..." が無い・または保存側とキーが不一致がよくあります。
表示側:
echo '<input type="checkbox" name="push_notify" value="1" />';
保存側:
isset($_POST['push_notify'])
この push_notify が一致しているか確認してください。
3) 出力場所が「投稿編集フォームの外」になっている
「管理画面のどこかに echo した」だけだと、投稿編集フォーム <form id="post"> ... </form> の外に出てしまい、送信されません。
安全なのはメタボックスです(フォーム内に確実に入ります)。
4) nonce がない/検証で弾いている
保存処理で nonce チェックしている場合、nonceフィールドを出してないと 保存処理が return して終了します。
5) save_post が複数回走って混乱(特にブロックエディタ)
ブロックエディタは自動保存やリビジョンで save_post が複数回走ることがあります。
値が取れないというより「意図しないタイミングの $_POST」で保存してしまうケースが出ます。
-
wp_is_post_autosave($post_id)/wp_is_post_revision($post_id)を弾く - 権限チェック
- post type 限定
が定石です。
いちばん堅い完成形(メタボックス + 保存)
これに寄せると、ほぼ確実に直ります。コピペで動く形です。
// 1) メタボックス追加(投稿編集フォーム内に出る)
add_action('add_meta_boxes', function () {
add_meta_box(
'push_notify_box',
'プッシュ通知',
function ($post) {
$val = (int) get_post_meta($post->ID, '_push_notify', true);
wp_nonce_field('push_notify_save', 'push_notify_nonce');
echo '<label style="display:flex; gap:8px; align-items:center;">';
echo '<input type="checkbox" name="push_notify" value="1" ' . checked(1, $val, false) . ' />';
echo 'この投稿を公開/更新時にプッシュ通知する';
echo '</label>';
},
'post', // 対象の投稿タイプに合わせて変更
'side',
'high'
);
});
// 2) 保存処理
add_action('save_post', function ($post_id) {
// autosave / revision は除外
if (wp_is_post_autosave($post_id) || wp_is_post_revision($post_id)) return;
// nonce チェック
if (!isset($_POST['push_notify_nonce']) || !wp_verify_nonce($_POST['push_notify_nonce'], 'push_notify_save')) return;
// 権限チェック(投稿タイプに合わせて)
if (!current_user_can('edit_post', $post_id)) return;
// チェックボックスは「無い=OFF」
$enabled = isset($_POST['push_notify']) ? 1 : 0;
update_post_meta($post_id, '_push_notify', $enabled);
}, 10, 1);
すぐ原因を特定するデバッグ(30秒)
保存時に $_POST に来てるかだけをログで確認できます。
add_action('save_post', function($post_id){
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
error_log('push_notify in POST? ' . (isset($_POST['push_notify']) ? 'YES' : 'NO'));
}, 99);
-
NO なら:フォーム外に出てる /
name不一致 / nonceで return / そもそもその画面で送信されてない -
YES なら:保存側のキー違い、または保存後の取得キー(
get_post_metaのキー)が違う
add_action save_postとtaransition_post_statues
予約投稿時の通知をさせるために、taransition_post_statuesで動く関数を作りました。
それが原因でしょうか。
結論から言うと、「save_post と transition_post_status に分けたこと」自体が直接の原因というより、
- どっちか一方(または両方)が
$_POSTを前提にしている - でも
transition_post_statusの実行タイミングでは$_POSTが無い/期待通り入ってない - さらに両方走って 上書き・二重送信・OFF扱いが起きてる
…この組み合わせが“破綻ポイント”になりがちです。
まず超重要:transition_post_status ではチェックボックス値は取れないことが多い
チェックボックスは「投稿編集フォームの送信」に乗ってくるので、基本は save_post のときしか確実に取れません。
transition_post_status は「ステータスが変わった瞬間」のフックで、
REST、クイック編集、予約公開、プラグイン更新、Cron など フォーム送信じゃない経路でも発火します。
そのとき $_POST['push_notify'] は 無いのが普通です。
なので、
- ✅ チェックボックスの保存(post_meta更新) →
save_postでやる - ✅ 送信(Firebaseへcurlなど) →
transition_post_statusでやる(ただし$_POST参照しない)
に分けると安定します。
“分けたせい”で起きる典型トラブル
A. transition_post_status 側で $_POST を見てしまい、常にOFF扱い
if (isset($_POST['push_notify'])) { ... }
→ transition_post_status では $_POST が無くて false になり、送らない/逆に誤判定。
対策:transition_post_status は get_post_meta($post_id, '_push_notify', true) だけを見る。
B. save_post と transition_post_status の両方で “送信” して二重通知
save_post でも送って、公開時に transition_post_status でも送ってしまう。
対策:送信は片方に寄せる(おすすめは transition_post_status)。
save_post は “設定保存だけ”。
おすすめの役割分担(安全版)
1) save_post:チェックボックスの値を post_meta に保存するだけ
- autosave/revision除外
- nonce/権限チェック
-
isset($_POST['push_notify']) ? 1 : 0で保存
2) transition_post_status:公開になった瞬間だけ送る
-
if ($old !== 'publish' && $new === 'publish')(公開になったとき) -
get_post_meta(... '_push_notify' ...)が 1 のときだけ送る - 二重発火対策にロック(transient/メタ)を入れる
そのまま使える完成形(ロック付き)
// save_post:チェックボックス保存だけ
add_action('save_post', function($post_id){
if (wp_is_post_autosave($post_id) || wp_is_post_revision($post_id)) return;
if (!isset($_POST['push_notify_nonce']) || !wp_verify_nonce($_POST['push_notify_nonce'], 'push_notify_save')) return;
if (!current_user_can('edit_post', $post_id)) return;
$enabled = isset($_POST['push_notify']) ? 1 : 0;
update_post_meta($post_id, '_push_notify', $enabled);
}, 10, 1);
// transition_post_status:公開になった瞬間だけ送信
add_action('transition_post_status', function($new, $old, $post){
if (!$post || $post->post_type !== 'post') return;
// 公開に変わった瞬間だけ
if ($old === 'publish' || $new !== 'publish') return;
// 保存した設定を見る($_POSTは見ない)
$enabled = (int) get_post_meta($post->ID, '_push_notify', true);
if ($enabled !== 1) return;
// 二重発火ロック(60秒)
$lock_key = 'push_notify_lock_' . $post->ID;
if (get_transient($lock_key)) return;
set_transient($lock_key, 1, 60);
// ここで Firebase に送る(curl など)
// push_send_to_firebase($post->ID);
}, 10, 3);
いまの現象に照らすと「ここが怪しい」
あなたの「値が取れてない気がする」は、かなり高確率で
-
transition_post_statusで$_POSTを見てしまってる
または -
save_postが2回走る(新規追加時は特に)→1回目でOFF扱いで保存→後で上書き
のどっちかです。
最短で原因確定するチェック
この2行をそれぞれのフックに入れてログを見てください。
save_post 側
error_log('save_post push_notify='. (isset($_POST['push_notify']) ? 'YES' : 'NO'));
transition_post_status 側
error_log('transition meta _push_notify=' . get_post_meta($post->ID, '_push_notify', true));
- save_post が YES なのに meta が 0 → 保存処理が return してる(nonce/権限/キー違い)
- transition の meta が 0 → 保存ができてない or キー違い
- transition で
$_POSTを見てたら、ほぼ確で NO になる
もし、今の 2つのフックの該当部分だけ(チェックボックスの name と、save_post / transition_post_status の中身)を貼ってくれたら、どこでズレてるか一発で指摘します。
(FirebaseのキーやURLは伏せてOKです)
save_postとtaransition_post_statues
を分けたことが原因か?
結論から言うと、「save_post と transition_post_status に分けたこと」自体が直接の原因というより、
- どっちか一方(または両方)が
$_POSTを前提にしている - でも
transition_post_statusの実行タイミングでは$_POSTが無い/期待通り入ってない - さらに両方走って 上書き・二重送信・OFF扱いが起きてる
…この組み合わせが“破綻ポイント”になりがちです。
まず超重要:transition_post_status ではチェックボックス値は取れないことが多い
チェックボックスは「投稿編集フォームの送信」に乗ってくるので、基本は save_post のときしか確実に取れません。
transition_post_status は「ステータスが変わった瞬間」のフックで、
REST、クイック編集、予約公開、プラグイン更新、Cron など フォーム送信じゃない経路でも発火します。
そのとき $_POST['push_notify'] は 無いのが普通です。
なので、
- ✅ チェックボックスの保存(post_meta更新) →
save_postでやる - ✅ 送信(Firebaseへcurlなど) →
transition_post_statusでやる(ただし$_POST参照しない)
に分けると安定します。
“分けたせい”で起きる典型トラブル
A. transition_post_status 側で $_POST を見てしまい、常にOFF扱い
if (isset($_POST['push_notify'])) { ... }
→ transition_post_status では $_POST が無くて false になり、送らない/逆に誤判定。
対策:transition_post_status は get_post_meta($post_id, '_push_notify', true) だけを見る。
B. save_post と transition_post_status の両方で “送信” して二重通知
save_post でも送って、公開時に transition_post_status でも送ってしまう。
対策:送信は片方に寄せる(おすすめは transition_post_status)。
save_post は “設定保存だけ”。
おすすめの役割分担(安全版)
1) save_post:チェックボックスの値を post_meta に保存するだけ
- autosave/revision除外
- nonce/権限チェック
-
isset($_POST['push_notify']) ? 1 : 0で保存
2) transition_post_status:公開になった瞬間だけ送る
-
if ($old !== 'publish' && $new === 'publish')(公開になったとき) -
get_post_meta(... '_push_notify' ...)が 1 のときだけ送る - 二重発火対策にロック(transient/メタ)を入れる
そのまま使える完成形(ロック付き)
// save_post:チェックボックス保存だけ
add_action('save_post', function($post_id){
if (wp_is_post_autosave($post_id) || wp_is_post_revision($post_id)) return;
if (!isset($_POST['push_notify_nonce']) || !wp_verify_nonce($_POST['push_notify_nonce'], 'push_notify_save')) return;
if (!current_user_can('edit_post', $post_id)) return;
$enabled = isset($_POST['push_notify']) ? 1 : 0;
update_post_meta($post_id, '_push_notify', $enabled);
}, 10, 1);
// transition_post_status:公開になった瞬間だけ送信
add_action('transition_post_status', function($new, $old, $post){
if (!$post || $post->post_type !== 'post') return;
// 公開に変わった瞬間だけ
if ($old === 'publish' || $new !== 'publish') return;
// 保存した設定を見る($_POSTは見ない)
$enabled = (int) get_post_meta($post->ID, '_push_notify', true);
if ($enabled !== 1) return;
// 二重発火ロック(60秒)
$lock_key = 'push_notify_lock_' . $post->ID;
if (get_transient($lock_key)) return;
set_transient($lock_key, 1, 60);
// ここで Firebase に送る(curl など)
// push_send_to_firebase($post->ID);
}, 10, 3);
いまの現象に照らすと「ここが怪しい」
あなたの「値が取れてない気がする」は、かなり高確率で
-
transition_post_statusで$_POSTを見てしまってる
または -
save_postが2回走る(新規追加時は特に)→1回目でOFF扱いで保存→後で上書き
のどっちかです。
最短で原因確定するチェック
この2行をそれぞれのフックに入れてログを見てください。
save_post 側
error_log('save_post push_notify='. (isset($_POST['push_notify']) ? 'YES' : 'NO'));
transition_post_status 側
error_log('transition meta _push_notify=' . get_post_meta($post->ID, '_push_notify', true));
- save_post が YES なのに meta が 0 → 保存処理が return してる(nonce/権限/キー違い)
- transition の meta が 0 → 保存ができてない or キー違い
- transition で
$_POSTを見てたら、ほぼ確で NO になる
今のところ、save_postの新規投稿の時だけ、送れていません アップデートと、予約投稿はうまく行っています。
それ、かなり典型で、原因はほぼこれです👇
「予約投稿(future→publish)」のときだけ送る条件になっていて、
“新規で今すぐ公開(draft/auto-draft→publish)” のルートが条件に引っかかってない
(+更新は save_post 側で送れているので、更新だけ成功して見える)
よくある “条件ミス” の例
こういう条件だと 予約投稿しか送れません:
if ($old === 'future' && $new === 'publish') {
// 送信
}
新規即公開はだいたい
auto-draft → draft → publish または draft → publish なので、ここに入りません。
修正:公開になった瞬間は「old が publish じゃなくて new が publish」
新規即公開も予約公開もまとめて拾うならこれです:
if ($old !== 'publish' && $new === 'publish') {
// 公開になった瞬間(新規即公開 / 予約公開 / 下書き→公開)
}
安定するおすすめ構成(コピペOK)
1) save_post:チェックボックスをメタに保存するだけ
2) transition_post_status:公開になった瞬間だけ送る(新規即公開もここで拾う)
(ついでに二重発火ロックも入れてます)
// save_post:チェックボックス保存だけ
add_action('save_post', function($post_id){
if (wp_is_post_autosave($post_id) || wp_is_post_revision($post_id)) return;
if (!current_user_can('edit_post', $post_id)) return;
// nonceを使ってるならここも入れる(使ってないなら一旦省略でも可)
// if (!isset($_POST['push_notify_nonce']) || !wp_verify_nonce($_POST['push_notify_nonce'], 'push_notify_save')) return;
$enabled = isset($_POST['push_notify']) ? 1 : 0;
update_post_meta($post_id, '_push_notify', $enabled);
}, 10, 1);
// transition_post_status:公開になった瞬間(新規即公開/予約公開)に送る
add_action('transition_post_status', function($new, $old, $post){
if (!$post || $post->post_type !== 'post') return;
// ★ここがポイント:future→publish だけじゃなく、publish になった瞬間を全部拾う
if ($old === 'publish' || $new !== 'publish') return;
$enabled = (int) get_post_meta($post->ID, '_push_notify', true);
if ($enabled !== 1) return;
// 二重送信ロック(60秒)
$lock_key = 'push_notify_lock_' . $post->ID;
if (get_transient($lock_key)) return;
set_transient($lock_key, 1, 60);
// ここでFirebaseへ送信
// push_send_to_firebase($post->ID);
}, 10, 3);
もし「更新時にも送る」を残したい場合
更新は transition_post_status では拾えない(publish→publish は除外してる)ので、更新通知は別でやります。
add_action('post_updated', function($post_id, $post_after, $post_before){
if ($post_after->post_type !== 'post') return;
if ($post_after->post_status !== 'publish') return;
$enabled = (int) get_post_meta($post_id, '_push_notify', true);
if ($enabled !== 1) return;
// 更新送信用のロック(60秒)
$lock_key = 'push_notify_update_lock_' . $post_id;
if (get_transient($lock_key)) return;
set_transient($lock_key, 1, 60);
// push_send_to_firebase_update($post_id);
}, 10, 3);
いまの状態で一番疑うべきポイント
あなたの説明だと、新規だけ送れない=かなり高確率で
-
transition_post_status側が future→publish 限定になってる
または -
save_post側で$updateを見て新規($update=false)を弾いてる
例:if (!$update) return;
のどちらかです。
もし貼れるなら、送信している条件のif文だけ(FirebaseのURLやキーは伏せてOK)を貼ってください。
「どの条件が新規即公開を落としてるか」を、そのままピンポイントで直す形にします。