- ベストアンサー
WordPressの文字数制限設定について
- WordPressで名前の文字数制限を設ける方法について詳しく解説します。
- 名前に50文字の制限を設けたい場合、メッセージの文字数制限のコードと組み合わせることが必要です。
- この記事では、具体的なコードや設定方法を紹介していますので、参考にしてください。
- みんなの回答 (39)
- 専門家の回答
質問者が選んだベストアンサー
No such file or directory そのようなファイル、又はディレクトリはありません home/vqnporqs/public_html/wp-content/uploads/attach/3_0.jpeg attachディレクトリがないとか。
その他の回答 (38)
- dell_OK
- ベストアンサー率13% (766/5720)
・アップロードの際にカメラ画像が見えなくなる方法として2つほど解決策がありそうで、そちらは有効であるのか今の形のままcssを変更するのみで問題ないのかお聞きしたいです。 今の形のCSSがわからないので問題ないかはなんとも言えませんが、それで実現できているのならそのままでいいと思います。 ふたつのコードを試してみました。 ひとつめは今のコードに近いもののようですが、開始<div>がないためか、期待通りの動作が見えませんでした。 ふたつめはカメラ画像をアップロード画像に差し替えていて画面がぶれないので操作しやすい気がしました。 欲を言うと、差し替えた画像クリックでファイルを選択し直せればと思うのですが、動画の場合は再生操作があるので無理そうですね。 どちらも有効だと思いますが、大事なことはコードを理解することです。 理解すれば、これでいい、これがいい、と自分で判断できると思います。 作って終わり、にはならないので、運用中になにかあれば自分で直す必要がある、前提で取り組んでください。
補足
回答ありがとうございます、もう少し深くお聞きしたいのですが2つめのコードで動画を表示させることは可能でしょうか? コードが大幅に変わるため心配なのですが、下記のコードはすべて教えて頂いたコードに変えても良いのでしょうか? cssをどこに書けばよいのか分からないので、アドバイス頂きたいです。 <style> .msg_partial { text-align: right; color: #333; margin: 0 15px 20px 0; } .msg_partial strong { color: #e52d77; } </style> ※最新ファイル https://wandbox.org/permlink/AayWn5zB8sUkjaBr const attach = document.querySelectorAll('.attach'); const del = document.querySelectorAll('.attachdel'); const clear = document.querySelectorAll('.attachclear'); const viewer = document.querySelectorAll('.viewer'); for (let i = 0; i < attach.length; i++) { attach[i].addEventListener('change', () => { if (attach[i].files[0].size > 15 * 1024 * 1024) { alert('ファイルサイズが 15MBバイトを超えています'); return; } del[i].value = ""; viewer[i].innerHTML = ""; if (attach[i].files.length !== 0) { const reader = new FileReader(); reader.onload = () => { var child = null; if (reader.result.indexOf("data:image/jpeg;base64,") === 0 || reader.result.indexOf("data:image/png;base64,") === 0) { child = document.createElement("img"); } else if (reader.result.indexOf("data:video/mp4;base64,") === 0) { child = document.createElement("video"); child.setAttribute("controls", null); } else if (reader.result.indexOf("data:application/pdf;base64,") === 0) { child = document.createElement("iframe"); } else { alert("対象外のファイルです"); alert(reader.result); attach[i].value = ""; } if (child !== null) { child.style.height = "100px"; child.src = reader.result; viewer[i].appendChild(child); } }; reader.readAsDataURL(attach[i].files[0]); } }); clear[i].addEventListener('click', () => { attach[i].value = ""; del[i].value = "1"; viewer[i].innerHTML = ""; }); }
- dell_OK
- ベストアンサー率13% (766/5720)
・function lengthCheck() {}内で複数のイベントが発生するので、要素を参照して識別させているような感じでしょうか? lengthCheck()内でイベントが発生しているのではなくて、イベントが発生して呼ばれるのがlengthCheck()です。 複数の要素を元にイベントが発生するので、それがどの要素かと聞かれたら、この(this)要素と言っているだけな感じです。 thisでは要素を参照するまでで、識別まではしていません。 JavaScriptが識別しているわけでもないと思います。 要素がどのようなものであっても(識別することなく)書かれたコードをそのまま実行して問題があればエラーになると思います。
補足
Q.lengthCheck()内でイベントが発生しているのではなくて、イベントが発生して呼ばれるのがlengthCheck()です。 複数の要素を元にイベントが発生するので、それがどの要素かと聞かれたら、この(this)要素と言っているだけな感じです。 thisでは要素を参照するまでで、識別まではしていません。 A.回答ありがとうございます、なるほど参照しているのですね。イベントの時に呼ばれるという事も理解できました。
- dell_OK
- ベストアンサー率13% (766/5720)
・ファイルを選択できなくなるということはclearボタンが機能しなくなることを指されていますでしょうか? 言われてみると、そうですね。 clearボタンは機能します。 いったんクリアしてもらって、新たにファイルを選択する、でよければ問題ありません。 私には、clearボタンが「アップロードするのをやめる」感じが強かったです。 私にはファイルを選択し直すためにクリアするのは面倒、と言う思いもあって、クリアせずに選択し直せないのかと思いそうです。 質問者さまがそれでよければまったく問題なかったです。 問題なさそうなので、いただいたコードは見ていません。
補足
Q.いったんクリアしてもらって、新たにファイルを選択する、でよければ問題ありません。 私には、clearボタンが「アップロードするのをやめる」感じが強かったです。 私にはファイルを選択し直すためにクリアするのは面倒、と言う思いもあって、クリアせずに選択し直せないのかと思いそうです。 A.回答ありがとうございます、×ボタンでキャンセルする方法もありそうですが、この場合は動画もあるため×でキャンセルというのは難しそうです。 dell_okさんに見て頂きたかった理由があるのですが、以前お伝えしたカメラ画像の下にアップロードファイルが表示されてしまうという問題についてになります。 アップロードの際にカメラ画像が見えなくなる方法として2つほど解決策がありそうで、そちらは有効であるのか今の形のままcssを変更するのみで問題ないのかお聞きしたいです。 1,lavelにも画像にもclassやidを設定して、画像の表示/非表示を切り替える <label for="inputFile" id="viewFile"> <img src="" alt="xxx.jpg" id="changeImg" style="height: 100px;"> </label> <input type="file" id="inputFile" class="hideItems"> </div> <button type="button" class="attachclear">clear</button> <script> const inputFile = document.getElementById('inputFile'); // 入力される画像 const changeImg = document.getElementById('changeImg');// 入力されたら消す画像 const viewFile = document.getElementById('viewFile'); // 入力されたら表示する画像 const fileHeight = "100px" // 置き換えた後のファイルの高さ const resetButtons = document.querySelectorAll('.attachclear'); inputFile.addEventListener('change', e => { const fileDate = inputFile.files.item(0); if (fileDate.size > 15 * 1024 * 1024) { return alert('ファイルサイズが 15MBバイトを超えています'); } const fileType = fileDate.type; let newElement; if (['image/jpeg', "image/png"].includes(fileType)) { newElement = document.createElement('img'); } else if (fileType === 'video/mp4') { newElement = document.createElement('video'); newElement.controls = true; } else if (fileType === 'application/pdf') { newElement = document.createElement("iframe"); } else { return alert("対象外のファイルです"); } changeImg.classList.add('hideItems'); // もともとの画像を消す newElement.style.height = fileHeight; newElement.src = URL.createObjectURL(inputFile.files.item(fileDate)); viewFile.appendChild(newElement); }) resetButtons.forEach(button => { button.addEventListener('click',e => { const nextElement = changeImg.nextElementSibling; // changeImgの次の要素 if(nextElement) { changeImg.classList.remove('hideItems') nextElement.remove(); } }) }); </script> _____________________________________________________________ 2,親要素に表示モードを切り替えるクラスを用意しておいて、親要素のクラス設定だけでレイアウトを変える <style> /* 通常時のレイアウト */ form > div { display: flex; } .image-selector-button { position: relative; margin: .3em; } .image-selector-button label img { width: 200px; height: 100px; } .image-selector-button input { display: none; } /* 画像表示時のレイアウト */ .image-selector-button.image-on label { visibility: hidden; } .image-selector-button.image-on .viewer { position: absolute; top:0; left: 0; width: 100%; height: 100px; overflow: hidden; } </style> </head> <body> <form method="post"> <div> <div class="image-selector-button"> <label> <div><img src="img/camera.jpg"></div> <input type="file" accept=".png, .jpg"> </label> <input type="hidden"> <button type="button">clear</button> <div class="viewer"></div> </div> </div> </form> <script> (()=>{ const fileTypes = ['image/jpeg','image/png'], wrap = document.querySelector('form > div'), P = e => e.closest('.image-selector-button'), V = e => P(e).querySelector('.viewer'), C = (e, s) => P(e).classList[s]('image-on'); for(let i = 0; i<3; i++) wrap.appendChild(wrap.firstElementChild.cloneNode(1)); /* クリアボタン */ wrap.addEventListener('click', (e, t = e.target) => { if(t.nodeName != 'BUTTON' || t.type != 'button') return; V(t).innerHTML = ''; C(t, 'remove'); }); /* 画像登録 */ wrap.addEventListener('change', (e, t = e.target) => { if(t.nodeName != 'INPUT' || t.type != 'file') return; if(t.files.length == 0 || !fileTypes.includes(t.files[0].type)) return; const img = document.createElement('img'); img.src = URL.createObjectURL(t.files[0]); img.style.height = '100px'; V(t).appendChild(img); C(t, 'add'); }); })(); </script>
- dell_OK
- ベストアンサー率13% (766/5720)
・.submit_disabledというのはclassかidに設定しなくても良いのでしょうか? 実際にボタンのdisabledを設定しているのはここです。 これは id を使ってボタンを定義しています。 ---- submit_button.disabled = disabled; ---- その前に、ボタンを使えるようにしたり使えなくしたりする条件が、前より複雑になったことに注意してください。 前は「メッセージの文字数が0より大きくかつ制限文字数以下の場合は使える」「それ以外は使えない」でした。 これに名前の文字数制限が増えたため「名前の文字数が0より大きくかつ制限文字数以下かつメッセージの文字数が0より大きくかつ制限文字数以下の場合は使える」「それ以外は使えない」になりました。 問題は、名前が入力された時にはメッセージがどうなっているかも確認する必要があるし、メッセージが入力された時には名前がどうなっているかも確認する必要があることです。 それで、名前とメッセージにはそれぞれをチェックした際に「ボタンをdisabledにして欲しい」と言う要求を値として.dataset.submit_disabledに覚えてもらっています。 ---- this.dataset.submit_disabled = this.value.length === 0; ---- this.dataset.submit_disabled = true; ---- 常に両方をチェックする方法もあるのですが、自分のイベントで他の要素まで直接処理すると言うのはあまりいい方法ではない気がしています。 それで、他の要素にはその状態「ボタンをdisabledにして欲しい」を覚えてもらっていて、どれかひとつでもそうなっていれば、ボタンを使えなくする、と言う処理にしています。 もっとスマートな方法もありそうなのだけれども、私が思いつくのはこんなところです。
補足
Q.実際にボタンのdisabledを設定しているのはここです。 これは id を使ってボタンを定義しています。 ---- submit_button.disabled = disabled; ---- A.回答ありがとうございます、見落としておりました申し訳ありません。 Q.問題は、名前が入力された時にはメッセージがどうなっているかも確認する必要があるし、メッセージが入力された時には名前がどうなっているかも確認する必要があることです。 それで、名前とメッセージにはそれぞれをチェックした際に「ボタンをdisabledにして欲しい」と言う要求を値として.dataset.submit_disabledに覚えてもらっています。 ---- this.dataset.submit_disabled = this.value.length === 0; ---- this.dataset.submit_disabled = true; ---- A.解説ありがとうございます、なるほど条件が2つあって互いに干渉しあってる状態ですね。 よく分からなかったのですが、名前とメッセージのチェックということで理解致しました。 Q.常に両方をチェックする方法もあるのですが、自分のイベントで他の要素まで直接処理すると言うのはあまりいい方法ではない気がしています。 それで、他の要素にはその状態「ボタンをdisabledにして欲しい」を覚えてもらっていて、どれかひとつでもそうなっていれば、ボタンを使えなくする、と言う処理にしています。 A.確かに逐一チェックするよりも効率が良いですね。
- dell_OK
- ベストアンサー率13% (766/5720)
・single-input.phpのthis.というのはどこから出てきたのでしょうか?文法のようなものがあるのでしょうか? 詳しくは知らなかったのですがJavaScriptのthisにはいろいろあるようです。 https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/this 今回のは「DOM イベントハンドラーとして」が該当するのですが、これをみてもズバリって感じはしないので、こちらの方がまだわかりやすいかな。 https://se-tomo.com/2019/03/09/%E3%80%90javascript%E3%80%91%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%81%AEthis%E3%81%A8bind/ 簡単に言うとイベントが発生したその要素のことです。 イベントを設定しているのはここで、 length_input[i].addEventListener('input', lengthCheck); その対象要素はこれで、 length_input[i] これは何かと言うと、クラスに length_input を含むもので、 const length_input = document.querySelectorAll('.length_input'); つまりこれらです。 <input class="length_input"> <textarea class="length_input"> それで、イベントから呼ばれる文字数チェック関数lengthCheck()の中では、<input>で呼ばれたのか、<textarea>で呼ばれたのか、どっちなんだってことは気にしなくてもいいように、this.と書けばその要素を参照できる、と言うわけです。
補足
Q.簡単に言うとイベントが発生したその要素のことです。 つまりこれらです。 <input class="length_input"> <textarea class="length_input"> それで、イベントから呼ばれる文字数チェック関数lengthCheck()の中では、<input>で呼ばれたのか、<textarea>で呼ばれたのか、どっちなんだってことは気にしなくてもいいように、this.と書けばその要素を参照できる、と言うわけです。 A.説明ありがとうございます、理解することが出来ました。function lengthCheck() {}内で複数のイベントが発生するので、要素を参照して識別させているような感じでしょうか?
- AsarKingChang
- ベストアンサー率46% (3467/7474)
>回答No.1さまへ。 >いつもありがとうございます。 こちらこそ!! こんな、酔っ払いのおやじでよければいつでも~♪ >class、とてもいいですね。 これのいいのは、extendsなんですよ。 やりこんでくればその威力がわかりますよ~ (よく、protectedつけてハマってます) 第一継承では、どの値が適切だったが、 第二継承では?って感じで、それ自体が世代を持てるメリットが 生きてきますね。逆に全くダメなのが、trait、メンバーの共有はできるが、 constできない。(trait自体が実体を持ってない為。。) >共通の処理をまとめて文字数の部分だけを分けるという感じでしょうか? 基本、後から値や、パラメーターを変更する可能性があるものは、 何でもぶっこんじゃえばいいんです!。 変な使い方として、 if ( n ) { require_once(..) }else{ require_once(..) } それ自体を読み分けして、 class A{ public const TEXT="こんにちは"; } もう一つのファイルは class A{ public const TEXT="hello"; } これだけで、多言語化にもなっちゃいます。 (変化しない部分は、さらにもう1段深い層からextendsする) ソースは変わらず、 <?= A::TEXT ?>のみ! しかも、クラスで名前分けをしている(namespace)ので、 同じラベルが発生しにくいので、色々なバグが事前に減らせます!
補足
回答ありがとうございます、なるほどそういう使い方もできるのですね。 切り替えるクラスと組み合わせると処理をまとめて別言語で表示させることもできそうです。
- dell_OK
- ベストアンサー率13% (766/5720)
回答No.1さまへ。 いつもありがとうございます。 class、とてもいいですね。 最初は配列にしようと思ってたのですが、いい感じに参照する方法が思いつかず、ベタでconstしてしまいました。 個人的にもこの方法を使わせていただこうと思います。 なんだか私のヘンテココードを見られるのがちょっぴりはずかしい気もします。 質問者さまへ。 文字数チェックを共通にしてみました。 single-index.php ---- class MAX_LENGTH { public const NAME = 50; public const MESSAGE = 500; } ---- single-input.php 文字数の数字は超過していてもしていなくても赤かったので、class="err"は廃止しました。 ---- <style> .msg_partial { text-align: right; color: #333; margin: 0 15px 20px 0; } .msg_partial strong { color: #e52d77; } </style> <div class="title-partial"> <h2>名前(name)<span class="required">※必須</span></h2> <input class="length_input" data-maxlength="<?php echo MAX_LENGTH::NAME; ?>" type="text" name="namae" id="name" placeholder="未入力の場合は、匿名で表示されます" value="<?php echo $namae; ?>"> <div class="msg_partial"></div> </div> <div class="body-partial"> <h2>コメント(comment)<span class="required">※必須</span></h2> <textarea class="length_input" data-maxlength="<?php echo MAX_LENGTH::MESSAGE; ?>" name="message" id="message" placeholder="荒らし行為や誹謗中傷や著作権の侵害はご遠慮ください"><?php echo $message; ?></textarea> <div class="msg_partial"></div> </div> ---- const length_input = document.querySelectorAll('.length_input'); const submit_button = document.getElementById('submit_button'); for (let i = 0; i < length_input.length; i++) { length_input[i].addEventListener('input', lengthCheck); let event = new Event("input"); length_input[i].dispatchEvent(event); } function lengthCheck() { const left = this.dataset.maxlength - this.value.length; if (left >= 0) { this.nextElementSibling.innerHTML = 'あと<strong>' + left + '</strong>文字'; this.dataset.submit_disabled = this.value.length === 0; } else { this.nextElementSibling.innerHTML = '<strong>' + -left + '</strong>文字超過しています'; this.dataset.submit_disabled = true; } let disabled = false; for (let i = 0; i < length_input.length; i++) { if (length_input[i].dataset.submit_disabled === "true") { disabled = true; } } submit_button.disabled = disabled; } ----
お礼
ファイルアップロードについてdell_okさんに質問したいのですが、ファイルを選択した後に見えなくすると違うファイルを選択する方法がなくなってしまうという問題があったと思います、ファイルを選択できなくなるということはclearボタンが機能しなくなることを指されていますでしょうか? 解決方法が2つあるようなのですがdell_okさんの意見をお聞きしたいです。 1,lavelにも画像にもclassやidを設定して、画像の表示/非表示を切り替える <label for="inputFile" id="viewFile"> <img src="" alt="xxx.jpg" id="changeImg" style="height: 100px;"> </label> <input type="file" id="inputFile" class="hideItems"> </div> <button type="button" class="attachclear">clear</button> <script> const inputFile = document.getElementById('inputFile'); // 入力される画像 const changeImg = document.getElementById('changeImg');// 入力されたら消す画像 const viewFile = document.getElementById('viewFile'); // 入力されたら表示する画像 const fileHeight = "100px" // 置き換えた後のファイルの高さ const resetButtons = document.querySelectorAll('.attachclear'); inputFile.addEventListener('change', e => { const fileDate = inputFile.files.item(0); if (fileDate.size > 15 * 1024 * 1024) { return alert('ファイルサイズが 15MBバイトを超えています'); } const fileType = fileDate.type; let newElement; if (['image/jpeg', "image/png"].includes(fileType)) { newElement = document.createElement('img'); } else if (fileType === 'video/mp4') { newElement = document.createElement('video'); newElement.controls = true; } else if (fileType === 'application/pdf') { newElement = document.createElement("iframe"); } else { return alert("対象外のファイルです"); } changeImg.classList.add('hideItems'); // もともとの画像を消す newElement.style.height = fileHeight; newElement.src = URL.createObjectURL(inputFile.files.item(fileDate)); viewFile.appendChild(newElement); }) resetButtons.forEach(button => { button.addEventListener('click',e => { const nextElement = changeImg.nextElementSibling; // changeImgの次の要素 if(nextElement) { changeImg.classList.remove('hideItems') nextElement.remove(); } }) }); </script> _____________________________________________________________ 2,親要素に表示モードを切り替えるクラスを用意しておいて、親要素のクラス設定だけでレイアウトを変える <style> /* 通常時のレイアウト */ form > div { display: flex; } .image-selector-button { position: relative; margin: .3em; } .image-selector-button label img { width: 200px; height: 100px; } .image-selector-button input { display: none; } /* 画像表示時のレイアウト */ .image-selector-button.image-on label { visibility: hidden; } .image-selector-button.image-on .viewer { position: absolute; top:0; left: 0; width: 100%; height: 100px; overflow: hidden; } </style> </head> <body> <form method="post"> <div> <div class="image-selector-button"> <label> <div><img src="img/camera.jpg"></div> <input type="file" accept=".png, .jpg"> </label> <input type="hidden"> <button type="button">clear</button> <div class="viewer"></div> </div> </div> </form> <script> (()=>{ const fileTypes = ['image/jpeg','image/png'], wrap = document.querySelector('form > div'), P = e => e.closest('.image-selector-button'), V = e => P(e).querySelector('.viewer'), C = (e, s) => P(e).classList[s]('image-on'); for(let i = 0; i<3; i++) wrap.appendChild(wrap.firstElementChild.cloneNode(1)); /* クリアボタン */ wrap.addEventListener('click', (e, t = e.target) => { if(t.nodeName != 'BUTTON' || t.type != 'button') return; V(t).innerHTML = ''; C(t, 'remove'); }); /* 画像登録 */ wrap.addEventListener('change', (e, t = e.target) => { if(t.nodeName != 'INPUT' || t.type != 'file') return; if(t.files.length == 0 || !fileTypes.includes(t.files[0].type)) return; const img = document.createElement('img'); img.src = URL.createObjectURL(t.files[0]); img.style.height = '100px'; V(t).appendChild(img); C(t, 'add'); }); })(); </script>
補足
single-index.php ---- class MAX_LENGTH { public const NAME = 50; public const MESSAGE = 500; } ---- single-input.php function lengthCheck() { const left = this.dataset.maxlength - this.value.length; if (left >= 0) { //読み取り専用のプロパティで、この要素の親の子リスト内ですぐ次にある要素を返す、HTML要素の中身を変更 this.nextElementSibling.innerHTML = 'あと<strong>' + left + '</strong>文字'; //要素に設定されたすべてのカスタムデータ属性 (data-*) への読み取り/書き込みアクセスを提供 //必要な入力内容が揃うまで設定の保存を無効 this.dataset.submit_disabled = this.value.length === 0; } else { this.nextElementSibling.innerHTML = '<strong>' + -left + '</strong>文字超過しています'; this.dataset.submit_disabled = true; } let disabled = false; for (let i = 0; i < length_input.length; i++) { if (length_input[i].dataset.submit_disabled === "true") { disabled = true; } } submit_button.disabled = disabled; } ---- A.修正ありがとうございます、共通の処理(single-input.php)の方をまとめるとばかり考えていましたが、MAX_LENGTHをまとめるのですね。 single-input.phpのthis.というのはどこから出てきたのでしょうか?文法のようなものがあるのでしょうか? const left = this.dataset.maxlength - this.value.length; single-input.phpの.dataset.submit_disabledについてお聞きしたいのですが、disabledでボタンを無効にしているのは分かるのですが、 .submit_disabledというのはclassかidに設定しなくても良いのでしょうか? this.dataset.submit_disabled = this.value.length === 0;
- AsarKingChang
- ベストアンサー率46% (3467/7474)
こんな感じで、PHPで展開するのであれば、 const contactsignature = <?php echo NAME_MAX_LENGTH; ?> ; 設定ファイル的な物に、 まとめて書いておくのもいいでしょうね。 class A{ const B=?; const C=?; }/*クラス名変数名は、それっぽく!*/ const contactsignature = <?= A::B ?> ; の方がすっきりするでしょうね。 根元で、一括管理されてるので、以後のメンテの時、 検索で該当箇所を探せるので、Gitなど更新を チェックする場合にも、一発でミスを見つけられるので、 可能な限り、直ハードコートよりはいいかもですね~。
補足
回答ありがとうございます、なるほど処理をまとめることが可能なんですね。 共通の処理をまとめて文字数の部分だけを分けるという感じでしょうか? class A{ const B=?; const C=?; }
お礼
質問の期限が切れてしまいました、申し訳ありません。 下記のURLからアドバイスお願い致します。 https://okwave.jp/qa/q10155839.html
補足
アドバイスありがとうございます、attachディレクトリを追加したところ表示することが出来ました。 不正なトークンと表示されるため、single-regist.phpから下記のコードを削除したのですが、そちらは問題ないでしょうか? 送信時にトークンが必要だと思いますので、登録結果画面には必要ないのではないかと考えております。 //確認画面でデータが安全であることが確認された上で$_SESSIONを経由して画面遷移するが、登録結果画面へは不正アクセスできるから必須 if (empty($_SESSION['token']) || empty($_POST['token']) || $_SESSION['token'] !== $_POST['token']) { exit('不正トークン'); } ※single-regist.phpの11行目からトークンのコードになります https://wandbox.org/permlink/vCbUEZFcvQf2n1DK