Skip to content

Migrate modal to bootstrap5 native 4.4 - #6764

Open
dotani1111 wants to merge 40 commits into
EC-CUBE:4.4from
dotani1111:dev/migrate-modal-to-bootstrap5-native-4.4
Open

Migrate modal to bootstrap5 native 4.4#6764
dotani1111 wants to merge 40 commits into
EC-CUBE:4.4from
dotani1111:dev/migrate-modal-to-bootstrap5-native-4.4

Conversation

@dotani1111

@dotani1111 dotani1111 commented May 18, 2026

Copy link
Copy Markdown
Contributor

概要(Overview・Refs Issue)

jQuery 4.0 へのアップグレードに備え、コア JS と Twig インラインスクリプトから jQuery 依存を廃止し、Bootstrap 5 ネイティブ API・標準DOM API・fetch ベースの実装に置き換える。

方針(Policy)

  • $.modal() を bootstrap.Modal.getOrCreateInstance() へ
  • コアの $.ajax 利用を撤去し、CSRF と X-Requested-With を自動付与する window.eccubeFetch ラッパに集約。
    • プラグイン互換のため$.ajaxSetup() による CSRF 自動付与は残している
  • jQuery UI Sortable は SortableJS、QR コード生成は qrcode-generator に置換
  • Modal → eccubeFetch → コア JS → 各 Twig → fix の Phase で段階移行

実装に関する補足(Appendix)

仕様変更について

以下が変更点になります。

  • 商品詳細ギャラリーのアクセシビリティ強化: サムネが tabindex 無→ tabindex="0" + role="button" + キーボード操作対応。Shift+Tab時のフォーカス停止位置が変わるため(影響小)
  • $.modal() の $() プラグイン形式が EC-CUBE コアで動かなくなる可能性: BS5 は jQuery 依存を切ったので、コア側で読み込んでいる bootstrap.bundle.min.js には $.modal() の jQuery shim 無し。(影響大)
    プラグイン側で $('#xxx').modal('show') を呼んでいる場合は壊れる
  • .invalid-feedback 表示ロジックの違い:
    • 旧 jQuery .show() (inline-style 上書き)
    • 新 vanilla style.display = 'block'。
    • 動作は同じだが「祖父クラス由来の display:none を上書きする方法」が変わっている

テスト(Test)

以下を手動で動作確認しました (CodeRabbit 由来の指摘事項 + jQuery廃止全体のスモークテスト)。

フロント

  • 規格切替時の for...in 例外回避 (空 option 選択)
  • 複数商品の規格切替で原始値が他商品へ伝播しない
  • TOP メインビジュアル (オートプレイ + visibilitychange による停止/再開)
  • 商品詳細ギャラリー (サムネクリック / 矢印キー / Enter / Space、ARIA 対応)
  • カート追加 Modal (商品一覧 / 詳細から)
  • TOP の Block/news の slideDown/slideUp 連打耐性
  • 購入確認画面の顧客情報インライン編集 (失敗時は編集モード継続)
  • お届け先複数指定 (shipping_multiple)
  • 非会員フロー (ゲスト購入完了まで通し)

管理画面

  • レイアウト編集の block-edit リンクが毎回正しいブロックに遷移
  • 一括メール送信が順次実行・進捗バー段階表示
  • 顧客検索モーダルのページング・「設定」が 2 ページ目以降も動作
  • 商品検索モーダルのページング・「追加」が 2 ページ目以降も動作 (受注編集 + 出荷編集の両画面)
  • 支払方法編集の画像アップロード失敗で payment_image_error にメッセージ表示
  • メンテナンス解除失敗時に「削除完了」誤表示が出ない (DevTools Console で res.ok 起因 catch 流入を確認)
  • 受注編集 (会員検索 / 商品検索 / addOrderItemType / 移動確認モーダル)
  • 受注一覧 (ステータス変更 / 一括メール送信モーダル + 進捗バー)
  • 各種削除確認モーダル (会員 / 商品 / カテゴリ / タグ / 規格 / お届け先住所 / メンバー / テンプレート 等)
  • レイアウト編集のコードプレビュー / ブロック D&D 移動 / ブロック移動モーダル
  • 設定系 (delivery / delivery_edit / payment / mail / calendar / tax_rule)
  • Customer/edit のお届け先住所削除モーダル
  • SortableJS による並び替え (Product 規格 / カテゴリ / タグ + 配送 / 支払、行内入力欄のクリックと両立)
  • 2FA 設定画面の QR コード生成 (qrcode-generator)
  • フォーム textarea のリサイズ (Content/{block_edit, page_edit, css, js})
  • 受注の送り状番号 PUT (正常系 + 入力 200 文字超バリデーション失敗時のサーバ messages 表示)
  • 受注 PDF 出力 (二重 submit / 自ウィンドウ遷移しないこと)
  • CSV 出力項目設定の上下並び替え (境界 no-op)
  • 商品 CSV 一括登録の進捗バー (500 件 / 5 分割で段階表示動作確認)

未検証 (4.4 公式プラグインリリース後に要再確認)

*オーナーズストアに 4.4 対応の公式プラグインがまだ無いため、以下のフローは検証できていません。

  • Store/plugin_confirm.twig のプラグインインストール / アンインストール時の 進捗バー段階表示 (10% → 40% → 60% → 80% → 100%)
  • Store/plugin_search_panel.twig のプラグイン検索モーダル
  • Store/plugin_table_official.twig の公式プラグイン一覧表示
  • プラグイン enable / disable (E2E では最終完了のみカバー、UI フィードバックの目視確認は別途)

相談(Discussion)

互換で残した $.ajaxSetup() 呼び出しと、jQuery 自体の撤去は慎重に進める必要がある。
両方とも jQuery 経由で CSRF トークンを 全 AJAX に自動付与する 旧仕組み。本 PR では

  • コア側 (eccube.js / 各 Twig) は window.eccubeFetch ラッパに置き換え済み
  • ただし $.ajaxSetup() 自体は残し、jquery.js の読み込みも維持
    としている。
    プラグインなどがajaxを使っている可能性があり、$.ajaxSetupを外すとCSRFが外れてAPIが落ちる可能性
# src/Eccube/Resource/template/admin/default_frame.twig:28
# src/Eccube/Resource/template/default/default_frame.twig:43
$.ajaxSetup({ headers: { 'ECCUBE-CSRF-TOKEN': csrfToken } });

マイナーバージョン互換性保持のための制限事項チェックリスト

  • 既存機能の仕様変更はありません
  • フックポイントの呼び出しタイミングの変更はありません
  • フックポイントのパラメータの削除・データ型の変更はありません
  • twigファイルに渡しているパラメータの削除・データ型の変更はありません
  • Serviceクラスの公開関数の、引数の削除・データ型の変更はありません
  • 入出力ファイル(CSVなど)のフォーマット変更はありません

レビュワー確認項目

  • 動作確認
  • コードレビュー
  • E2E/Unit テスト確認(テストの追加・変更が必要かどうか)
  • 互換性が保持されているか
  • セキュリティ上の問題がないか
    • 権限を超えた操作が可能にならないか
    • 不要なファイルアップロードがないか
    • 外部へ公開されるファイルや機能の追加ではないか
    • テンプレートでのエスケープ漏れがないか

Summary by CodeRabbit

  • 新機能

    • QRコード生成ライブラリを更新し表示互換と安定性を向上しました
    • 並び替えUIをSortable.jsへ移行しドラッグ&ドロップ操作を改善しました
  • 改善

    • Bootstrapを5.3.8へ更新しスタイルやアクセシビリティを調整しました
    • 管理画面・フロントのスクリプトをネイティブDOMへ移行し応答性と保守性を向上しました
    • ファイルアップロード挙動とエディタのリサイズ処理を改善しました

dotani1111 and others added 26 commits May 18, 2026 16:52
jQuery 4.0.0 へのアップグレードに備え, `.modal('show')` / `.modal('hide')`
スタイルの jQuery プラグインシグネチャを, Bootstrap 5 のネイティブ API
(`bootstrap.Modal.getInstance()` / `getOrCreateInstance()`) に差し替える.

Made-with: Cursor
`$.ajaxSetup()` による CSRF ヘッダ付与は jQuery 前提のため, Twig の
インライン初期化を DOMContentLoaded 内に整理したうえで, jQuery 非依存の
`window.eccubeFetch` ラッパを追加する. 以降の Twig/JS で fetch API への
移行を段階的に進められるようにするための基盤.

互換のため `$.ajaxSetup()` 呼び出しは当面残し, jQuery の読み込みが終わって
から実施する.

Made-with: Cursor
jQuery 廃止の第一段として, 共通読み込みされる JS ファイルを native DOM API
ベースに書き直す.

- html/template/default/assets/js/{function,eccube}.js
- html/template/admin/assets/js/{function,file_manager,layout_design}.js
- html/template/install/assets/js/function.js

あわせて, jQuery UI Sortable の代替として sortablejs (^1.15.7) を追加し,
admin bundle からグローバルに利用できるようにする (bundle.js / admin.bundle.js).
フォーム送信中のローディングオーバーレイは `click` から document 全体の
`submit` イベントをトリガに変更し, 既存モーダルとの z-index 競合を解消する.

Made-with: Cursor
フロント側のインライン JS から jQuery を取り除き, native DOM API と
fetch ベースの実装に置き換える.

- TOP (index.twig): Slick carousel を vanilla JS 製オートプレイスライダへ
- Block/news.twig: slideDown/slideUp をカスタム vanilla 実装へ
- Product/list.twig, Product/detail.twig: カート追加 AJAX を fetch API + BS5 Modal ネイティブ API に置換. 商品詳細ギャラリーも Slick を外して vanilla で再実装
- Shopping/index.twig: `$(window).on('pageshow')` を window の addEventListener へ, 非会員編集フローや data-trigger リダイレクトを fetch + native DOM に置換
- Shopping/shipping_multiple.twig: 行の複製・属性書き換え・イベントバインドを native JS へ

Made-with: Cursor
install 系 Twig のインライン JS から jQuery を取り除く.

- frame.twig: SVG アイコンの遅延読み込みを $.ajax から fetch に置換
- complete.twig: プラグイン関連の AJAX ($.ajax / $.get) を fetch API へ,
  jQuery DOM 操作・イベントハンドラも native JS に書き直し

Made-with: Cursor
admin/Content 配下および admin/index.twig, 管理画面フォームテンプレートの
インライン JS を native DOM API / fetch / Bootstrap 5 ネイティブ API に
書き換える.

- Content/cache.twig: メンテナンスモード解除 POST を fetch に置換
- Content/{block,page}.twig: `on('input')` を addEventListener('input') へ
- Content/{block_edit,page_edit,css,js}.twig: jQuery UI `.resizable()` を
  CSS `resize: vertical` + ResizeObserver に置換. 保存ボタンの disable 制御と
  form submit ハンドラも native 化
- Content/layout_list.twig: `shown.bs.modal` を BS5 の addEventListener へ
- Content/file.twig: ブレッドクラム生成など DOM 操作を native JS へ
- Content/layout.twig: コンテキストメニュー (Popover) と block 移動ロジックを
  `new bootstrap.Popover` / insertBefore / insertAdjacentHTML / fetch で再実装
