Skip to content

ADM-001 P-WORLDメールアーカイブ管理画面

概要

  • P-WORLD 配信メールを IMAP で取得・保存し、管理画面上で一覧・詳細表示・記事リンク付与・削除などを行う管理画面。
  • WordPress トップレベルメニューとして登録。操作はすべて admin-ajax 経由の非同期通信で処理する。
  • メール取得は 手動実行(AJAX → WP-Cron 非同期)と 定期実行(WP-Cron 単発連鎖)の 2 経路を持つ。

外部インターフェース

管理画面 URL

項目
URL/wp-admin/admin.php?page=pworld-archive-admin
メニュー名P-WORLDメールアーカイブ
権限manage_options

admin-ajax(HTTP 契約)

すべて POST /wp-admin/admin-ajax.phpmanage_options 権限+操作別 nonce 必須。

action説明
pworld_archive_run手動メール取得ジョブを非同期起動
pworld_archive_job_status実行中ジョブの状態確認
pworld_archive_mail_listメール一覧取得(20件/ページ)
pworld_archive_mail_detailメール詳細取得
pworld_mail_deleteメール削除
pworld_mail_update_article_link記事リンク個別更新
pworld_mail_bulk_save_article_links記事リンク一括保存
pworld_archive_force_reset実行中ジョブの強制リセット
pworld_archive_cron_settings_save定期 Cron 設定の保存
pworld_gif_still_regenerateGIF スチルフレーム再生成
pworld_gif_backfill_countGIF バックフィル対象件数取得
pworld_gif_backfillGIF バックフィル実行
pworld_gif_backfill_candidatesGIF バックフィル候補一覧取得
pworld_mail_deletable_image_count削除可能画像数取得

POST パラメータ・レスポンス data の契約は EP-一覧 および admin-ajax/endpoints/ を参照。使用画面 ID は ADM-一覧 を参照。

WP-Cron フック

フック名クラス実行契機処理内容
pworld_archive_async_runPworldArchiveAsyncRunner手動取得ボタン押下後に WP-Cron 単発登録IMAP 取得(デフォルト 5 件、最大 500 件)。MySQL ロックで重複防止
pworld_archive_scheduled_jobPworldArchiveScheduledCronRunner毎日 21:00 / 22:00 / 22:30(単発連鎖方式)IMAP 取得 + メール TTL パージ + GIF TTL パージ(各設定で有効化)

