git -C '/home/opc/rocketa.git' show 23b23db -- resources/views/admin/media/site/regist.blade.phpcommit 23b23db7f909c788ebc35f447165aa1e465b24e4
Author: Satoshi Ujihara <satoshi_ujihara@fivegate.jp>
Date: Wed Nov 12 11:38:20 2025 +0900
翻訳とクリックURLの仕様変更
diff --git a/resources/views/admin/media/site/regist.blade.php b/resources/views/admin/media/site/regist.blade.php
index 2567f31..36bc109 100644
--- a/resources/views/admin/media/site/regist.blade.php
+++ b/resources/views/admin/media/site/regist.blade.php
@@ -64,6 +64,21 @@
</div>
</div><!-- /.form-group -->
+ <div class="form-group row m-0 border-bottom">
+ <div class="col-sm-2 px-0 bg-gray d-flex align-items-center">
+ {{ Form::label('',__('media-site-regist.click_url_custom_params'), ['class'=>'px-2 col-form-label2'], false) }}
+ </div>
+
+ <div class="col-sm-10 px-0 d-flex align-items-center">
+ <div class="p-2 w-100">
+ {{ Form::text('custom_param', $datas['custom_param'], ['id'=>'enumInput','class'=>'form-control', 'placeholder'=>__('media-site-regist.enter_custom_params')]) }}
+
+ @if($errors->has('custom_param'))
+ <div class="err_msg mt-2">{{ $errors->first('custom_param') }}</div>
+ @endif
+ </div>
+ </div>
+ </div><!-- /.form-group -->
<div class="form-group row m-0 border-bottom">
<div class="col-sm-2 px-0 bg-gray d-flex align-items-center">
{{ Form::label('',__('media-site-regist.performance_return_api').'<span class="required">'.__('media-site-regist.required').'</span>', ['class'=>'px-2 col-form-label'], false) }}
@@ -71,7 +86,7 @@
<div class="col-sm-10 px-0 d-flex align-items-center">
<div class="p-2 w-100">
- {{ Form::url('api_url', $datas['api_url'] ? $datas['api_url']:"https://domain/path?ad_id={ad_id}&media_uid={media_uid}&sid={sid}&amount={amount}&reward={reward}&count={count}&status={status}&date={date}&stage={stage}", ['class'=>'form-control', 'placeholder'=>__('media-site-regist.please_enter_performance_return_api_url')]) }}
+ {{ Form::url('api_url', $datas['api_url'], ['class'=>'form-control url-input', 'autocomplete'=>'off', 'placeholder'=>__('media-site-regist.please_enter_performance_return_api_url')]) }}
@if(__('common._')=="ja")
<a href="https://docs.google.com/presentation/d/1tkfpeO_VsCeYXddGMe_4CAltP9lXuw9uppD9odHyFXo/edit?usp=sharing" target="_blank">help</a>
@elseif(__('common._')=="kr")
@@ -93,7 +108,7 @@
<div class="col-sm-10 px-0 d-flex align-items-center">
<div class="p-2 w-100">
- {{ Form::url('api_url_test', $datas['api_url_test'] ? $datas['api_url_test']:"", ['class'=>'form-control', 'placeholder'=>__('media-site-regist.please_enter_performance_return_api_url')]) }}
+ {{ Form::url('api_url_test', $datas['api_url_test'] ? $datas['api_url_test']:"", ['class'=>'form-control url-input', 'autocomplete'=>'off', 'placeholder'=>__('media-site-regist.please_enter_performance_return_api_url')]) }}
@if($errors->has('api_url_test'))
<div class="err_msg mt-2">{{ $errors->first('api_url_test') }}</div>
@@ -253,6 +268,18 @@
</div>
</div><!-- /.form-group -->
+ <div class="form-group row m-0 border-bottom">
+ <div class="col-sm-2 px-0 bg-gray d-flex align-items-center">
+ <div class="px-2 col-form-label">{{__('media-site-regist.click_url_custom_params')}}</div>
+ </div>
+
+ <div class="col-sm-10 px-0 d-flex align-items-center">
+ <div class="px-2 py-3 w-100">
+ {{ $datas['custom_param'] }}
+ </div>
+ </div>
+ </div><!-- /.form-group -->
+
<div class="form-group row m-0 border-bottom">
<div class="col-sm-2 px-0 bg-gray d-flex align-items-center">
<div class="px-2 col-form-label">{{__('media-site-regist.performance_return_api')}}<span class="required">{{__('media-site-regist.required')}}</span></div>
@@ -393,4 +420,341 @@
{{ Form::hidden('action', 'regist') }}
{{ Form::close() }}
@endif
+
+<!-- 共通モーダル -->
+<div class="modal fade" id="urlBuilderModal" tabindex="-1" aria-labelledby="urlBuilderModalLabel" aria-hidden="true">
+ <div class="modal-dialog modal-lg">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h5 class="modal-title" id="urlBuilderModalLabel">{{__('ad-regist.url_builder')}}</h5>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{{__('ad-regist.close')}}"></button>
+ </div>
+ <div class="modal-body">
+ <form id="urlBuilderForm" class="row g-2">
+ <div class="col-12">
+ <label class="form-label">{{__('ad-regist.domain_example')}}</label>
+ <input type="text" id="ubDomain" class="form-control" placeholder="abr.ge">
+ </div>
+ <div class="col-12">
+ <label class="form-label">{{__('ad-regist.path_note')}}</label>
+ <input type="text" id="ubPath" class="form-control" placeholder="/@appname/rocket_a">
+ </div>
+
+ <div class="col-12 mt-3">
+ <label class="form-label">{{__('ad-regist.query_parameters')}}</label>
+ <div id="kvContainer" class="mb-2">
+ <!-- key/value 行がここに挿入される -->
+ </div>
+ <button type="button" id="addKvBtn" class="btn btn-sm btn-outline-primary">+ {{__('ad-regist.add_parameter')}}</button>
+ </div>
+
+ <div class="col-12">
+ <small class="text-muted">※{{__('ad-regist.empty_keys_ignored')}}</small>
+ </div>
+ </form>
+ </div>
+
+ <div class="modal-footer">
+ <button type="button" id="ubCancel" class="btn btn-secondary" data-bs-dismiss="modal">{{__('ad-regist.cancel')}}</button>
+ <button type="button" id="ubApply" class="btn btn-primary">{{__('ad-regist.confirm')}}</button>
+ </div>
+ </div>
+ </div>
+</div>
+
+<!-- テンプレート(非表示) -->
+<template id="kvRowTemplate">
+ <div class="input-group mb-2 kv-row">
+ <input type="text" class="form-control kv-key" placeholder="key">
+ <select class="form-control kv-value" placeholder="{{__('ad-regist.please_select')}}">
+ </select>
+ <button type="button" class="btn btn-outline-danger remove-kv-btn" title="{{__('ad-regist.delete')}}">✕</button>
+ </div>
+</template>
+
+
+
+ <!-- モーダル -->
+ <div class="modal fade" id="enumModal" tabindex="-1" aria-hidden="true">
+ <div class="modal-dialog modal-dialog-centered modal-lg">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h5 class="modal-title">{{__('media-site-regist.register_custom_params')}}</h5>
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
+ </div>
+ <div class="modal-body">
+ <div id="assume_url">
+ <u>{{__('media-site-regist.custom_params_description')}}</u><br />
+ <b>htts://api.rocket-a.com/click_url?xxx=yyy<font color="red" id="enumPreview"></font></b>
+ </div>
+ <div id="enumList">
+ <div class="input-group mb-2 enum-item">
+ <input type="text" class="form-control enum-value" placeholder="{{__('media-site-regist.enter_value')}}">
+ <button class="btn btn-outline-danger remove-btn">−</button>
+ </div>
+ </div>
+ <button id="addEnum" class="btn btn-outline-primary w-100">+ {{__('media-site-regist.add_row')}}</button>
+ </div>
+ <div class="modal-footer">
+ <button class="btn btn-secondary" data-bs-dismiss="modal">{{__('ad-regist.cancel')}}</button>
+ <button id="saveEnum" class="btn btn-primary">{{__('ad-regist.confirm')}}</button>
+ </div>
+ </div>
+ </div>
+ </div>
+
@endsection
+
+@section('respectively_js')
+<script>
+//////////////////////////
+//トラッキングURL設定 start
+//////////////////////////
+$(function() {
+ // Bootstrap Modal インスタンスを生成(要素は DOM に存在する前提)
+ var urlModalEl = document.getElementById('urlBuilderModal');
+ var urlModal = new bootstrap.Modal(urlModalEl, { backdrop: 'static', keyboard: false });
+
+ var $currentInput = null; // モーダル操作対象の input 要素
+
+ // --- ヘルパー: KV 行を追加 ---
+ function addKvRow(key, value) {
+
+ var tpl = document.getElementById('kvRowTemplate').content.cloneNode(true);
+ var $row = $(tpl).find('.kv-row');
+ $row.find('.kv-key').val(key || '');
+ $row.find('.kv-value').val(value || '');
+
+ const $select = $row.find('.kv-value');
+ $select.empty();
+ //rocket-aパラメータをselect optionに追加
+ const enumInputItems1 = 'status,sid,ad_id,client_id,media_id,media_uid,reward,date,stage,product_code,amount,count,device_uuid'.split(',').map(s => s.trim()).filter(s => s !== '');
+ enumInputItems1.forEach(value => {
+ $select.append($('<option>', {value: '{'+value+'}',text: '{'+value+'}'}));
+ });
+ const enumInputText = $('#enumInput').val();
+ const enumInputItems2 = enumInputText.split(',').map(s => s.trim()).filter(s => s !== '');
+ enumInputItems2.forEach(value => {
+ $select.append($('<option>', {value: '{'+value+'}',text: '{'+value+'} ({{__('media-site-regist.custom_parameters')}})'}));
+ });
+
+ $('#kvContainer').append($row);
+
+
+ }
+
+ // --- 初期で1行用意 ---
+ function ensureAtLeastOneRow() {
+ if ($('#kvContainer').children().length === 0) addKvRow();
+ }
+
+ // + ボタン
+ $('#addKvBtn').on('click', function() {
+ addKvRow();
+ });
+
+ // input 選択(フォーカス/クリック)でモーダルを開く
+ // 複数あっても対応できるよう class .url-input を使う
+ $(document).on('focus', '.url-input', function() {
+ $currentInput = $(this);
+ openModalWithInputValue($currentInput.val());
+ });
+
+ // 既存値を解析してモーダルにプリセットする
+ function openModalWithInputValue(val) {
+ // クリア
+ $('#ubDomain').val('');
+ $('#ubPath').val('');
+ $('#kvContainer').empty();
+
+ if (val && $.trim(val) !== '') {
+ // URL パース(protocol が無ければ https を仮置き)
+ var tryUrl = val;
+ if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(tryUrl)) {
+ tryUrl = 'https://' + tryUrl;
+ }
+ try {
+ var parsed = new URL(tryUrl);
+ // host は host (ホスト名:ポート) ではなく hostname が望ましい
+ $('#ubDomain').val(parsed.hostname + (parsed.port ? (':' + parsed.port) : ''));
+ // pathname は先頭スラッシュあり
+ $('#ubPath').val(parsed.pathname === '/' ? '' : parsed.pathname);
+ // query params
+ parsed.searchParams.forEach(function(value, key) {
+ addKvRow(key, value);
+ });
+ } catch (e) {
+ // URL でない場合(例: "example.com/path" でパースできないなど)は簡易分解
+ // domain と path をスラッシュで分ける
+ var m = val.match(/^([^\/]+)(\/.*)?$/);
+ if (m) {
+ $('#ubDomain').val(m[1]);
+ $('#ubPath').val(m[2] || '');
+ }
+ // query があればパースする(? が含まれている場合)
+ var qIndex = val.indexOf('?');
+ if (qIndex !== -1) {
+ var qs = val.substring(qIndex + 1);
+ var pairs = qs.split('&');
+ pairs.forEach(function(p) {
+ if (!p) return;
+ var kv = p.split('=');
+ var k = decodeURIComponent(kv[0] || '');
+ var v = decodeURIComponent(kv.slice(1).join('=') || '');
+ addKvRow(k, v);
+ });
+ }
+ }
+ }
+ ensureAtLeastOneRow();
+ // モーダル表示
+ urlModal.show();
+
+ const $select = $('.kv-value');
+ $select.empty();
+ //rocket-aパラメータをselect optionに追加
+ const enumInputItems1 = 'status,sid,ad_id,client_id,media_id,media_uid,reward,date,stage,product_code,amount,count,device_uuid'.split(',').map(s => s.trim()).filter(s => s !== '');
+ enumInputItems1.forEach(value => {
+ $select.append($('<option>', {value: '{'+value+'}',text: '{'+value+'}'}));
+ });
+ const enumInputText = $('#enumInput').val();
+ const enumInputItems2 = enumInputText.split(',').map(s => s.trim()).filter(s => s !== '');
+ enumInputItems2.forEach(value => {
+ $select.append($('<option>', {value: '{'+value+'}',text: '{'+value+'} ({{__('media-site-regist.custom_parameters')}})'}));
+ });
+ }
+
+ // 決定ボタンの動作: 入力値を URL に組み立てて input に反映
+ $('#ubApply').on('click', function() {
+ var domain = $.trim($('#ubDomain').val() || '');
+ var path = $.trim($('#ubPath').val() || '');
+ // path は先頭に / をつける(空なら空)
+ if (path && path[0] !== '/') path = '/' + path;
+
+ if (!domain) {
+ alert('{{__('media-site-regist.enter_domain_example')}}');
+ return;
+ }
+
+ // query を収集
+ var params = [];
+ $('#kvContainer .kv-row').each(function() {
+ var k = $.trim($(this).find('.kv-key').val() || '');
+ var v = $(this).find('.kv-value').val() || '';
+ if (k === '') return; // 空キーは無視
+ // encodeURIComponent で値を安全にエンコード
+ params.push(encodeURIComponent(k) + '=' + (v));
+ });
+
+ var query = params.length ? '?' + params.join('&') : '';
+ // デフォルトスキームは https
+ var finalUrl = 'https://' + domain + (path || '') + query;
+
+ // もし input の元の値がスキーム付きで別スキームだった場合、スキームを維持する
+ var original = $currentInput && $currentInput.val();
+ if (original && /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(original)) {
+ try {
+ var origParsed = new URL(original);
+ // 同一ホスト? というチェックはしないで、scheme を維持したい場合だけ置換
+ finalUrl = origParsed.protocol + '//' + domain + (path || '') + query;
+ } catch (e) {
+ // 解析失敗なら無視
+ }
+ }
+
+ // 入力に反映
+ if ($currentInput) {
+ $currentInput.val(finalUrl).trigger('change');
+ }
+
+ // モーダル閉じる
+ urlModal.hide();
+ });
+
+ // モーダルが閉じられたら currentInput をクリア(必要なら)
+ urlModalEl.addEventListener('hidden.bs.modal', function () {
+ $currentInput = null;
+ });
+
+ // 初期状態で KV を1行確保(モーダル毎に表示時に ensureAtLeastOneRow が呼ばれるため冗長ではない)
+ ensureAtLeastOneRow();
+});
+//////////////////////////
+//トラッキングURL設定 end
+//////////////////////////
+
+//////////////////////////
+//独自パラメータ設定 start
+//////////////////////////
+ $(function () {
+ const updatePreview = () => {
+ const values = $('.enum-value').map(function () {
+ return $(this).val().trim();
+ }).get().filter(v => v);
+
+ // 値を &key={key} の形式に変換して結合
+ const joined = values.map(v => `&${v}={${v}}`).join('');
+
+ $('#enumPreview').text(joined || '');
+ };
+
+ // input選択でモーダル表示
+ $('#enumInput').on('focus', function () {
+ const currentValues = $(this).val().split(',').map(v => v.trim()).filter(v => v);
+ const $list = $('#enumList');
+ $list.empty();
+
+ // 既存値を反映
+ if (currentValues.length) {
+ currentValues.forEach(v => {
+ $list.append(`<div class="input-group mb-2 enum-item">
+ <input type="text" class="form-control enum-value" value="${v}">
+ <button class="btn btn-outline-danger remove-btn">−</button>
+ </div>`);
+ });
+ } else {
+ $list.append(`<div class="input-group mb-2 enum-item">
+ <input type="text" class="form-control enum-value" placeholder="{{__('media-site-regist.enter_value')}}">
+ <button class="btn btn-outline-danger remove-btn">−</button>
+ </div>`);
+ }
+ updatePreview();
+ $('#enumModal').modal('show');
+ });
+ $(document).on('input change', '.enum-value', updatePreview);
+
+ // 行追加
+ $('#addEnum').on('click', function () {
+ const count = $('.enum-value').length;
+ if (count >= 4) {
+ alert('{{__('media-site-regist.cannot_add_more_items_max_4',['cnt'=>4])}}');
+ return;
+ }
+
+ $('#enumList').append(`<div class="input-group mb-2 enum-item">
+ <input type="text" class="form-control enum-value" placeholder="{{__('media-site-regist.enter_value')}}">
+ <button class="btn btn-outline-danger remove-btn">−</button>
+ </div>`);
+ updatePreview();
+ });
+
+ // 行削除
+ $(document).on('click', '.remove-btn', function () {
+ $(this).closest('.enum-item').remove();
+ });
+
+ // 決定ボタン
+ $('#saveEnum').on('click', function () {
+ const values = $('.enum-value').map(function () {
+ return $(this).val().trim();
+ }).get().filter(v => v);
+ $('#enumInput').val(values.join(','));
+ $('#enumModal').modal('hide');
+ $('.url-input').val('');
+ });
+ });
+//////////////////////////
+//独自パラメータ設定 end
+//////////////////////////
+</script>
+@endsection
\ No newline at end of file