- index.twig: 売上ダッシュボードの $.ajax を fetch に置換
- Form/bootstrap_4_horizontal_layout.html.twig: フォーム補助 JS を native 化

Made-with: Cursor
- 規格管理 (class_name)、規格分類 (class_category)、カテゴリ (category)、
  タグ (tag) の並び替え/編集/削除 UI をネイティブ JS + SortableJS へ置換
- 規格 CSV/規格分類 CSV/カテゴリ CSV 登録画面の jQuery 依存を廃止
- 商品一覧 (index)、商品編集 (product) の Bootstrap Modal 呼び出しを
  Bootstrap 5 ネイティブ API へ置換
- bundle.js: sortablejs の ESM 名前空間 export から Sortable クラスを抽出して
  global へ公開するよう修正 (admin.bundle.js を再ビルド)

Made-with: Cursor
- $.Deferred チェーンをネイティブ Promise チェーンへ置換
- $.ajax / $.post を window.eccubeFetch + URLSearchParams へ置換
- DOM 操作を querySelector / createElement / insertBefore に置換
- .always() を .finally() へ置換

Made-with: Cursor
- mail.twig: $(function) → DOMContentLoaded、イベントハンドラをネイティブ JS へ
- mail_confirm.twig: 同上
- order_pdf.twig: $(function) + $('.btn-ec-regular').on('click') をネイティブ JS へ
- csv_shipping.twig: $('#upload-form').submit / $('#file-select').click をネイティブ JS へ
- order_item_type.twig: fnAddOrderItem の jQuery 依存を廃止、onclick の $(this) を this.closest('tr') へ置換

Made-with: Cursor
- edit.twig / shipping.twig: $(function), $.ajax, .find/.parents/.val/.attr などをネイティブ JS へ
- index.twig: チェックボックス一括/PDF出力/送り状番号 PUT を fetch + DOM API へ
- search_customer.twig / search_product.twig: モーダルの ajax と DOM 操作をネイティブ化、$collectionHolder を DOM 要素として扱う
- confirmationModal_js.twig: jQuery $.Deferred を ProgressDeferred 軽量クラスで代替し、4 種の updater (BulkStatus/SimpleStatus/BulkSendMail/SimpleSendMail) を window.eccubeFetch + ネイティブ DOM へ移行

Made-with: Cursor
Phase 2-7a: product / product_class / calendar / csv / payment_edit /
tax_rule / authority / member / authentication_setting /
plugin_table / plugin_table_official / template の 12 ファイルを
ネイティブ JS に移行。

Made-with: Cursor
Phase 2-7b: delivery / delivery_edit (Sortable.js) / mail (resize→CSS) /
payment / plugin_confirm ($.Deferred→Promise) / two_factor_auth_edit /
two_factor_auth_set (qrcode-generator) の 7 ファイルを移行。
qrcode-generator を npm 依存に追加し admin.bundle.js に同梱。

Made-with: Cursor
- eccubeFetch に X-Requested-With ヘッダを追加
  (Symfony isXmlHttpRequest() が false になり AJAX 空レスポンス)
- innerHTML → createContextualFragment でスクリプト実行を保証
  (edit.twig / shipping.twig / Product/index.twig)
- search_product.twig のモーダル ID を addProduct に修正
- copy-customer の forEach を null チェック付きに変更
  (order_Shipping_email が存在しないため TypeError で phone_number 未コピー)
- setup-fixtures.php: createUnsafeMutable → createImmutable で APP_ENV 汚染を防止

Made-with: Cursor
`window.eccubeFetch` は素の fetch を返すため, 旧 jQuery の
`err.response.json()` 互換オブジェクトは無く, ネットワークエラー時に
TypeError で例外が落ちる. また 4xx/5xx でも `response.ok` を判定して
いなかったため, `data.status != 'OK'` 経路に流れて `Update failed.`
だけが出てサーバ側 messages が表示できていなかった.

`response.ok` が false なら body を Promise.reject で `.catch` に回し,
.catch 側で `body.messages` をハンドルする形に統一.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
旧実装は default_frame.twig の `$.ajaxSetup` で全 Ajax に
ECCUBE-CSRF-TOKEN が付与されていたが, jQuery 廃止後の素の `fetch()` は
CSRF / X-Requested-With ヘッダが付かない. 顧客検索・商品検索のページネーションや
一括メールのプレビュー fetch などで管理画面の CSRF / XHR 判定に依存する
エンドポイントの挙動が変わる恐れがあるため, `window.eccubeFetch` に統一する.

- admin/Order/search_customer.twig: ページネーション
- admin/Order/search_product.twig: ページネーション
- admin/Order/edit.twig: 商品検索モーダル内ページネーション
- admin/Order/confirmationModal_js.twig: メール本文プレビュー

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
旧 jQuery `\$.Deferred` の `.always` は成功・失敗いずれも progress.notify を
呼んでいたが, 新実装は失敗時に `progress.reject()` のみで `notifyWith` を
呼ばないため, ConfirmationModal.progress() の `currentCount` が増えず,
1 件でも HTTP エラーが発生すると resolve に到達せずモーダルがハングしていた.

加えて per-item の `progress.reject()` は always callback を 1 件目の失敗で
即時発火させ, 残件の処理結果が反映される前に「完了」状態に遷移させていた.

`.catch` 内では `progress.notifyWith(null)` でカウンタを進めるだけにし,
fail メッセージは個別ログとして追加, 全件処理後に resolve が自然発火する
ようにする.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`bindContextMenuHandlers` は `document.querySelectorAll('.popover .list-group')`
の最後を「現在開いている popover のメニュー」として参照していたが,
BS5 の popover 閉鎖はアニメーション付きで, `hideOtherPopovers` 直後でも
他の `.popover` が DOM に残っている瞬間がある. 結果として誤った popover の
ボタンに click ハンドラが付与され, 別ブロックの並び替え/プレビューを呼んで
しまう恐れがあった.

BS5 が popover 表示時にトリガに付与する `aria-describedby` 属性から
対象 popover の DOM を一意に特定する方式に変更.

なお Sortable.js による block 移動後でも BS5 Popover インスタンスは要素に
紐付いたまま再利用可能 (instance store + container='body' で位置再計算)
であるため, ここでは dispose は追加していない.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
createUnsafeMutable → createImmutable の置換は妥当だが, 同タイミングで
変更された APP_ENV のデフォルト値 ('codeception' → 'dev') によって,
APP_ENV 未指定でこのスクリプトが直接実行された場合に開発環境 DB へ
フィクスチャを書き込んでしまう恐れがあった.

E2E 用途であることが明確なスクリプトなので 'codeception' に戻す.
admin01 系 dockerbuild の失敗もこれが原因の可能性がある.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…lt に置換

`addEventListener` で登録したハンドラは jQuery と異なり戻り値による
default 動作の抑止ができない. data-link が指定された type=submit ボタン
(`#calculate`) や PDF 出力リンク (`.pdf-print`) でネイティブ動作が走り,
二重 submit や本ウィンドウ遷移の原因になっていた.

- admin/Order/edit.twig: data-link ボタンの click ハンドラに preventDefault
- admin/Order/edit.twig: pdf-print の click ハンドラに preventDefault
- admin/Order/shipping.twig: pdf-print の click ハンドラに preventDefault

Enter キー抑止 (keydown) と confirmFormChange の click は既に
preventDefault が呼ばれているため不要だった `return false` のみ残っているが
害は無いのでそのままにしている.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
旧 jQuery 実装は \$op.first().prev().before(\$op) / \$op.last().next().after(\$op)
で, 先頭/末尾の選択肢に対して .prev()/.next() が空集合となり,
.before()/.after() が no-op になることで境界では何も動かなかった.

新ネイティブ実装は previousElementSibling / nextElementSibling が null の
場合に anchor を全選択肢の先頭または末尾にフォールバックさせていたため,
境界選択時に複数選択をまとめて先頭または末尾に移動させてしまっていた.
特に複数選択順と元順序が異なるケースで意図しない並び替えが発生する.

境界に到達している場合は早期 return して旧仕様 (no-op) と一致させる.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
旧 jQuery UI sortable は handle 指定が無く, 行 (.sortable-item) 全体が
ドラッグ可能だった. PR で追加された `handle: '.fa-bars'` はアイコン部分
(数 px 四方) のみドラッグ可能にしてしまい, 操作感が大きく退化していた.

handle を外し, 代わりに `filter: 'input, textarea, select, button, a'`
を指定することで:
- 行全体でドラッグ開始可能 (旧 UX 復元)
- 行内の入力欄・操作ボタン・リンクはクリック動作を阻害しない

`preventOnFilter: false` で filter にマッチした要素のクリック・タップが
preventDefault されないようにし, 既存ハンドラ (action-up/down/edit 等) と
共存させる.

対象:
- admin/Product/{category,class_category,class_name,tag}.twig
- admin/Setting/Shop/{delivery,delivery_edit,payment}.twig

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
旧 Slick carousel は内部で document.visibilitychange / focus blur を見て
非アクティブ時にオートプレイを止めていたが, vanilla 実装には引き継がれて
おらず, タブ非表示でも setInterval が回り続けていた.

タブが非表示になったら clearInterval で停止し, 再表示で再開する.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
旧 jQuery の slideToggle は内部キューでアニメーションを直列化していたが,
vanilla 実装には引き継がれていない. アニメーション途中に再度クリックされると
scrollHeight が遷移途中の値となり, 開閉の高さが意図せず縮む/膨らむ視覚崩れが
発生する.

対象要素に `data-animating` フラグを立て, 完了 (setTimeout コールバック) で
解除する. アニメ中はクリックを無視する形にして互換挙動に近づける.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
プラグイン enable/disable が失敗したときにフッター(閉じるボタン)を表示
した直後に bootstrapModal.hide() を呼んでモーダルごと閉じてしまっており,
ユーザがエラー文言を読めない状態になっていた.

失敗時は hide() を呼ばずモーダルを開いたままにし, 進捗バーだけ非表示,
フッターを表示, エラー文言を表示する形に変更. ユーザが手動でフッターの
閉じるボタンで閉じる導線とする.

加えて 1 段目の install_plugin_check_api の .catch で完全に握り潰して
いたエラーを console.warn で残すようにした.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
旧 Slick carousel が自動で付与していたキーボード操作 (左右矢印) と
スクリーンリーダ向けの状態通知 (aria 属性) が, vanilla 実装に
引き継がれずアクセシビリティが退化していた.

サムネイル (.slideThumb) に対して以下を追加:
- role="button" と tabindex="0" でフォーカス可能化
- 現在表示中サムネイルに aria-current="true"
- Enter / Space で選択
- ArrowLeft/Up, ArrowRight/Down で前後に移動 (循環) しフォーカス追従

selectSlide ヘルパに切り出し, click/keydown 双方から呼び出す形に整理.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`npm run build` で front.bundle.js / install.bundle.js とソースマップを
再生成する。4.4 ベースに rebase した結果、ソース側の jQuery 廃止内容と
bundle 内容を一致させるための再ビルド。

admin.bundle.js は rebase 中に PR 側 (theirs) を採用したものと再生成結果が
一致したため変更なし。CSS は本 PR の対象外のため commit しない。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 18, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