定期 Cron スロット設定(PworldArchiveCronScheduler

  • スロット: 2100, 2200, 2230(HHmm 形式)
  • daily 再発火ではなく単発連鎖(実行後に次回スロットを再登録)
  • 管理者ログイン時の admin_init フックで sync_schedule を 1 日 1 回実行(transient でスロットル)
  • 取得または TTL パージの少なくともどちらかが有効な場合のみスケジュール登録

サムネイル自動生成(PworldHallDayThumbnailAutoGenerateService

処理フロー

PworldArchiveService::run() でのメール保存成功直後に呼び出される。

呼び出し元サービスタイミング
PworldArchiveAsyncRunner / PworldArchiveScheduledCronRunnerPworldArchiveService::run()PworldHallDayThumbnailAutoGenerateServiceメール 1 件保存成功直後(mail_id, article_target_date, hall_name を渡す)

入力

フィールド出所
mail_idint保存済みメール ID
article_target_datestring|nullPworldMailArchiveEntity::get_article_target_date()
hall_namestringPworldMailArchiveEntity::get_hall_name()

スキップ条件(早期リターン)

条件理由
article_target_date が null日付未解決のため対象外
HallEnum::from_string(hall_name) === nullunknown・未登録ホール名を含む全未解決ホールをスキップ
トリガーメールの show_in_article = false記事表示対象外のメールは処理しない

対象メールの絞り込み

スキップ条件をパスした後、pworld_mail_archive から同一 article_target_date × hall_nameHallEnum::value 正規化後)かつ show_in_article = true のメールを id ASC で全件取得する。

件数処理
0 件スキップ(安全のため)
1 件単体サムネイル生成
2 件以上先頭 2 件で合成サムネイル生成(3 件目以降は無視)

各メールの attachment_id 解決

対象メール 1 件ごとに以下の優先順で attachment_id を解決する。メールに pworld_mail_image_ref が 1 件もない場合はそのメールをスキップ扱いとし、解決可能なメールが 0 件なら全体をスキップ。

方針: メイン画像をソースとする

日別ホールサムネの自動生成では、メール本文に複数の画像がある場合でも、告知の 主たるビジュアル(メイン画像) に相当する 1 枚の添付をサムネイルのソースとする。選定はサーバー側で行い、メール HTML をブラウザ表示したときのスクロール位置をシステムが再現してキャプチャする処理は行わない

旧設計文書にあった「メール本文を縦方向に 100px スクロールした状態で見える GIF」という表現は、運用上「本文を少し下にスクロールしてから手動キャプチャしやすい領域」に近い結果を得たいという意図のたとえであった。自動生成ではそれを メイン画像のヒューリスティック選定(下記)で近似する。

メイン画像の定義(PworldHallDayThumbnailAutoGenerateService 準拠)

複数の pworld_mail_image_ref がある通常メールでは、次の手順で メイン画像に使う ref を決める。

  1. 当該 mail_idpworld_mail_image_refid 昇順に列挙する(メール HTML 上の画像登録順に相当)。
  2. 先頭から順に見て、添付のピクセル寸法が取得できる場合は 帯状の装飾画像とみなしてスキップする条件を適用する:幅 ÷ 高さ ≥ 2.0 かつ 高さ ≤ 200px(PR #2000 で実 P-WORLD バナー 480×193 例を帯と判定するため見直し)。
  3. スキップされなかった 最初の ref をメイン画像とする。走査中に寸法が取得できない ref に当たった場合は その ref をメイン画像として採用する(以降の帯判定は行わない)。すべて帯判定で消えた場合は 先頭 ref にフォールバックする。

GIF をソースにする場合(画像1枚メールを除く)

メイン画像として選んだ attachment_id が GIF のとき、約 3 秒再生相当の代表静止画(EP-009 経由で生成済みの _pworld_gif_still_attachment_id)があれば、その静止画の attachment_id をサムネイル生成のソースとする。静止画がない場合は 元の GIF 添付をソースとする。

例外(「画像1枚メール」)

P-WORLD 配信メールのうち、Gmail 表示相当のラッパー付きで 本文の主たる視覚コンテンツが 1 枚の画像のみである構成(挨拶行 Dear ...mailto<hr>単一の <div> 内にメイン img 1 つ → 注記・店舗名・フッターリンク等)に一致するものを「画像1枚メール」とする。実装では processed_html を正とし、次をすべて満たすときに該当する。

  • img 要素が ドキュメント全体で 1 つのみ(トラッキング用など追加 img が混入した場合は本例外に該当しない)。
  • 当該 imgsrcローカルアップロード配下の URL(取り込み後の正規化済み HTML)であること。
  • pworld_mail_image_ref が当該 mail_id1 行のみであること。

該当時は GIF 代表静止画への置き換えを行わず、当該 pworld_mail_image_refattachment_id元画像そのもの)をサムネイル元とする。

優先条件使用する attachment_id
1**「画像1枚メール」**の判定を満たす当該 ref の attachment_id(GIF でも静止画に差し替えない)
2上記以外(複数画像を含む通常メール。メイン画像は上記 メイン画像の定義 で選んだ ref)選んだ ref の attachment_id を起点とし、post_mime_type = image/gif かつ get_post_meta(attachment_id, '_pworld_gif_still_attachment_id') > 0 ならその静止画、否则同 attachment_id

サムネイル生成(1 件 vs 2 件)

メール由来の解決済み attachment_id(ソース)を pworld_hall_day_thumbnail に直接指させない。単体・合成のいずれも、新規メディアとしてサムネイル用ファイルを生成し、その新 attachment_id を upsert する(ソース添付は変更しない)。

1 件の場合(単体サムネイル生成)

ステップ内容
1上記解決ロジックで得たソース attachment_id の実ファイルを wp_get_image_editor で読み込む
2固定キャンバス(818x722)へ contain 方式で中央配置する。アスペクト比を維持したまま、縦または横のいずれか一辺がキャンバス上限に達するまで拡大・縮小する
3余白は processed_html から抽出した背景色で塗りつぶす(stylebackground-color、または bgcolor を優先)。抽出不能時は白(#ffffff)にフォールバックする
4ピクセル加工が不要でも、再エンコードして新規ファイルとして保存する(メール添付とサムネイルメディアを分離する)
5pworld-thumbnail-single-{mail_id}.jpg として WordPress メディアに保存し新 attachment_id を得る
6メディア保存が完全に成功し有効な attachment_id が確定した直後のみ、新 attachment に自動生成マーカー post meta を付与(後述)。失敗時はマーカーを付けない

2 件の場合(合成画像生成)

ステップ内容
1解決した 2 つの attachment_idattachment_a:先行メール、attachment_b:後行メール)の実ファイルを wp_get_image_editor で読み込む
2両画像を同一サイズに統一する(小さい方の高さ基準でリサイズ)
3横並び合成:左に attachment_a、右に attachment_b を配置
4pworld-composite-{mail_id_a}-{mail_id_b}.{ext} として WordPress メディアに保存し新 attachment_id を得る
5メディア保存が完全に成功し有効な attachment_id が確定した直後のみ、新 attachment に自動生成マーカー post meta を付与(後述)。失敗時はマーカーを付けない

単体・合成のいずれかで生成に失敗した場合は、生成途中の一時ファイルは wp_delete_file 等で削除し、attachment は作成しないマーカーは付与しない(下表)。

サムネイル生成失敗時のエラー処理(単体・合成共通)

リトライは行わない。pworld_hall_day_thumbnail にセンチネル値は置かない。

失敗箇所挙動ログpworld_hall_day_thumbnail
wp_get_image_editorfalse / WP_Error当該実行の生成処理を中止error_log、先頭タグ例 [PworldHallDayThumbnailAutoGenerate]単体: mail_id・ソース attachment_id / 合成: mail_id_amail_id_b・各ソース attachment_id、失敗理由変更しない
リサイズ・合成(resize / crop / ピクセル合成など)失敗同上(単体経路でピクセル加工が無い場合は該当しにくい)同上変更しない
メディア保存失敗(ファイル書き込み失敗、wp_insert_attachment 等失敗)生成済みの一時ファイルは wp_delete_file 等で削除。attachment は作成しない同上変更しない

生成失敗時はattachment_id も削除しない(既存サムネイルを維持する)。

旧サムネイル削除(自動生成分のみ)

upsert のに、同一 article_target_date × hall_name既存行があれば旧 attachment_id を退避する。新サムネイルの生成が成功し(単体・合成ともにメディア保存まで完了)、pworld_hall_day_thumbnail の upsert が成功した後に限り、退避した旧 attachment_id を下表で評価して削除する。生成失敗・メディア保存失敗・upsert 失敗のいずれかでは旧 attachment は削除しない

確認内容処理
退避した旧 attachment_id が存在する下記 meta を確認
get_post_meta(旧_attachment_id, '_pworld_hall_day_thumbnail_auto_generated') === '1'wp_delete_attachment(旧_attachment_id, true) で物理削除
上記 meta がない(手動登録画像)attachment は削除しない(upsert でレコードのみ上書き)

自動生成マーカー(WordPress post meta)

DB スキーマ変更なし。単体・合成で新規作成したサムネイル attachment について、メディア保存が完全に成功し有効な attachment_id が確定した直後にのみ _pworld_hall_day_thumbnail_auto_generated を付与する。保存失敗時は付与しない。メール由来のソース attachment には付与しない。

meta キー説明
_pworld_hall_day_thumbnail_auto_generated'1'自動生成フラグ(手動登録画像と区別)

pworld_hall_day_thumbnail への upsert

カラム
article_target_date入力の article_target_date
hall_nameHallEnum::from_string(hall_name)->value(正規化済みスラッグ)
attachment_id新規生成したサムネイル用 attachment_id(単体: pworld-thumbnail-single-*、合成: pworld-composite-*
ON DUPLICATE KEY UPDATEattachment_id を上書き

hall_nameunknown 判定

  • HallEnum::from_string()island / espasu / bigapple / uno のみ非 null を返す
  • それ以外(unknown・空文字・未登録ホール名)はすべて null → スキップ
  • 登録する hall_nameHallEnum::value(正規化済みスラッグ)を使用し、生文字列は使わない

エラー

条件ユーザー向け挙動メッセージ / ログ
admin-ajax で nonce 不正400 / エラーメッセージ返却wp_die() または JSON error レスポンス
手動取得時のクールダウン中(1分以内の再実行)ジョブ起動せず、クールダウン中メッセージpworld_archive_cooldown transient で判定
非同期ジョブで MySQL ロック取得失敗ジョブ状態は running のまま維持error_log[PworldArchiveAsyncRunner] 先頭)
非同期ジョブで \Throwableジョブ状態を error に更新error_log[PworldArchiveAsyncRunner] 先頭)
定期 Cron でロック取得失敗スキップ(ログに skipped/lock_not_acquiredPworldArchiveScheduledJobLogger::log() に記録
定期 Cron で \Throwableログに error として記録PworldArchiveScheduledJobLogger::log()status: error