多数の Twig/JS/CSS を対象に jQuery 依存をネイティブ DOM/fetch/ResizeObserver/Sortable/qrcode-generator 等へ置換し、Bootstrap を 5.3.3→5.3.8 に更新、依存追加と共通 fetch 基盤を導入した一括移行。

Changes

全体(単一コホート:Vanilla JS 移行)

Layer / File(s) Summary
全変更一括確認
多数の Twig / JS / CSS / package.json / html/*
依存追加(qrcode-generator, sortablejs)とバンドル公開、Bootstrap CSS 差し替え(5.3.3→5.3.8)、window.eccubeFetch の導入、管理・フロント側の jQuery → Vanilla JS 移行(fetch, ResizeObserver, Sortable, FilePond CSRF 等)を含む大規模移植。

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • EC-CUBE/ec-cube#6700: Bootstrap/CSS およびバンドル関連の重複・類似変更があります。
  • EC-CUBE/ec-cube#6759: .env / APP_ENV の読み込みタイミング調整に関する類似変更が含まれます。

Suggested reviewers

  • ji-eunsoo
  • nanasess

Poem

"🐰 バニラの風に乗って跳ぶよ、
jQueryの殻をそっと脱ぎ、
fetchで夜道を渡り、
Sortableがリズム刻む列、
ニンジン齧ってレビュー乾杯!"

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@dotani1111
dotani1111 marked this pull request as ready for review May 18, 2026 15:24
`npm run build` で SCSS (app/bootstrap/style) から CSS とソースマップを
再生成する。sass / postcss バージョン差による出力ずれを取り込み、
リポジトリ同梱の CSS をビルド成果物と同期する。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (14)
src/Eccube/Resource/template/admin/Setting/Shop/tax_rule.twig-38-46 (1)

38-46: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

要素の存在確認が不足しています。

Line 42 で getElementById により取得した tr 要素が存在するかを確認せずに、Line 43 で querySelectorAll を呼び出しています。該当する ID の要素が存在しない場合、TypeError が発生します。

🛡️ 提案する修正
             document.querySelectorAll('.tax_rule_list_item td.action a.edit-button').forEach(function(btn) {
                 btn.addEventListener('click', function() {
                     var id = this.dataset.id;
                     var tr = document.getElementById('ex-tax_rule-' + id);
+                    if (!tr) return;
                     tr.querySelectorAll('.list').forEach(function(el) { el.style.display = 'none'; });
                     tr.querySelectorAll('.edit').forEach(function(el) { el.style.display = 'block'; });
                 });
             });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Eccube/Resource/template/admin/Setting/Shop/tax_rule.twig` around lines
38 - 46, The click handler for the edit buttons assumes tr =
document.getElementById('ex-tax_rule-' + id) always exists and calls
tr.querySelectorAll(...) which can throw; modify the handler inside
document.addEventListener('DOMContentLoaded', locate the function bound in
document.querySelectorAll('.tax_rule_list_item td.action a.edit-button') and
after computing var tr = document.getElementById('ex-tax_rule-' + id) add a
null-check (if (!tr) return; or similar) before calling tr.querySelectorAll(...)
so missing elements are safely ignored.
src/Eccube/Resource/template/admin/Setting/Shop/calendar.twig-38-46 (1)

38-46: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

要素の存在確認が不足しています。

Line 42 で getElementById により取得した tr 要素が存在するかを確認せずに、Line 43 で querySelectorAll を呼び出しています。該当する ID の要素が存在しない場合、TypeError が発生します。

🛡️ 提案する修正
             document.querySelectorAll('.calendar_list_item td.action a.edit-button').forEach(function(btn) {
                 btn.addEventListener('click', function() {
                     var id = this.dataset.id;
                     var tr = document.getElementById('ex-calendar-' + id);
+                    if (!tr) return;
                     tr.querySelectorAll('.list').forEach(function(el) { el.style.display = 'none'; });
                     tr.querySelectorAll('.edit').forEach(function(el) { el.style.display = 'block'; });
                 });
             });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Eccube/Resource/template/admin/Setting/Shop/calendar.twig` around lines
38 - 46, The click handler for '.calendar_list_item td.action a.edit-button'
assumes the row element retrieved by var tr =
document.getElementById('ex-calendar-' + id) always exists; add a null check
after that call and bail out (or return) if tr is falsy before calling
tr.querySelectorAll, so edit/display toggling only runs when the element is
found; update the event listener inside
document.addEventListener('DOMContentLoaded', referencing the variables id and
tr) to perform this existence check.
src/Eccube/Resource/template/admin/Content/page_edit.twig-52-59 (1)

52-59: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

editor 変数の存在確認が不足しています。

ResizeObserver のコールバック内で editor.resize() を呼び出していますが、editor が正常に初期化されたかを確認していません。Ace エディタの初期化が失敗した場合、ランタイムエラーが発生します。

🛡️ 提案する修正
         // CSS resize: vertical によるサイズ変更に追従させる
         var editorEl = document.getElementById('editor');
-        if (editorEl && typeof ResizeObserver !== 'undefined') {
+        if (editorEl && typeof ResizeObserver !== 'undefined' && editor) {
             var resizeObserver = new ResizeObserver(function() {
                 editor.resize();
             });
             resizeObserver.observe(editorEl);
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Eccube/Resource/template/admin/Content/page_edit.twig` around lines 52 -
59, ResizeObserver のコールバック内で editor が未定義だと例外になるため、コールバックで editor の存在と resize
メソッドの型を確認してから呼び出すよう修正してください(参照する識別子: editor, resizeObserver, editorEl,
ResizeObserver, editor.resize)。具体的には ResizeObserver のコールバック先で if (editor &&
typeof editor.resize === 'function') のようなガードを入れて安全に editor.resize()
を実行し、不要なエラーを防止してください。
src/Eccube/Resource/template/admin/Order/csv_shipping.twig-49-56 (1)

49-56: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

change ハンドラをクリック内で重複登録しないでください。

Line 52 の登録がクリックのたびに増えるため、同一ページ内で change が多重発火します。change は1回だけ外で登録してください。

差分案
-            document.getElementById('file-select').addEventListener('click', function() {
-                var fileInput = document.getElementById('admin_csv_import_import_file');
-                fileInput.click();
-                fileInput.addEventListener('change', function() {
-                    if (this.files.length) {
-                        document.getElementById('admin_csv_import_import_file_name').textContent = this.files[0].name;
-                    }
-                });
-            });
+            var fileInput = document.getElementById('admin_csv_import_import_file');
+            document.getElementById('file-select').addEventListener('click', function() {
+                fileInput.click();
+            });
+            fileInput.addEventListener('change', function() {
+                if (this.files.length) {
+                    document.getElementById('admin_csv_import_import_file_name').textContent = this.files[0].name;
+                }
+            });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Eccube/Resource/template/admin/Order/csv_shipping.twig` around lines 49 -
56, The click handler for the 'file-select' element currently registers the
'change' listener on the 'admin_csv_import_import_file' input every time it's
clicked, causing multiple firings; fix it by moving the
fileInput.addEventListener('change', ...) out of the
document.getElementById('file-select').addEventListener('click', ...) callback
so the 'change' handler is registered exactly once (or register it with the {
once: true } option), and leave the click handler to only call
fileInput.click(); reference the 'file-select' element, the
'admin_csv_import_import_file' input, and the 'change' event listener in your
edit.
src/Eccube/Resource/template/admin/Setting/System/member.twig-23-40 (1)

23-40: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

disabled 見た目の矢印でもリクエストが送られてしまいます。

a 要素は disabled クラスだけでは無効化されないため、先頭/末尾の行でも並び替え API が呼ばれます。クリック時に早期 return を入れてください。

修正案
         document.querySelectorAll('.action-down, .action-up').forEach(function(btn) {
             btn.addEventListener('click', function(e) {
                 e.preventDefault();
                 var self = this;
+                if (self.classList.contains('disabled')) {
+                    return false;
+                }
                 var current = this.closest('tr');
                 var backdrop = document.createElement('div');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Eccube/Resource/template/admin/Setting/System/member.twig` around lines
23 - 40, The click handler for the sort links (the anonymous function bound to
btn in the btn.addEventListener call) does not prevent action when the link has
the 'disabled' class, so requests are still sent; modify the handler to check if
the clicked element (self or this) has classList.contains('disabled') and if so
call e.preventDefault() and return early before creating the backdrop or calling
window.eccubeFetch, leaving the rest of the handler (finalize, backdrop logic,
and the eccubeFetch call) unchanged.
src/Eccube/Resource/template/admin/Setting/Shop/delivery.twig-43-46 (1)

43-46: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

追加したローディング用 backdrop だけを片付けてください。

ここで .modal-backdrop を全削除すると、この処理と無関係な Bootstrap モーダルの backdrop まで消します。moveSortNo() で挿入した要素を保持して、その 1 枚だけ remove() する形に絞ったほうが安全です。

修正例
+            var loadingBackdrop = null;
+
             function updateSortNo() {
                 var items = getItems();
                 var body = new URLSearchParams();
                 items.forEach(function(el, i) {
                     body.append(el.dataset.id, oldSortNos[i]);
@@
                 window.eccubeFetch('{{ url('admin_setting_shop_delivery_sort_no_move') }}', {
                     method: 'POST',
                     headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
                     body: body
                 }).finally(function() {
-                    document.querySelectorAll('.modal-backdrop').forEach(function(el) {
-                        el.remove();
-                    });
+                    if (loadingBackdrop) {
+                        loadingBackdrop.remove();
+                        loadingBackdrop = null;
+                    }
                 });
             }
@@
             function moveSortNo() {
-                document.body.insertAdjacentHTML('beforeend', '<div class="modal-backdrop show"></div>');
+                if (!loadingBackdrop) {
+                    loadingBackdrop = document.createElement('div');
+                    loadingBackdrop.className = 'modal-backdrop show';
+                    document.body.appendChild(loadingBackdrop);
+                }
                 updateSortNo();
                 redrawDisableAllows();
             }

Also applies to: 65-69

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Eccube/Resource/template/admin/Setting/Shop/delivery.twig` around lines
43 - 46, 現在の finally ブロックは document.querySelectorAll('.modal-backdrop')
を全削除しており、他の Bootstrap モーダルの backdrop を巻き込んでしまいます。修正としては moveSortNo() が挿入するロード用
backdrop を一意に識別して参照を保持し(例えば moveSortNo() 内で生成した要素を戻り値として返すか、生成時に data
属性や特定のクラスを付与してグローバル/親スコープに保存する)、finally ではその保持した要素だけを remove()
する形に変更してください(該当する箇所は moveSortNo() 呼び出し周辺と finally ブロック、同様の修正が必要な別箇所—コメント記載の
65-69 行にも適用)。
src/Eccube/Resource/template/admin/Store/authentication_setting.twig-52-56 (1)

52-56: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

CAPTCHA エラー表示をモーダル再表示時にリセットしてください。

400 を一度踏むと #captcha_error が表示されたままなので、閉じて開き直しても即エラー状態の UI になります。モーダルを開くタイミングでエラー表示と入力値を初期化しておくほうが自然です。

修正例
         var captchaModal = document.getElementById('captcha');
         if (captchaModal) {
             captchaModal.addEventListener('show.bs.modal', function() {
+                var error = document.getElementById('captcha_error');
+                if (error) error.style.display = 'none';
+                var input = document.getElementById('captcha_text');
+                if (input) input.value = '';
                 refreshCaptchaImage();
             });
         }

Also applies to: 83-85

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Eccube/Resource/template/admin/Store/authentication_setting.twig` around
lines 52 - 56, モーダルを開いたときに既存のエラー表示と入力値が残るため、show.bs.modal
ハンドラ内でエラー要素と入力値をリセットしてください: 現在の captchaModal の 'show.bs.modal'
ハンドラ(refreshCaptchaImage()
を呼んでいる箇所)に、エラー表示要素(#captcha_error)を非表示にし、キャプチャ入力要素(キャプチャ入力のセレクタ/id)を空にする処理を追加し、同様の処理をもう一箇所の
show.bs.modal ハンドラ(該当する 83-85 のリスナー)にも反映してください。
src/Eccube/Resource/template/admin/Order/index.twig-47-54 (1)

47-54: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

PDF出力(複数) でも同様に e.preventDefault() が必要

return false はリンクやボタンのデフォルト動作を防止しません。

🔧 修正案
             // PDF出力(複数)
             document.getElementById('bulkExportPdf').addEventListener('click', function(event) {
+                event.preventDefault();
                 window.open('', 'newwin', 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=yes, resizable=yes, width=700, height=700');
                 var form = document.getElementById('form_bulk');
                 form.action = "{{ url('admin_order_export_pdf') }}";
                 form.target = 'newwin';
                 form.submit();
-                return false;
             });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Eccube/Resource/template/admin/Order/index.twig` around lines 47 - 54,
The click handler attached to document.getElementById('bulkExportPdf') relies on
"return false" which does not reliably prevent the default action; update the
inline handler in the anonymous function to call event.preventDefault() at the
start (and remove the trailing "return false" if present) so the button/link
default is suppressed before opening the new window and submitting form
'form_bulk' to target 'newwin' (keep form.action = "{{
url('admin_order_export_pdf') }}" and form.target logic intact).
src/Eccube/Resource/template/admin/Order/index.twig-57-62 (1)

57-62: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

return false はイベントリスナー内では preventDefault として機能しない

addEventListener で登録されたイベントリスナー内では return false はデフォルト動作を防止しません。リンクのデフォルト動作(ページ遷移)を防ぐには e.preventDefault() が必要です。

🔧 修正案
             // PDF出力(単一)
             document.querySelectorAll('.pdf-print').forEach(function(el) {
-                el.addEventListener('click', function() {
+                el.addEventListener('click', function(e) {
+                    e.preventDefault();
                     window.open(this.href, 'newwin', 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=yes, resizable=yes, width=700, height=700');
-                    return false;
                 });
             });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Eccube/Resource/template/admin/Order/index.twig` around lines 57 - 62,
The click handler attached to elements selected by
document.querySelectorAll('.pdf-print') uses return false which does not prevent
default behavior in addEventListener callbacks; update the addEventListener
callback for '.pdf-print' to accept the event parameter (e) and call
e.preventDefault() before opening the popup, and remove the return false so the
link's default navigation is properly suppressed.
src/Eccube/Resource/template/default/Product/list.twig-55-65 (1)

55-65: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

数量バリデーション時の return true は不要で、e.preventDefault() の位置に問題あり

return true; はイベントリスナー内では特別な意味を持ちません。また、e.preventDefault() が数量チェックの後に呼ばれているため、数量が不正な場合にフォームのデフォルト動作が発生する可能性があります。

🔧 修正案
             // 個数フォームのチェック
             var quantity = form.parentElement ? form.parentElement.querySelector('.quantity') : null;
             if (quantity && Number(quantity.value) < 1) {
                 quantity.setCustomValidity('{{ 'front.product.invalid_quantity'|trans }}');
                 setTimeout(function() {
                     loadingOverlay('hide');
                 }, 100);
-                return true;
-            } else if (quantity) {
+                return;
+            }
+            if (quantity) {
                 quantity.setCustomValidity('');
             }
             e.preventDefault();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Eccube/Resource/template/default/Product/list.twig` around lines 55 - 65,
数量チェックブロックで不要な `return true;`
を削除し、イベントオブジェクトの既定動作抑止を不正数量判定の前に移動してください(`e.preventDefault()`
をフォーム送信ハンドラの先頭か、`if (quantity && Number(quantity.value) < 1)` に入る直前に置く)。具体的には
`quantity`, `form`, `setCustomValidity` と `loadingOverlay`
を使っている箇所を修正し、不正時はフォーム送信を止めてカスタムエラーメッセージをセットし、正常時は
`quantity.setCustomValidity('')` でクリアするようにしてください。
html/template/admin/assets/js/function.js-69-80 (1)

69-80: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

カード collapse を再展開すると矢印に上下両方のクラスが残ります。

Line 76-80 では fa-angle-up を追加していますが、hidden.bs.collapse 側で付けた fa-angle-down を外していません。1 回閉じて再度開くとアイコン状態が不安定になるので、shown 側でも fa-angle-down を外してください。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@html/template/admin/assets/js/function.js` around lines 69 - 80, The
shown.bs.collapse handler only adds 'fa-angle-up' but doesn't remove the
'fa-angle-down' class added in hidden.bs.collapse, causing both classes to
remain after reopen; update the shown.bs.collapse listener (the function
attached with el.addEventListener('shown.bs.collapse', ...)) to also remove
'fa-angle-down' from the icon before adding 'fa-angle-up' so the icon state is
toggled cleanly.
src/Eccube/Resource/template/admin/Order/edit.twig-106-113 (1)

106-113: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

グローバル変数の暗黙的な宣言

$collectionHolderindexformIdPrefixvar/let/const なしで代入されており、暗黙的にグローバル変数となっています。これは strict mode ではエラーになり、変数の意図しない上書きのリスクがあります。

🔧 変数宣言の追加
             // 商品追加
-            $collectionHolder = document.getElementById('table-form-field');
-            index = $collectionHolder.querySelectorAll('tbody > tr').length;
-            formIdPrefix = '`#order_OrderItems_`';
+            var $collectionHolder = document.getElementById('table-form-field');
+            var index = $collectionHolder.querySelectorAll('tbody > tr').length;
+            var formIdPrefix = '`#order_OrderItems_`';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Eccube/Resource/template/admin/Order/edit.twig` around lines 106 - 113,
The snippet in edit.twig assigns $collectionHolder, index, formIdPrefix (and
local vars like trs and row) without declarations, causing implicit global
variables; update the block to declare these with appropriate block-scoped
keywords (use const for DOM refs like $collectionHolder and formIdPrefix, let
for mutable counters like index and row/trs) so the variables are properly
scoped and avoid globals (refer to identifiers $collectionHolder, index,
formIdPrefix, trs, row).
src/Eccube/Resource/template/admin/Product/class_name.twig-64-73 (1)

64-73: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

eccubeFetch のエラーハンドリングが不足

eccubeFetch が失敗した場合(ネットワークエラーなど)、エラーがキャッチされず、ユーザーにフィードバックがありません。finally でバックドロップは削除されますが、エラー通知がありません。

🛡️ エラーハンドリング追加の提案
                 window.eccubeFetch('{{ url('admin_product_class_name_sort_no_move') }}', {
                     method: 'POST',
                     headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
                     body: body
+                }).catch(function(err) {
+                    alert('{{ 'admin.common.system_error'|trans }}');
                 }).finally(function() {
                     redrawDisableAllows();
                     document.querySelectorAll('.modal-backdrop').forEach(function(el) {
                         el.remove();
                     });
                 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Eccube/Resource/template/admin/Product/class_name.twig` around lines 64 -
73, The fetch call using eccubeFetch to
url('admin_product_class_name_sort_no_move') lacks error handling; wrap the
promise chain from eccubeFetch in a .catch(...) before .finally to log the error
(console.error) and show a user-facing notification (e.g., alert or existing UI
notifier) so failures are visible, while keeping the existing cleanup in
redrawDisableAllows() and removal of '.modal-backdrop' elements; update the call
site where eccubeFetch is invoked to add the .catch handler.
html/template/default/assets/css/style.css-4647-4715 (1)

4647-4715: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

RGBA の大文字表記で stylelint エラーが発生しています

Line 4647-4715 の RGBA(...)rgba(...) に統一してください。lint 失敗を防げます。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@html/template/default/assets/css/style.css` around lines 4647 - 4715, CSS
uses uppercase RGBA causing stylelint errors; replace all occurrences of "RGBA("
with lowercase "rgba(" in the affected rules (e.g., selectors .link-primary,
.link-secondary, .link-success, .link-info, .link-warning, .link-danger,
.link-light, .link-dark, .link-body-emphasis and their :hover/:focus variants)
so the color function is consistently lowercase and lint passes, keeping the
argument list and !important flags unchanged.
🧹 Nitpick comments (10)
src/Eccube/Resource/template/admin/Order/mail_confirm.twig (2)

23-27: ⚡ Quick win

return false の位置が不適切です。

submit() を実行した後に return false しても意味がありません。フォーム送信を条件付きで防ぎたい場合は、submit() の前に処理を記述してください。現状では return false は到達しても効果がありません。

♻️ 提案する修正
     document.getElementById('template-change').addEventListener('change', function() {
         document.getElementById('mode').value = 'change';
         document.getElementById('order-mail-form').submit();
-        return false;
     });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Eccube/Resource/template/admin/Order/mail_confirm.twig` around lines 23 -
27, The change handler attached to 'template-change' currently calls
document.getElementById('order-mail-form').submit() and then has an
unreachable/ineffective return false; update the listener on the
'template-change' element (the anonymous function bound to addEventListener) so
that you either remove the trailing return false or, if you intend to
conditionally prevent the native event, call event.preventDefault() or return
false before calling submit() (i.e., evaluate the condition and prevent default
first), and ensure you still set document.getElementById('mode').value =
'change' and then submit the form via
document.getElementById('order-mail-form').submit().

29-34: 💤 Low value

return false が冗長です。

Line 30 で既に e.preventDefault() を呼び出しているため、Line 33 の return false は不要です。

♻️ 提案する修正
     document.getElementById('back').addEventListener('click', function(e) {
         e.preventDefault();
         document.getElementById('mode').value = 'back';
         document.getElementById('order-mail-form').submit();
-        return false;
     });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Eccube/Resource/template/admin/Order/mail_confirm.twig` around lines 29 -
34, Remove the redundant "return false" from the click handler attached to
document.getElementById('back'); the handler already calls e.preventDefault(),
so delete the final "return false" in the anonymous function that sets
document.getElementById('mode').value = 'back' and calls
document.getElementById('order-mail-form').submit(); ensure the event listener
logic remains unchanged otherwise.
src/Eccube/Resource/template/admin/Content/cache.twig (1)

25-31: ⚡ Quick win

メンテナンス解除リクエストの失敗を検知してください。

Line 25-31 は失敗時の扱いがないため、解除失敗を見逃します。response.ok 判定と catch を追加してください。

差分案
-    fetch("{{ url('admin_disable_maintenance', { 'mode': 'auto_maintenance' }) }}", {
+    fetch("{{ url('admin_disable_maintenance', { 'mode': 'auto_maintenance' }) }}", {
         method: 'POST',
         headers: {
             'X-Requested-With': 'XMLHttpRequest',
             'ECCUBE-CSRF-TOKEN': csrfToken
         }
-    });
+    })
+    .then(function(response) {
+        if (!response.ok) {
+            throw new Error('disable maintenance failed');
+        }
+    })
+    .catch(function(error) {
+        console.error(error);
+    });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Eccube/Resource/template/admin/Content/cache.twig` around lines 25 - 31,
The fetch call to "{{ url('admin_disable_maintenance', { 'mode':
'auto_maintenance' }) }}" currently ignores failures; update the fetch
invocation that uses csrfToken so it checks response.ok in a .then handler and
treats non-OK responses as errors (e.g., log or show a user-facing
notification), and add a .catch to handle network/exception cases; ensure you
reference the same fetch call and csrfToken variable so failures during the
maintenance-disable request are detected and reported.
src/Eccube/Resource/template/admin/Content/css.twig (1)

56-56: ⚡ Quick win

デバッグ用の console.log は削除してください。

本番運用時の不要ログになっているため、削除を推奨します。

修正案
-                    console.log(annot[key]);
                     if (annot[key].type && annot[key].type == 'error') {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Eccube/Resource/template/admin/Content/css.twig` at line 56, Remove the
debug console.log from the template: delete the line that calls
console.log(annot[key]) in the admin Content css.twig so no debug output is
emitted in production; if you need to keep behavior, replace it with a proper
debug flag check or server-side logger invocation instead of console.log,
referencing the usage of the annot and key variables in the template.
src/Eccube/Resource/template/admin/Order/shipping.twig (1)

129-133: 💤 Low value

return false は不要

addEventListener 内の return false は特別な意味を持ちません。また、form.submit() の後なので実害はありませんが、削除してコードを明確にできます。

♻️ 修正案
             document.getElementById('addShipping').addEventListener('click', function() {
                 document.getElementById('form_add_shipping').value = '1';
                 document.getElementById('form1').submit();
-                return false;
             });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Eccube/Resource/template/admin/Order/shipping.twig` around lines 129 -
133, The anonymous click handler attached to the element with id 'addShipping'
sets 'form_add_shipping' and calls 'form1'.submit(), then returns false; remove
the trailing "return false" from the event listener to avoid a misleading/no-op
since addEventListener handlers ignore return values—update the listener
attached to document.getElementById('addShipping') so it only sets
document.getElementById('form_add_shipping').value and calls
document.getElementById('form1').submit().
html/template/install/assets/js/function.js (1)

10-27: 💤 Low value

wrapper 変数の重複宣言を整理可能

同じ関数スコープ内で var wrapper が3回宣言されています。動作上は問題ありませんが、関数の先頭で一度だけ宣言する方が明確です。

♻️ リファクタリング案
     function applyBreakpoint() {
+        var wrapper = document.getElementById('wrapper');
         if (window.innerWidth < 768) {
             document.body.classList.add('sp_view');
             document.body.classList.remove('md_view', 'pc_view');
-            var wrapper = document.getElementById('wrapper');
             if (wrapper) wrapper.classList.remove('sidebar-open');
         } else if (window.innerWidth < 992) {
             document.body.classList.remove('sp_view', 'pc_view');
             document.body.classList.add('md_view');
-            var wrapper = document.getElementById('wrapper');
             if (wrapper) wrapper.classList.add('sidebar-open');
         } else {
             document.body.classList.remove('sp_view', 'md_view');
             document.body.classList.add('pc_view');
-            var wrapper = document.getElementById('wrapper');
             if (wrapper) wrapper.classList.add('sidebar-open');
         }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@html/template/install/assets/js/function.js` around lines 10 - 27, In
applyBreakpoint(), remove the repeated var wrapper declarations by declaring a
single wrapper variable at the top of the function (e.g., var wrapper =
document.getElementById('wrapper');) and then reuse that wrapper reference in
each conditional branch where document.getElementById('wrapper') is currently
called; update the branches that call wrapper.classList.add/remove to use this
single wrapper variable instead.
src/Eccube/Resource/template/default/Product/list.twig (1)

87-93: 💤 Low value

複数メッセージがある場合、最後のメッセージのみ表示される

forEachtextContent を上書きしているため、data.messages に複数のメッセージがある場合は最後のメッセージのみが表示されます。意図的な動作であれば問題ありませんが、全メッセージを表示したい場合は結合が必要です。

♻️ 全メッセージを表示する場合の修正案
                     var modalHeader = document.getElementById('ec-modal-header');
                     if (modalHeader && data.messages) {
-                        data.messages.forEach(function(msg) {
-                            modalHeader.textContent = msg;
-                        });
+                        modalHeader.textContent = data.messages.join('\n');
                     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Eccube/Resource/template/default/Product/list.twig` around lines 87 - 93,
forEach currently overwrites modalHeader.textContent so only the last message
shows; replace the loop in the then handler so that when modalHeader and
data.messages exist you set modalHeader.textContent to the joined messages (e.g.
data.messages.join('\n') or join(' — ')) instead of repeatedly assigning inside
data.messages.forEach; update the block referencing modalHeader and
data.messages to build the combined string and assign it once.
src/Eccube/Resource/template/admin/Content/file.twig (1)

106-123: ⚖️ Poor tradeoff

document.execCommand('copy') は非推奨APIへの移行を検討してください

document.execCommand('copy') は公式に非推奨とされており、今後削除される可能性があります。モダンな navigator.clipboard.writeText() への移行が推奨されます。ただし、Clipboard API は HTTPS または localhost でのみ動作するため、HTTP環境での互換性が必要な場合はフォールバック機構(document.execCommand('copy') の従来の使用など)を実装してください。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Eccube/Resource/template/admin/Content/file.twig` around lines 106 - 123,
Replace the deprecated document.execCommand('copy') call in the focus handler
attached to elements selected by '.copy-file-path input' with the modern
Clipboard API: use navigator.clipboard.writeText() to copy the input.value (or
selected text) and fall back to the existing document.execCommand('copy')
behavior when navigator.clipboard is unavailable (or when running on insecure
origins). Keep the existing logic that finds the closest tr, selects the
'.action-copy' element and calls setTooltipTitle(actionCopy,
'admin.common.copy_path_complete'|trans) and shows the bootstrap.Tooltip
instance after a short timeout; ensure success and failure branches both trigger
appropriate tooltip/show behavior and any errors are caught/handled.
src/Eccube/Resource/template/default/Product/detail.twig (1)

255-260: 💤 Low value

複数メッセージが存在する場合、最後のメッセージのみ表示される

forEach 内で textContent を上書きしているため、data.messages に複数のメッセージがある場合、最後のメッセージのみがモーダルヘッダーに表示されます。

💡 複数メッセージを連結する場合の修正案
                     var modalHeader = document.getElementById('ec-modal-header');
                     if (modalHeader && data.messages) {
-                        data.messages.forEach(function(msg) {
-                            modalHeader.textContent = msg;
-                        });
+                        modalHeader.textContent = data.messages.join(' ');
                     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Eccube/Resource/template/default/Product/detail.twig` around lines 255 -
260, The modal header update loop currently overwrites modalHeader.textContent
inside the data.messages.forEach, so only the last message appears; change this
to concatenate all messages (e.g., join data.messages with a separator like " "
or "\n" or build a single string) and set modalHeader.textContent once, or
create/appended text nodes for each message instead; update the code around
modalHeader, data.messages, and the forEach usage to produce a combined string
and assign it to modalHeader.textContent (or append nodes) rather than
repeatedly overwriting it.
html/template/admin/assets/js/file_manager.js (1)

541-561: 💤 Low value

ES6 構文(let、アロー関数)の使用が他のコードと不一致

この関数では let やアロー関数 (.find(function(child) { ... }) 内で == を使用) を使用していますが、ファイル内の他の部分では var と通常の function を使用しています。ブラウザ互換性の観点から、一貫した ES5 スタイルを使用することを検討してください。

♻️ ES5 スタイルへの統一案
     eccube.fileManager.buildNodeRecursive = function(node, nodeEl, path, idx) {
         if (idx < path.length) {
-            let item  = path[idx];
-            let dir   = node.children.find(function(child) { return child.name == item; });
+            var item  = path[idx];
+            var dir   = null;
+            for (var i = 0; i < node.children.length; i++) {
+                if (node.children[i].name == item) {
+                    dir = node.children[i];
+                    break;
+                }
+            }
             if (!dir) {
                 node.children.push(dir = {name: item, children: []});
             }

-            let dirEl = nodeEl.querySelector('ul');
+            var dirEl = nodeEl.querySelector('ul');
             if (!dirEl) {
                 dirEl = document.createElement('ul');
-                let li = document.createElement('li');
+                var li = document.createElement('li');
                 li.textContent = path[idx];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@html/template/admin/assets/js/file_manager.js` around lines 541 - 561, The
function eccube.fileManager.buildNodeRecursive uses ES6 `let` and a loose
equality check; update it to ES5 style by replacing `let` declarations (for
variables `item`, `dir`, `dirEl`, `li`) with `var`, ensure the callback to
`node.children.find` is a normal function expression (already is) and change the
`==` comparison to strict `===` when comparing `child.name` to `item`; also
remove the stray `console.log(dirEl)` to match the file's style and
browser-compatibility expectations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@html/template/default/assets/css/style.css`:
- Line 1707: Replace the nonstandard Firefox placeholder pseudo-class with the
standardized :placeholder-shown in all affected selectors: change occurrences
like ".form-floating > .form-control:not(:-moz-placeholder)" (and the similar
selectors at the other two occurrences) to use the corresponding standard
pseudo-class (e.g. ".form-floating > .form-control:not(:placeholder-shown)") so
the floating label logic matches Bootstrap 5.3.8; update all three identical
selectors to ensure consistent behavior across browsers.

In `@html/template/default/assets/js/eccube.js`:
- Around line 58-67: The code can throw when classcat2 is undefined if
eccube.productsClassCategories[product_id][classcat_id1] or
eccube.classCategories[classcat_id1] is missing; change the assignment logic
around classcat2 (the branch that sets classcat2 using
eccube.productsClassCategories and eccube.classCategories) to defensively
fallback to an empty object (e.g., {}) when the looked-up value is falsy, so the
subsequent for...in loop and the call to checkStock receive an empty object
instead of undefined; update references where classcat2 is used (including the
code that iterates over it and the checkStock invocation) to rely on this safe
fallback.
- Around line 111-143: The code currently stores product_code_origin,
product_cart_origin and price01_origin on this as single shared values and
queries document for elements, causing cross-item overwrites; change these to be
stored per-product (keyed by product_id or by form identifier, similar to
price02_origin) and when querying elements use formParent (or formEl) instead of
document so each form's productCode, cartbtn and price01 are discovered within
the form; update the checks/assignments that reference product_code_origin,
product_cart_origin and price01_origin to read/write into the per-product map
(e.g., this.product_code_origin[product_id]) and mirror how price02_origin is
handled, and apply the same fix to the other block noted (lines ~154-185).

In `@src/Eccube/Resource/template/admin/Content/layout.twig`:
- Around line 183-186: The current logic mutates blockEdit's onclick string in
place using replace on the DUMMY_BLOCK_ID, so after the first replacement the
template URL is lost and subsequent edits point to the wrong block; to fix,
store the original onclick template (containing
Eccube\\Controller\\Admin\\Content\\LayoutController::DUMMY_BLOCK_ID) on the
element (e.g., data-original-onclick) when first encountered, and on each open
use that original template to build the new onclick by replacing the
DUMMY_BLOCK_ID with the current blockId, then set
blockEdit.setAttribute('onclick', newOnclick'); ensure the code that checks/sets
data-original-onclick runs before any replacements so the template is preserved
for repeated uses.

In `@src/Eccube/Resource/template/admin/Order/confirmationModal_js.twig`:
- Around line 320-328: BulkSendMail.getPromises currently fires all requests in
parallel causing inaccurate progress; change it to run sequentially like
BulkStatusUpdate: collect the checked inputs' URLs and chain calls to
ConfirmationModal.prototype.getPromises (or wrap it to return a Promise) using a
reducer or explicit promise chain so each URL is processed one after another,
await each call before continuing, and propagate progress updates from each step
to the provided progress callback.

In `@src/Eccube/Resource/template/admin/Order/search_customer.twig`:
- Around line 12-55: Event handlers bound directly to elements (selectors
'.set-customer' and '`#customer_pagination` a') are lost after replacing innerHTML
of the container 'searchCustomerModalList'; change to event delegation on a
stable parent (e.g., the modal or the element with id 'searchCustomerModalList')
so clicks bubble and are handled for dynamically injected content. Specifically,
remove direct forEach bindings and instead attach a single click listener to the
container (document.getElementById('searchCustomerModalList') or the modal root)
that checks event.target/closest('.set-customer') for customer selection (then
call window.eccubeFetch(..., new URLSearchParams({'id': ...})) and update fields
like order_CustomerId/order_Customer/order_name_name01 etc.) and checks
event.target/closest('`#customer_pagination` a') for pagination clicks
(preventDefault, clear list.innerHTML, fetch the href and set list.innerHTML),
preserving existing success/error handling and modal hide logic.

In `@src/Eccube/Resource/template/admin/Order/search_product.twig`:
- Around line 102-118: The pagination click handlers are bound too early and not
rebound after innerHTML replacement; change to a single delegated listener (on a
stable parent like document or the modal container) that listens for click
events and filters for '`#product_pagination` a' targets, prevents default, skips
if the anchor's parent has class 'active', clears 'searchProductModalList',
calls window.eccubeFetch(href) and replaces the list HTML, and handles
errors—this ensures pagination links added later still work without rebinding.

In `@src/Eccube/Resource/template/admin/Setting/Shop/payment_edit.twig`:
- Around line 84-85: In the initfile event handler fix the incorrect element ID:
replace the getElementById call that looks up 'product_image_error' with
'payment_image_error' so the variable el refers to the correct DOM node (i.e.
update the line using document.getElementById('product_image_error') inside the
initfile handler to document.getElementById('payment_image_error')).

In `@src/Eccube/Resource/template/admin/Store/plugin_table_official.twig`:
- Around line 48-54: 現在のコード calls eccubeFetch(...).then(function() { ... }) but
ignores the response and treats any HTTP status as success; change the .then
callback to accept the fetch response (e.g., res), check res.ok, parse JSON
(e.g., res.json()) and only set message.textContent = '削除が完了しました。' and update
deleteLog (result.data.log) when res.ok is true; on non-ok, parse the error body
if available and show an error message (or call showFinal with an error state).
Update the anonymous function around eccubeFetch to use the response/result
variables consistently (reference eccubeFetch, the .then handler,
result.data.log, message, and showFinal) so HTTP 4xx/5xx are handled like in
Order/index.twig and Setting/System/member.twig.

In `@src/Eccube/Resource/template/default/Shopping/index.twig`:
- Around line 161-169: The toggleDisplay calls are executed outside the fetch
Promise chain causing UI switches before fetch completes; move the three
toggleDisplay('.non-customer-display', true),
toggleDisplay('.non-customer-edit', false), toggleDisplay('.mod-button', false)
into the Promise handlers: place them inside the .then() block so they only run
after a successful fetch (if you want to keep edit state on failure), or into
.finally() if you want the UI reverted regardless of success/failure; keep
loadingOverlay('hide') where it is (inside finally) so the overlay still hides
after completion.

---

Minor comments:
In `@html/template/admin/assets/js/function.js`:
- Around line 69-80: The shown.bs.collapse handler only adds 'fa-angle-up' but
doesn't remove the 'fa-angle-down' class added in hidden.bs.collapse, causing
both classes to remain after reopen; update the shown.bs.collapse listener (the
function attached with el.addEventListener('shown.bs.collapse', ...)) to also
remove 'fa-angle-down' from the icon before adding 'fa-angle-up' so the icon
state is toggled cleanly.

In `@html/template/default/assets/css/style.css`:
- Around line 4647-4715: CSS uses uppercase RGBA causing stylelint errors;
replace all occurrences of "RGBA(" with lowercase "rgba(" in the affected rules
(e.g., selectors .link-primary, .link-secondary, .link-success, .link-info,
.link-warning, .link-danger, .link-light, .link-dark, .link-body-emphasis and
their :hover/:focus variants) so the color function is consistently lowercase
and lint passes, keeping the argument list and !important flags unchanged.

In `@src/Eccube/Resource/template/admin/Content/page_edit.twig`:
- Around line 52-59: ResizeObserver のコールバック内で editor が未定義だと例外になるため、コールバックで
editor の存在と resize メソッドの型を確認してから呼び出すよう修正してください(参照する識別子: editor, resizeObserver,
editorEl, ResizeObserver, editor.resize)。具体的には ResizeObserver のコールバック先で if
(editor && typeof editor.resize === 'function') のようなガードを入れて安全に editor.resize()
を実行し、不要なエラーを防止してください。

In `@src/Eccube/Resource/template/admin/Order/csv_shipping.twig`:
- Around line 49-56: The click handler for the 'file-select' element currently
registers the 'change' listener on the 'admin_csv_import_import_file' input
every time it's clicked, causing multiple firings; fix it by moving the
fileInput.addEventListener('change', ...) out of the
document.getElementById('file-select').addEventListener('click', ...) callback
so the 'change' handler is registered exactly once (or register it with the {
once: true } option), and leave the click handler to only call
fileInput.click(); reference the 'file-select' element, the
'admin_csv_import_import_file' input, and the 'change' event listener in your
edit.

In `@src/Eccube/Resource/template/admin/Order/edit.twig`:
- Around line 106-113: The snippet in edit.twig assigns $collectionHolder,
index, formIdPrefix (and local vars like trs and row) without declarations,
causing implicit global variables; update the block to declare these with
appropriate block-scoped keywords (use const for DOM refs like $collectionHolder
and formIdPrefix, let for mutable counters like index and row/trs) so the
variables are properly scoped and avoid globals (refer to identifiers
$collectionHolder, index, formIdPrefix, trs, row).

In `@src/Eccube/Resource/template/admin/Order/index.twig`:
- Around line 47-54: The click handler attached to
document.getElementById('bulkExportPdf') relies on "return false" which does not
reliably prevent the default action; update the inline handler in the anonymous
function to call event.preventDefault() at the start (and remove the trailing
"return false" if present) so the button/link default is suppressed before
opening the new window and submitting form 'form_bulk' to target 'newwin' (keep
form.action = "{{ url('admin_order_export_pdf') }}" and form.target logic
intact).
- Around line 57-62: The click handler attached to elements selected by
document.querySelectorAll('.pdf-print') uses return false which does not prevent
default behavior in addEventListener callbacks; update the addEventListener
callback for '.pdf-print' to accept the event parameter (e) and call
e.preventDefault() before opening the popup, and remove the return false so the
link's default navigation is properly suppressed.

In `@src/Eccube/Resource/template/admin/Product/class_name.twig`:
- Around line 64-73: The fetch call using eccubeFetch to
url('admin_product_class_name_sort_no_move') lacks error handling; wrap the
promise chain from eccubeFetch in a .catch(...) before .finally to log the error
(console.error) and show a user-facing notification (e.g., alert or existing UI
notifier) so failures are visible, while keeping the existing cleanup in
redrawDisableAllows() and removal of '.modal-backdrop' elements; update the call
site where eccubeFetch is invoked to add the .catch handler.

In `@src/Eccube/Resource/template/admin/Setting/Shop/calendar.twig`:
- Around line 38-46: The click handler for '.calendar_list_item td.action
a.edit-button' assumes the row element retrieved by var tr =
document.getElementById('ex-calendar-' + id) always exists; add a null check
after that call and bail out (or return) if tr is falsy before calling
tr.querySelectorAll, so edit/display toggling only runs when the element is
found; update the event listener inside
document.addEventListener('DOMContentLoaded', referencing the variables id and
tr) to perform this existence check.

In `@src/Eccube/Resource/template/admin/Setting/Shop/delivery.twig`:
- Around line 43-46: 現在の finally ブロックは
document.querySelectorAll('.modal-backdrop') を全削除しており、他の Bootstrap モーダルの
backdrop を巻き込んでしまいます。修正としては moveSortNo() が挿入するロード用 backdrop を一意に識別して参照を保持し(例えば
moveSortNo() 内で生成した要素を戻り値として返すか、生成時に data
属性や特定のクラスを付与してグローバル/親スコープに保存する)、finally ではその保持した要素だけを remove()
する形に変更してください(該当する箇所は moveSortNo() 呼び出し周辺と finally ブロック、同様の修正が必要な別箇所—コメント記載の
65-69 行にも適用)。

In `@src/Eccube/Resource/template/admin/Setting/Shop/tax_rule.twig`:
- Around line 38-46: The click handler for the edit buttons assumes tr =
document.getElementById('ex-tax_rule-' + id) always exists and calls
tr.querySelectorAll(...) which can throw; modify the handler inside
document.addEventListener('DOMContentLoaded', locate the function bound in
document.querySelectorAll('.tax_rule_list_item td.action a.edit-button') and
after computing var tr = document.getElementById('ex-tax_rule-' + id) add a
null-check (if (!tr) return; or similar) before calling tr.querySelectorAll(...)
so missing elements are safely ignored.

In `@src/Eccube/Resource/template/admin/Setting/System/member.twig`:
- Around line 23-40: The click handler for the sort links (the anonymous
function bound to btn in the btn.addEventListener call) does not prevent action
when the link has the 'disabled' class, so requests are still sent; modify the
handler to check if the clicked element (self or this) has
classList.contains('disabled') and if so call e.preventDefault() and return
early before creating the backdrop or calling window.eccubeFetch, leaving the
rest of the handler (finalize, backdrop logic, and the eccubeFetch call)
unchanged.

In `@src/Eccube/Resource/template/admin/Store/authentication_setting.twig`:
- Around line 52-56: モーダルを開いたときに既存のエラー表示と入力値が残るため、show.bs.modal
ハンドラ内でエラー要素と入力値をリセットしてください: 現在の captchaModal の 'show.bs.modal'
ハンドラ(refreshCaptchaImage()
を呼んでいる箇所)に、エラー表示要素(#captcha_error)を非表示にし、キャプチャ入力要素(キャプチャ入力のセレクタ/id)を空にする処理を追加し、同様の処理をもう一箇所の
show.bs.modal ハンドラ(該当する 83-85 のリスナー)にも反映してください。

In `@src/Eccube/Resource/template/default/Product/list.twig`:
- Around line 55-65: 数量チェックブロックで不要な `return true;`
を削除し、イベントオブジェクトの既定動作抑止を不正数量判定の前に移動してください(`e.preventDefault()`
をフォーム送信ハンドラの先頭か、`if (quantity && Number(quantity.value) < 1)` に入る直前に置く)。具体的には
`quantity`, `form`, `setCustomValidity` と `loadingOverlay`
を使っている箇所を修正し、不正時はフォーム送信を止めてカスタムエラーメッセージをセットし、正常時は
`quantity.setCustomValidity('')` でクリアするようにしてください。

---

Nitpick comments:
In `@html/template/admin/assets/js/file_manager.js`:
- Around line 541-561: The function eccube.fileManager.buildNodeRecursive uses
ES6 `let` and a loose equality check; update it to ES5 style by replacing `let`
declarations (for variables `item`, `dir`, `dirEl`, `li`) with `var`, ensure the
callback to `node.children.find` is a normal function expression (already is)
and change the `==` comparison to strict `===` when comparing `child.name` to
`item`; also remove the stray `console.log(dirEl)` to match the file's style and
browser-compatibility expectations.

In `@html/template/install/assets/js/function.js`:
- Around line 10-27: In applyBreakpoint(), remove the repeated var wrapper
declarations by declaring a single wrapper variable at the top of the function
(e.g., var wrapper = document.getElementById('wrapper');) and then reuse that
wrapper reference in each conditional branch where
document.getElementById('wrapper') is currently called; update the branches that
call wrapper.classList.add/remove to use this single wrapper variable instead.

In `@src/Eccube/Resource/template/admin/Content/cache.twig`:
- Around line 25-31: The fetch call to "{{ url('admin_disable_maintenance', {
'mode': 'auto_maintenance' }) }}" currently ignores failures; update the fetch
invocation that uses csrfToken so it checks response.ok in a .then handler and
treats non-OK responses as errors (e.g., log or show a user-facing
notification), and add a .catch to handle network/exception cases; ensure you
reference the same fetch call and csrfToken variable so failures during the
maintenance-disable request are detected and reported.

In `@src/Eccube/Resource/template/admin/Content/css.twig`:
- Line 56: Remove the debug console.log from the template: delete the line that
calls console.log(annot[key]) in the admin Content css.twig so no debug output
is emitted in production; if you need to keep behavior, replace it with a proper
debug flag check or server-side logger invocation instead of console.log,
referencing the usage of the annot and key variables in the template.

In `@src/Eccube/Resource/template/admin/Content/file.twig`:
- Around line 106-123: Replace the deprecated document.execCommand('copy') call
in the focus handler attached to elements selected by '.copy-file-path input'
with the modern Clipboard API: use navigator.clipboard.writeText() to copy the
input.value (or selected text) and fall back to the existing
document.execCommand('copy') behavior when navigator.clipboard is unavailable
(or when running on insecure origins). Keep the existing logic that finds the
closest tr, selects the '.action-copy' element and calls
setTooltipTitle(actionCopy, 'admin.common.copy_path_complete'|trans) and shows
the bootstrap.Tooltip instance after a short timeout; ensure success and failure
branches both trigger appropriate tooltip/show behavior and any errors are
caught/handled.

In `@src/Eccube/Resource/template/admin/Order/mail_confirm.twig`:
- Around line 23-27: The change handler attached to 'template-change' currently
calls document.getElementById('order-mail-form').submit() and then has an
unreachable/ineffective return false; update the listener on the
'template-change' element (the anonymous function bound to addEventListener) so
that you either remove the trailing return false or, if you intend to
conditionally prevent the native event, call event.preventDefault() or return
false before calling submit() (i.e., evaluate the condition and prevent default
first), and ensure you still set document.getElementById('mode').value =
'change' and then submit the form via
document.getElementById('order-mail-form').submit().
- Around line 29-34: Remove the redundant "return false" from the click handler
attached to document.getElementById('back'); the handler already calls
e.preventDefault(), so delete the final "return false" in the anonymous function
that sets document.getElementById('mode').value = 'back' and calls
document.getElementById('order-mail-form').submit(); ensure the event listener
logic remains unchanged otherwise.

In `@src/Eccube/Resource/template/admin/Order/shipping.twig`:
- Around line 129-133: The anonymous click handler attached to the element with
id 'addShipping' sets 'form_add_shipping' and calls 'form1'.submit(), then
returns false; remove the trailing "return false" from the event listener to
avoid a misleading/no-op since addEventListener handlers ignore return
values—update the listener attached to document.getElementById('addShipping') so
it only sets document.getElementById('form_add_shipping').value and calls
document.getElementById('form1').submit().

In `@src/Eccube/Resource/template/default/Product/detail.twig`:
- Around line 255-260: The modal header update loop currently overwrites
modalHeader.textContent inside the data.messages.forEach, so only the last
message appears; change this to concatenate all messages (e.g., join
data.messages with a separator like " " or "\n" or build a single string) and
set modalHeader.textContent once, or create/appended text nodes for each message
instead; update the code around modalHeader, data.messages, and the forEach
usage to produce a combined string and assign it to modalHeader.textContent (or
append nodes) rather than repeatedly overwriting it.

In `@src/Eccube/Resource/template/default/Product/list.twig`:
- Around line 87-93: forEach currently overwrites modalHeader.textContent so
only the last message shows; replace the loop in the then handler so that when
modalHeader and data.messages exist you set modalHeader.textContent to the
joined messages (e.g. data.messages.join('\n') or join(' — ')) instead of
repeatedly assigning inside data.messages.forEach; update the block referencing
modalHeader and data.messages to build the combined string and assign it once.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

color: transparent;
}
.form-floating > .form-control:not(:-moz-placeholder-shown), .form-floating > .form-control-plaintext:not(:-moz-placeholder-shown) {
.form-floating > .form-control:not(:-moz-placeholder), .form-floating > .form-control-plaintext:not(:-moz-placeholder) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# form-floating 周辺で不正確な placeholder セレクタが残っていないか確認
rg -n ':-moz-placeholder(?!-shown)|:-moz-placeholder-shown|:placeholder-shown' html/template/default/assets/css/style.css -C2

Repository: EC-CUBE/ec-cube

Length of output: 1952


🏁 Script executed:

# Try to find file information without cloning
find . -name "style.css" -path "*/html/template/default/assets/css/*" 2>/dev/null | head -5

Repository: EC-CUBE/ec-cube

Length of output: 104


🌐 Web query:

:-moz-placeholder vs :placeholder-shown CSS selector differences Firefox

💡 Result:

The:-moz-placeholder selector is a deprecated, non-standard Mozilla-specific pseudo-class that has been removed from modern web standards [1]. It was historically used in older versions of Firefox to target placeholder text [1]. In modern web development, you should use the standardized:placeholder-shown pseudo-class and the::placeholder pseudo-element instead [2][3]. Key differences and usage: 1.:placeholder-shown (Pseudo-class): This standard selector targets the input or textarea element itself, but only when it is currently displaying placeholder text [4][2]. It is useful for styling the container based on the presence of the placeholder (e.g., changing the border color or background when the field is empty) [4][2]. 2.::placeholder (Pseudo-element): This standard selector targets the placeholder text itself, allowing you to style the text content (e.g., changing its color, font size, or opacity) [4][5][3]. Because:-moz-placeholder is non-standard and deprecated, it should not be used in new projects [1]. Modern browsers, including Firefox, fully support the standardized:placeholder-shown and::placeholder selectors [2][3]. If you encounter:-moz-placeholder in legacy codebases, it is recommended to replace it with the standard equivalents to ensure cross-browser compatibility and future-proof your CSS [1].

Citations:


🌐 Web query:

Bootstrap 5.3.8 form-floating placeholder-shown selector

💡 Result:

In Bootstrap 5.3, the.form-floating component relies on the:placeholder-shown CSS pseudo-class to manage the state of floating labels [1][2]. The implementation works as follows: 1. Requirement: A non-empty placeholder attribute (e.g., placeholder=" ") is required on the input element [1][2]. This is necessary because the CSS-only logic uses:placeholder-shown to detect when the input is empty [2]. 2. Mechanism: When the input is empty (i.e., the placeholder is shown), the label remains in its default position [2]. When the user types in the input (or a value is pre-filled), the:placeholder-shown condition becomes false, triggering the CSS to move the label to its floated position [1][2]. 3. Selector Usage: Bootstrap uses the sibling selector (~) to target the label based on the state of the input [1][2]. For example, the internal CSS effectively uses selectors like.form-floating >.form-control:not(:placeholder-shown) ~ label to adjust the label's position [3][4]. Note that the placeholder text itself is typically not visible to the user; the floating label serves as the primary label for the field [2]. If you are experiencing issues where labels are always in the floated state, ensure your input has a valid placeholder attribute and that the input element precedes the label in the DOM [1][2].

Citations:


🏁 Script executed:

# Read the relevant sections of the CSS file
sed -n '1705,1710p' html/template/default/assets/css/style.css
echo "---"
sed -n '1725,1730p' html/template/default/assets/css/style.css
echo "---"
sed -n '1737,1742p' html/template/default/assets/css/style.css

Repository: EC-CUBE/ec-cube

Length of output: 667


form-floating の Firefox 条件セレクタが廃止予定の非標準セレクタを使用しています

行 1707、1727、1739 の :-moz-placeholder は廃止予定の非標準セレクタです。Bootstrap 5.3.8 は標準化された :placeholder-shown 擬似クラスを使用しており、現在のコードではラベルの浮上判定が正しく機能しません。標準セレクタに統一する必要があります。

修正案
-.form-floating > .form-control:not(:-moz-placeholder), .form-floating > .form-control-plaintext:not(:-moz-placeholder) {
+.form-floating > .form-control:not(:placeholder-shown), .form-floating > .form-control-plaintext:not(:placeholder-shown) {
   padding-top: 1.625rem;
   padding-bottom: 0.625rem;
 }

-.form-floating > .form-control:not(:-moz-placeholder) ~ label {
+.form-floating > .form-control:not(:placeholder-shown) ~ label {
   transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem);
 }

-.form-floating > textarea:not(:-moz-placeholder) ~ label::after {
+.form-floating > textarea:not(:placeholder-shown) ~ label::after {
   position: absolute;
   inset: 1rem 0.375rem;
   z-index: -1;

(1727、1739 行にも同様の修正が必要)

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.form-floating > .form-control:not(:-moz-placeholder), .form-floating > .form-control-plaintext:not(:-moz-placeholder) {
.form-floating > .form-control:not(:placeholder-shown), .form-floating > .form-control-plaintext:not(:placeholder-shown) {
🧰 Tools
🪛 Stylelint (17.11.0)

[error] 1707-1707: Vendor-prefixed selector ":-moz-placeholder" (selector-no-vendor-prefix)

(selector-no-vendor-prefix)


[error] 1707-1707: Vendor-prefixed selector ":-moz-placeholder" (selector-no-vendor-prefix)

(selector-no-vendor-prefix)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@html/template/default/assets/css/style.css` at line 1707, Replace the
nonstandard Firefox placeholder pseudo-class with the standardized
:placeholder-shown in all affected selectors: change occurrences like
".form-floating > .form-control:not(:-moz-placeholder)" (and the similar
selectors at the other two occurrences) to use the corresponding standard
pseudo-class (e.g. ".form-floating > .form-control:not(:placeholder-shown)") so
the floating label logic matches Bootstrap 5.3.8; update all three identical
selectors to ensure consistent behavior across browsers.

Comment thread html/template/default/assets/js/eccube.js
Comment thread html/template/default/assets/js/eccube.js
Comment thread src/Eccube/Resource/template/admin/Content/layout.twig
Comment thread src/Eccube/Resource/template/admin/Order/confirmationModal_js.twig
Comment thread src/Eccube/Resource/template/admin/Order/search_customer.twig Outdated
Comment thread src/Eccube/Resource/template/admin/Order/search_product.twig Outdated
Comment thread src/Eccube/Resource/template/admin/Setting/Shop/payment_edit.twig Outdated
Comment thread src/Eccube/Resource/template/admin/Store/plugin_table_official.twig
Comment thread src/Eccube/Resource/template/default/Shopping/index.twig
@codecov

codecov Bot commented May 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 77.64%. Comparing base (e5d42e9) to head (1860436).

Additional details and impacted files
@@            Coverage Diff             @@
##              4.4    #6764      +/-   ##
==========================================
+ Coverage   77.54%   77.64%   +0.10%     
==========================================
  Files         595      595              
  Lines       29173    29173              
==========================================
+ Hits        22622    22652      +30     
+ Misses       6551     6521      -30     
Flag Coverage Δ
Unit 77.64% <ø> (+0.10%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

dotani1111 and others added 5 commits May 19, 2026 10:21
CodeRabbit review (#2, #3) の指摘に対応する.

- setClassCategories / checkStock の `eccube.productsClassCategories[product_id]`
  および `[classcat_id1]` 参照を, 中間が undefined のとき例外にならないよう
  ガード付きに変更. classcat2 が見つからない場合は `|| {}` で空オブジェクトに
  フォールバックし, `for...in` が安全に no-op になるようにする.
- `this.product_code_origin` / `this.product_cart_origin` / `this.price01_origin`
  はスカラ保持されており商品一覧画面で 1 商品目の値が他商品に伝播していた.
  既に正しい実装になっている `price02_origin[product_id]` パターンに合わせ,
  3 つとも product_id キーのマップに変更する.
- `.product-code-default` の検索を `document.querySelector` から
  `formParent.querySelector` にスコープ化し, 一覧画面の他商品要素を
  誤って引かないようにする.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CodeRabbit review (#6, #7) の指摘に対応する.

search_customer.twig と search_product.twig はモーダル内 fetch でリストを
innerHTML 差替えするため, テンプレ内で直接バインドしたイベントが 2 ページ目
以降のリンクに引き継がれず, 「顧客設定」ボタンやページング操作が無効化されて
いた.

- edit.twig: 既存の searchProductModalList 用 document delegate に並べ,
  searchCustomerModalList の `.set-customer` クリックと
  `#customer_pagination a` クリックも document delegate に集約する.
- shipping.twig: 商品検索のページング delegate を追加 (edit.twig 同等).
- search_customer.twig: 直バインドだった <script> ブロックを削除.
- search_product.twig: 直バインドだった `#product_pagination a` のクリック
  ハンドラを削除. 親テンプレ側の delegate で処理する旨をコメントで残す.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CodeRabbit review (#8) の指摘に対応する.

`pond.on('initfile')` ハンドラが `product_image_error` という存在しない ID を
参照しており, アップロード前のエラーメッセージリセットが no-op になっていた.
同テンプレート内の `error` ハンドラで使われている正しい ID
`payment_image_error` に揃える.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CodeRabbit review (#5) の指摘に対応する.

`BulkSendMail.getPromises` は `forEach` で全 fetch を並列発行しており,
進捗バーが実態と乖離するうえ SMTP への同時接続が発生していた. 同ファイル内の
`BulkStatusUpdate.getPromises` (ポイント・在庫の加算減算用に既に順次実行
パターンが組まれている) と同じ `urls.shift()` + `.then()` のコールバック
チェーンに揃え, 1 件ずつ送信する形に変更する.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CodeRabbit review (#4, #9, #10) の指摘に対応する.

- admin/Content/layout.twig: codePreview モーダルの「ブロック編集」リンクは
  `onclick` 属性を直接 replace していたため, 2 回目以降のオープンでは前回
  置換済みの ID で固定化されていた. 初回読み取った値を
  `dataset.onclickTemplate` に退避し, 毎回そこから組み立て直すよう変更.
- admin/Store/plugin_table_official.twig: メンテナンスモード解除 fetch の
  `.then()` が `res.ok` を見ておらず, 解除失敗時にも「削除が完了しました。」
  と誤表示していた. `res.ok` が false なら `throw` し外側 `.catch` で
  失敗メッセージを出すよう統一.
- default/Shopping/index.twig: 顧客情報更新の `toggleDisplay` 3 件が
  `fetch().finally()` の外にあり, リクエスト発火直後に同期実行されて
  しまっていた. 成功時のみ表示モードへ遷移するよう `.then()` の
  `data.status == 'OK'` 分岐内に移動.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@dotani1111 dotani1111 changed the title Dev/migrate modal to bootstrap5 native 4.4 igrate modal to bootstrap5 native 4.4 May 19, 2026
@dotani1111 dotani1111 changed the title igrate modal to bootstrap5 native 4.4 Migrate modal to bootstrap5 native 4.4 May 19, 2026
dotani1111 and others added 3 commits May 19, 2026 18:49
Slick 撤去で `.slick-list` / `.slick-track` などの wrapper が無くなった結果,
`.item_visual` (main visual) と `.item_nav` (サムネ行) の間にあった ~30px
の余白が潰れて密着表示になっていた.

inline style の `.item_visual .slide-item` 宣言ブロックに合わせて
`.item_visual { margin-bottom: 30px }` を追加し旧表示を踏襲する.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…r を修正

`document.getElementById('searchCustomerModalButton').addEventListener(...)` が
new 注文 (`Order.id is empty`) でしかレンダリングされないボタンを参照しており,
既存注文の編集画面 (`/admin/order/{id}/edit`) で
`Cannot read properties of null (reading 'addEventListener')` で落ちていた.

旧 jQuery (`$('#xxx').on(...)`) はセレクタ空集合で silently no-op だったが,
vanilla 化で null チェックが必要になる. edit.twig / shipping.twig 双方で
条件レンダリングされうるボタン (`searchCustomerModalButton` /
`searchProductModalButton`) に null ガードを追加.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…書きする

問題点:
- 商品画像 / 支払方法画像のアップロード失敗時にエラーメッセージが表示されない。
- 旧 jQuery `.show()` は inline-style `display: <type>` を書き込んで Bootstrap5
  の `.invalid-feedback { display: none }` を上書きしていたが、 vanilla 変換で
  `style.display = ''` (inline-style 削除) にしたためクラス由来の `display: none`
  がそのまま残って非表示のままになっていた。

修正点:
- `style.display = 'block'` を明示的に指定し、 `.invalid-feedback` のクラス
  CSS を上書きする。

対象:
- src/Eccube/Resource/template/admin/Product/product.twig
- src/Eccube/Resource/template/admin/Setting/Shop/payment_edit.twig

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@dotani1111 dotani1111 added improvement 機能改善 javascript Pull requests that update Javascript code labels May 20, 2026
@dotani1111 dotani1111 added this to the 4.4.0 milestone May 20, 2026
問題点:
- ブロック移動モーダルの移動先 select で `&lt;head&gt;&lt;/head&gt;タグ内` の
  ように HTML エンティティがそのまま表示されていた。

修正点:
- Twig 側で `'{{ ... |trans }}'` (auto-escape あり) で出力していた section ラベルを
  `{{ ... |trans|json_encode|raw }}` に変更し、 escape を介さず JS 文字列リテラル
  として出力する。
- 旧 jQuery `.append('<option>' + label + '</option>')` は HTML パース時に
  エンティティを復号していたが、 vanilla 化後の `opt.textContent = label` は
  リテラル代入のため復号されない。出力側で escape しない形に揃える。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@ttokoro20240902 ttokoro20240902 self-assigned this Jun 17, 2026
@ttokoro20240902

Copy link
Copy Markdown
Contributor

@dotani1111
最新化お願いします。

dotani1111 and others added 4 commits August 6, 2026 14:43
…-bootstrap5-native-4.4

コンフリクトの解決方針:

- ビルド系は upstream の esbuild へ統一し、gulp/webpack 由来の依存は破棄。
  本 PR で追加した qrcode-generator / sortablejs のみ package.json に残す。
- 生成物 (html/bundle/*, html/template/*/assets/css/*) は esbuild で再生成。
  webpack 固有の admin.bundle.js.LICENSE.txt とハッシュ名アセットは削除。
- e2e/setup-fixtures.php は upstream を採用 (symfony/dotenv の boot_env と
  APP_ENV=e2e へ移行済みのため、phpdotenv 前提の変更は不要)。
- upstream が追加した jQuery 実装はネイティブ JS へ移植して取り込む。
  - 二重送信ガード (function.js)
  - 入金日・出荷日の編集トグル (Order/edit.twig, Order/shipping.twig)
  - 認証キー発行前の注意喚起モーダル (Store/authentication_setting.twig)
  - フォーム action 保持と bfcache 復元時のみの初期化 (Shopping/index.twig)
  - インストール完了画面のモーダル残留回避と管理画面遷移待ち (install/complete.twig)
- json_encode|raw は upstream の json_encode_safe に合わせる。
- 受注メール送信確認画面の #template-change ハンドラは upstream に合わせて削除。
- `|json_encode|raw` は `</script>` を含む値でスクリプトを閉じられるため、
  JSON_HEX_TAG 等でエスケープする json_encode_safe を使う
- `|raw` が不要になり、コア他テンプレートの出力方法と揃う
- $.ajax を fetch に置換。CSRF は body の `_token` を維持し、
  X-Requested-With も従来どおり付与する
- fadeOut は opacity トランジション + display:none で置換
- $(fn) は readyState を見て DOMContentLoaded に登録

エラー経路の扱い:

- 403 / 500 は HTML が返り JSON parse に失敗するため、parse エラーを
  ログに残したうえで失敗として扱う(原因を握り潰さない)
- 成功判定に res.ok を含める。4xx/5xx の本文に success があっても成功にしない
- onError の message はサーバ由来の文言があるときだけ渡し、無いときは空にして
  呼び出し側のローカライズ文言にフォールバックさせる
- ネットワーク・parse 系の失敗は then の第 2 引数で受け、onSuccess 内の例外を
  保存失敗として通知しない
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

improvement 機能改善 javascript Pull requests that update Javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants