WordPressでカート機能を作る方法。プラグインなしで自作しよう
こんにちは。あっきーです。
フリーのエンジニアで、webサイト・サービスを作りながら生活しています。
今回は「Wordpressでカート機能を作ってみよう。」という回です。
カート機能はECサイトによくある買い物かごです。WordpressにはECサイト構築用のプラグインがあり(WooCommerceやWelcart)これらを使うと誰でも簡単にカート機能を実装できます。
ただ、これらのプラグインに対応したテーマを使わないといけなかったり、プラグインをインストールすることでサイトの動きが悪くなったりする可能性もあります。
なので、どんなテーマでもカート機能を構築したいという方向けにその方法を教えます。
プログラマー向けにも、コードの解説をするので勉強にも使ってください。
手っ取り早く構築したいしたい人へ
「コードの説明とかそういうのはどうでも良い!」という人はこちらに乗っているのでコピペして一部修正して使ってください。
以降はコードを確認しながら機能の説明をしていきます。
全体の流れ
まず、カート機能を実装するための流れです。すでにwordpressでサイトを運営している前提で話します。
- 商品用ページを作成する(なくても良い)
- カートボタンを設置
- カート画面を作成
- 申し込み画面を作成
- 確認画面を作成
- 完了画面を作成
また以降はテーマファイルを編集していきます。バックアップを取ってから編集をしてください!
カスタム投稿を使って商品ページを作成する
まずは商品作成用のページを作っていきます。ここは必ず必要というわけでもないんですが、作った方が管理がしやすいです。
functions.php
に以下のコードを書きます。
function create_post_type() {
register_post_type('product', [ //投稿タイプ名
'labels' => [
'name' => '商品', // 管理画面上の名前
'singular_name' => 'product', // カスタム投稿の識別名
],
'public' => true, //投稿タイプをpublicにする
'has_archive' => true, //アーカイブ機能をオン
'menu_position' => 5, // 管理画面上での配置場所
'supports' => array('title','editor','thumbnail','excerpt'),
]);
register_taxonomy_for_object_type('category', 'product');
}
add_action('init', 'create_post_type');
保存すると管理画面に新しく「商品」という項目が現れたかと思います。
ここから商品を作成することができます。見た目は「投稿」と全く同じです。
また、商品に値段をつけたいので、値段を設定できるようにしましょう。
先ほどのコードの下に以下を書いてください。
function add_product_fields() {
add_meta_box('product_setting', '商品情報', 'insert_product_fields', 'product', 'normal');
}
add_action('admin_menu', 'add_product_fields');
// カスタムフィールドの入力エリア
function insert_product_fields() {
global $post;
$data = get_post_meta($post->ID, 'product', true);
echo '<div style="margin-top: 15px">価格 : <input type="number" name="product_price" min="0" value="'.$data['price'].'" />円 </div>';
}
このように書くと、商品作成画面の下の方に価格を設定できる欄ができるかと思います。
また、設定した料金を保存できるように以下のコードを追加します。
// カスタムフィールドの値を保存
function save_product_fields($post_id) {
$name = get_the_title($post_id);
$data = [
'name' => $name,
'price' => $_POST['product_price'],
];
update_post_meta($post_id, 'product', $data);
}
add_action('save_post', 'save_product_fields');
これで商品ページを作ることができました。実際に投稿する際は
- タイトル → 商品名
- アイキャッチ画像 → 商品画像
- 記事欄 → 商品の説明
- 価格 → 商品の値段
のように設定しましょう。
商品ページを表示
次は実際に商品ページを表示させていきます。
テーマフォルダ直下(index.php
などがある場所)に新たにsingle-product
を作成しましょう。
そして以下のコードを書きます。
<?php get_header(); ?>
<article>
<div class="container">
<?php if(have_posts()) : while(have_posts()) : the_post(); ?>
<div class="row">
<div class="col-6">
<?php if(has_post_thumbnail()) : ?>
<div class="img-container thumbnail">
<img src="<?= get_the_post_thumbnail_url('', 'full') ?>" alt="<?php the_title(); ?>">
</div>
<?php endif; ?>
</div>
<div class="col-6">
<h1 class="text-center"><?php the_title(); ?></h1>
<section>
<?php the_content(); ?>
</section>
<section>
<p>料金 : <?php echo get_post_meta($post->ID, 'product', true)['price']; ?>円</p>
<input type="hidden" name="csrf_token" id="csrfToken" value="<?= session_id(); ?>">
<button id="addProductButton" class="btn btn-success">カートに追加</button>
<p id="message"></p>
</section>
</div>
</div>
<?php endwhile; endif ?>
</div>
</article>
<?php get_footer(); ?>
レイアウトはシンプルなものにしています。
商品ページを作成すると以下のように表示されると思います。
カートボタンを追加する
次は実際にカートに商品を追加していきます。
すでに「カートに追加」ボタンは作成されていますが、このボタンを押してもエラーになってしまいます。
ボタンが機能するように構築をこれからしていきます。
実装方法はいくつかありますが、ここではajaxを使っていきたいと思います。
ajaxの実装
wordpressにはjQueryがすでに組み込まれているのでajaxは簡単に実装できます。
main.js
ファイルを作ってコードを書いていきましょう。
jQuery(function($) {
$('#addProductButton').on('click', function() {
$thisButton = $(this);
//二重送信を防ぐ
$thisButton.attr('disabled', true);
//ajax送信
$.ajax({
type: "POST",
url: ajax_script.url,
data: {
'action' : 'add_product',
'product' : product,
'csrf_token' : csrfToken.value
},
dataType: "json"
}).done(function(data) { //成功
if(data == 0) {
$('#message').text('すでに商品が追加されています。');
} else {
$('#message').text('商品を追加しました。');
}
$thisButton.attr('disabled', false);
}).fail(function(XMLHttpRequest, textStatus, error) { //失敗
console.log(error);
console.log(XMLHttpRequest.responseText);
$thisButton.attr('disabled', false);
});
});
});
以降やるべきことは2つです。
- 送信データを定義する(ここでは変数
product
) - 送信後の処理を作る
送信データを定義する
今の段階だと、ajax_script.url
やproduct
といった変数が定義されていないのでこれらを定めます。
functions.php
で以下のコードを書きましょう。
function my_enqueue() {
// 特定のページのみで読み込む
// Ajaxの処理を書いたjsの読み込み
wp_enqueue_script( 'ajax-script', get_template_directory_uri().'/dist/main.js', array('jquery'), null, true );
// 「ad_url.ajax_url」のようにしてURLを指定できるようになる
wp_localize_script( 'ajax-script', 'ajax_script', array('url' => admin_url('admin-ajax.php')));
if(is_singular('product')) {
global $post;
$data = get_post_meta($post->ID, 'product', true);
wp_localize_script('ajax-script', 'product', array(
'id' => $post->ID,
'name' => $data['name'],
'price' => $data['price'],
'description' => $data['description'],
'thumbnail' => get_the_post_thumbnail_url($post->ID, 'full'),
));
}
}
add_action( 'wp_enqueue_scripts', 'my_enqueue' );
wp_localize_script
を使うと、scriptタグを挿入することができます。
webページのソースコードで確認できます。
ajax_script
はajax通信する情報(urlなど)が格納されていて
product
は商品情報を持っています。
これで、必要なデータを定義することができました。
送信後の処理を作る
次は送信後の処理を作っていきます。functions.php
に書いていきます。
//セッション開始
function my_session_start() {
if(session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
}
add_action('init', 'my_session_start');
// csrf対策
function check_csrf_token($token) {
if($token !== session_id()) {
wp_die('不正なリクエストです', 'error', array('response' => 400));
}
}
//商品を追加
function add_product() {
check_csrf_token($_POST['csrf_token']);
$data = $_POST['product'];
//商品項目がない場合は作成
if(!array_key_exists('order', $_SESSION)) {
$_SESSION['order'] = array();
$_SESSION['order']['products'] = array();
$_SESSION['order']['total'] = 0;
}
$item_exists = false;
//すでにアイテムが存在しているかチェック
foreach($_SESSION['order']['products'] as $num => $item) {
if($item['id'] == $data['id']) {
$item_exists = true;
break;
}
}
if($item_exists) { //商品がすでに存在している場合は追加しない
echo 0;
} else { //商品を追加し、総額も増加させる
array_push($_SESSION['order']['products'], $data);
$_SESSION['order']['total'] += $data['price'];
header("Content-Type: application/json; charset=UTF-8");
echo json_encode($data, JSON_UNESCAPED_UNICODE);
}
wp_die();
}
// ログインしているユーザー向け関数
add_action( 'wp_ajax_add_product', 'add_product' );
// 非ログインユーザー用関数
add_action( 'wp_ajax_nopriv_add_product', 'add_product' );
まず、カート機能にはセッションが欠かせないのでセッションを開始する関数を作っています(my_session_start
)。
また、データ送信が関わる処理なので、csrf対策もしておきます(check_csrf_token<\code>)。
add_product
が実際に商品を追加する処理を書いた関数です。
コメントを見ていただければどんな処理をしているかが分かるかと思います。取得したデータをセッションに格納しているだけなので、そこまで難しくありません。
これで「カートに追加」ボタンを押せば商品が追加されます。
カートページを作成する
カートに入れた商品一覧を表示するカートページを作成していきましょう。
新たにpage-cart.php
ファイルを作成し以下のコード書きます。
<?php get_header(); ?>
<div class="container">
<h1 class="text-md text-center mb-5">商品カート</h1>
<div>
<section class="mb-3 border-bottom">
<table class="table">
<thead>
<tr>
<th scope="col">商品情報</th>
<th scope="col">値段 (円)</th>
<th scope="col">変更</th>
</tr>
</thead>
<tbody>
<?php if(!empty($_SESSION['order']['products'])) : foreach ($_SESSION['order']['products'] as $key => $value) : ?>
<tr>
<td>
<p><?= $value['name'] ?></p>
<div class="img-container w-60">
<img src="<?= esc_html($value['thumbnail']); ?>" alt="<?= esc_html($value['name']); ?>">
</div>
</td>
<td><?= esc_html(number_format($value['price'])); ?></td>
<td><button class="btn btn-secondary delete-product-button" value="<?= esc_html($value['id']); ?>">取り消し</button></td>
</tr>
<?php endforeach; ?>
<?php else : ?>
<tr>
<td>商品がありません</td>
<td>-</td>
<td></td>
</tr>
<?php endif ?>
</tbody>
</table>
<div class="mb-3 d-flex justify-content-between align-items-center">
<p class="bg-gray-light px-1 py-3 text-center"><span class="mr-3">合計</span> <?= esc_html( number_format($_SESSION['order']['total'])); ?> 円</p>
</div>
</section>
<input type="hidden" name="csrf_token" id="csrfToken" value="<?= session_id(); ?>">
<?php if(!empty($_SESSION['order']['products'])): ?>
<div class="text-right">
<a href="<?= home_url('order'); ?>" class="btn bg-green submit-button">申し込みへ進む</button>
</div>
<?php endif; ?>
<p class="text-danger" id="errorMessage"></p>
</div>
</div>
<?php get_footer() ?>
セッションファイルにある商品情報をここで出力しています。
商品がない場合は「商品がありません」と表示されるように設計しています。
その後固定ページで「カートページ」を作成してください。このとき、コンテンツは何も書かなくて良いんですが、パーマリンクは「cart」で保存してください。でないと表示されません。
非常にシンプルですが、以下のように表示されるはずです。
商品の削除機能を構築する
このカートページでは、商品の削除を行えるようにしましょう。
やり方は商品追加のときと同じです。main.jsに以下を書き足します。
$('.delete-product-button').on('click', function() {
$thisButton = $(this);
//二重送信を防ぐ
$thisButton.attr('disabled', true);
//ajax送信
$.ajax({
type: "POST",
url: ajax_script.url,
data: {
'action' : 'delete_product',
'product_id' : $thisButton.val(),
'csrf_token' : csrfToken.value
},
dataType: "json"
}).done(function(data) { //成功
document.location.reload();
}).fail(function(XMLHttpRequest, textStatus, error) { //失敗
console.log(error);
console.log(XMLHttpRequest.responseText);
$thisButton.attr('disabled', false);
});
});
またfunctions.php
に通信時の処理を書きます。
//商品を削除
function delete_product() {
check_csrf_token($_POST['csrf_token']);
foreach($_SESSION['order']['products'] as $num => $item) {
if($item['id'] == $_POST['product_id']) { //商品の削除と合計額の減算
array_splice($_SESSION['order']['products'], $num, 1);
$_SESSION['order']['total'] -= $item['price'];
break;
}
}
echo 1;
wp_die();
}
// ログインしているユーザー向け関数
add_action( 'wp_ajax_delete_product', 'delete_product' );
// 非ログインユーザー用関数
add_action( 'wp_ajax_nopriv_delete_product', 'delete_product' );
これで、削除ボタンも完成しました。
申し込み画面を作成する
次は申し込み画面です。非常にシンプルに以下のような画面を作ります。
新たにpage-order.php
というファイルを作成して、以下のように書きます。
<?php get_header(); ?>
<div class="container">
<h1 class="text-md text-center">申し込み</h1>
<form id="profileForm" name="profile_form">
<div class="form-group">
<div>
<label for="name">お名前</label>
<input type="text" name="name" id="name" class="form-control required" value="<?= esc_html( $_SESSION['order']['profile']['name']); ?>">
<p class="error" id="nameError"></p>
</div>
</div>
<div class="form-group">
<label for="email">メールアドレス</label>
<input type="email" name="email" id="email" class="form-control required email" value="<?= esc_html( $_SESSION['order']['profile']['email']); ?>">
<p class="error" id="emailError"></p>
</div>
<div class="form-group">
<div>
<label for="zip">郵便番号</label>
<input type="text" name="zip" id="zip" class="form-control required number" onKeyUp="AjaxZip3.zip2addr('zip', '', 'address', 'address');" value="<?= esc_html( $_SESSION['order']['profile']['zip']); ?>">
<p class="error" id="zipError"></p>
</div>
<div class="mt-2">
<label for="address">住所</label>
<input type="text" name="address" id="address" class="form-control required" value="<?= esc_html( $_SESSION['order']['profile']['address']); ?>">
<p class="error" id="addressError"></p>
</div>
</div>
<div class="button-group">
<a href="<?= home_url('cart') ?>" class="btn btn-info">戻る</a>
<input type="hidden" name="csrf_token" id="csrfToken" value="<?= session_id(); ?>">
<button type="submit" id="submitButton" class="btn bg-green submit-button">次へ</button>
</div>
<p id="message" class="error"></p>
</form>
</div>
<?php get_footer() ?>
ここでは郵便番号から住所を自動入力する方法をとっています。html側ではこれでOKなのですが、自動入力するためのスクリプトを持ってくる必要があるので、それを書きましょう。
と言っても簡単で、functions.php
の中で、「カートボタンを追加する」の部分で書いたmy_enqueue
関数を編集してください。
function my_enqueue() {
// 特定のページのみで読み込む
// Ajaxの処理を書いたjsの読み込み
wp_enqueue_script( 'ajax-script', get_template_directory_uri().'/dist/main.js', array('jquery'), null, true );
// 「ad_url.ajax_url」のようにしてURLを指定できるようになる
wp_localize_script( 'ajax-script', 'ajax_script', array('url' => admin_url('admin-ajax.php')));
if(is_singular('product')) {
global $post;
// メタデータを取得
$data = get_post_meta($post->ID, 'product', true);
wp_localize_script('ajax-script', 'product', array(
'id' => $post->ID,
'name' => $data['name'],
'price' => $data['price'],
'description' => $data['description'],
'thumbnail' => get_the_post_thumbnail_url($post->ID, 'full'),
));
}
//追加
if(is_page('order')) {
wp_enqueue_script('ajaxzip3', 'https://ajaxzip3.github.io/ajaxzip3.js', array('jquery'), null, true);
}
}
add_action( 'wp_enqueue_scripts', 'my_enqueue' );
これで住所の自動入力ができます。
また、ここでもajax通信するので、main.js
にコードを追加します。
$('#profileForm').on('submit', function(e) {
e.preventDefault();
//二重送信を禁止
$('#submitButton').attr('disabled', true);
var formData = new FormData(e.target);
//バリエーション
var errors = validation(formData);
$('.error').text('');
//エラーチェック
if(Object.keys(errors).length !== 0) {
for(var key in errors) {
var strArray = key.split('_');
for(var i = 0 ; i < strArray.length ; i++) {
if(i == 0) {
continue;
}
strArray[i] = strArray[i][0].toUpperCase() + strArray[i].slice(1);
}
var keyForId = strArray.join('') + 'Error';
// delete Object.assign(errors, {[keyForId]: errors[key]})[key];
$(`#${keyForId}`).text(errors[key]);
}
$('#submitButton').attr('disabled', false);
$('#message').text('入力に誤りがあります。');
return;
}
formData.append('action', 'save_profile');
//ajax通信
$.ajax({
type: "POST",
url: ajax_script.url,
data: formData,
processData: false,
contentType: false,
}).done(function(data) { //成功
window.location.href = data;
}).fail(function(XMLHttpRequest, textStatus, error) {失敗
console.log(error);
console.log(XMLHttpRequest.responseText);
$('#submitButton').attr('disabled', false);
});
});
//バリエーション
function validation(formData) {
var errors = {};
var regex = null;
for(var item of formData) {
var key = item[0];
var value = item[1];
$el = $(`input[name='${key}']`);
if($el.hasClass('required') && !value) { // 必須項目のバリデーション
errors[key] = 'この項目は入力必須です。';
}
else if($el.hasClass('number')) {
regex = /^[0-9]+$/;
if(!regex.test(value)) {
errors[key] = '数値のみで入力してください。';
}
}
else if($el.hasClass('email')) {
regex = /^[A-Za-z0-9]{1}[A-Za-z0-9_.-]*@{1}[A-Za-z0-9_.-]{1,}\.[A-Za-z0-9]{1,}$/;
if(!regex.test(value)) {
errors[key] = '無効なメールアドレスです。';
}
}
}
return errors;
}
[/html_escape]</code></pre>
<p>バリデーションをフォーム送信する前にjavascript側で行っています。<br />
それ以外は商品追加・削除と同じ構造になっています。</p>
<p>そして、先ほどと同じように通信時の処理を<code>functions.php</code>に書いていきます。</p>
<pre><code class="language-php">[html_escape]
// 申し込みの際のお客様情報
function save_profile() {
check_csrf_token($_POST['csrf_token']);
$_SESSION['order']['profile'] = $_POST;
echo home_url('confirmation');
wp_die();
}
// ログインしているユーザー向け関数
add_action( 'wp_ajax_save_profile', 'save_profile' );
// 非ログインユーザー用関数
add_action( 'wp_ajax_nopriv_save_profile', 'save_profile' );
送信データをセッション変数に格納しました。
また、遷移するurlを返し、javascriptでこのurl(確認画面)に実際に移動します。
確認画面を作成する
次は確認画面です。これはシンプルにセッション変数に格納したデータを表示するだけです。
新たにpage-confirmation.php
というファイルを作成し、以下のコードを書きます。
<?php get_header(); ?>
<div class="container">
<h1 class="text-md text-center">申し込み確認</h1>
<section class="mb-4">
<table class="table">
<thead>
<tr>
<th scope="col">商品情報</th>
<th scope="col">値段 (円)</th>
</tr>
</thead>
<tbody>
<?php foreach ($_SESSION['order']['products'] as $key => $value) : ?>
<tr>
<td>
<p><?= esc_html($value['name']) ?></p>
<div class="img-container w-60">
<img src="<?= $value['thumbnail'] ?>" alt="<?= esc_html($value['name']) ?>">
</div>
</td>
<td><?= esc_html(number_format($value['price'])) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<p>合計金額 : <?= esc_html(number_format($_SESSION['order']['total'])); ?>円</p>
</section>
<section class="mb-4">
<div>
<p>お名前 : <?= esc_html($_SESSION['order']['profile']['name']); ?></p>
</div>
</section>
<section class="mb-4">
<p>メールアドレス : <?= esc_html($_SESSION['order']['profile']['email']); ?></p>
</section>
<section class="mb-4">
<div>
<p>郵便番号 : <?= esc_html($_SESSION['order']['profile']['zip']); ?></p>
</div>
<div class="mt-2">
<p>住所 : <?= esc_html($_SESSION['order']['profile']['address']); ?></p>
</div>
</section>
<div class="button-group">
<a href="<?= home_url('order') ?>" class="btn btn-info">戻る</a>
<input type="hidden" name="csrf_token" id="csrfToken" value="<?= session_id(); ?>">
<button id="cofirmOrderButton" class="btn bg-green submit-button">申し込みを確定</button>
</div>
</div>
<?php get_footer() ?>
セッション変数が多次元配列になっているので複雑ですが、頑張りましょう。
そして、「申し込みを確定」を押したらデータベースに申し込み情報を保存していきます。ここでもajaxを使っていきます。
main.js
に同じように書きます。
$('#cofirmOrderButton').on('click', function() {
$thisButton = $(this);
$thisButton.attr('disabled', true);
$.ajax({
type: "POST",
url: ajax_script.url,
data: {
'action' : 'store_order_in_db',
'csrf_token' : csrfToken.value
}
}).done(function(data) {
console.log(data);
window.location.href = data;
}).fail(function(XMLHttpRequest, textStatus, error) {
console.log(error);
console.log(XMLHttpRequest.responseText);
$thisButton.attr('disabled', false);
});
})
ここでは、申し込み情報を管理するために独自にテーブルをデータベースに作成する必要があるので、そこからやりましょう。
申し込み情報を保存するデータベーステーブルを作成する
wordpress用のデータベースにアクセスしてwp_ordersample
というテーブルを作成しましょう。
wp_
という部分は必須なのでここは変えないでね。
各カラムは画像のようにしましょう。
そして、wp_content
フォルダの直下にdb.php
というファイルを作成してださい。
その後以下のコードをそのファイルに書きます。
<?php
require_once( ABSPATH . WPINC . '/wp-db.php' );
class my_wpdb extends wpdb {
var $tables = array( 'posts', 'comments', 'links', 'options', 'postmeta',
'terms', 'term_taxonomy', 'term_relationships', 'termmeta', 'commentmeta' ,
'ordersample');
}
if ( ! isset($wpdb) ) {
$wpdb = new my_wpdb(DB_USER, DB_PASSWORD, DB_NAME, DB_HOST);
}
[/html_escape]</code></pre>
<p>これで申し込み情報用のテーブルが作成できました。</p>
<p>そしたら<code>functions.php</code>にajax通信時の処理を書きます。</p>
<pre><code class="language-php">[html_escape]
function store_order_in_db() {
check_csrf_token($_POST['csrf_token']);
global $wpdb;
//データの名前
$table_columns = ['products', 'total', 'created_at', 'name', 'email', 'zip', 'address'];
$products = '';
$data = [];
//セッション変数の申し込み情報を取り出す
foreach ($_SESSION['order'] as $key => $value) {
if($key == 'products') { //商品情報は名前をカンマ区切りで保存する
foreach ($_SESSION['order']['products'] as $key => $value) {
$products .= $value['name'] . ',';
}
$data['products'] = $products;
} else if($key == 'profile') { //ユーザー情報も配列になっているのでループで取り出す
foreach ($_SESSION['order']['profile'] as $key => $value) {
if(!in_array($key, $table_columns)) continue;
$data[$key] = $value;
}
} else {
if(!in_array($key, $table_columns)) continue;
$data[$key] = $value;
}
}
//申し込み日時
$data['created_at'] = date("Y-m-d");
//データベースに保存
$sql = $wpdb->prepare(
"INSERT INTO $wpdb->ordersample
(products, total, created_at, name, email, zip, address)
values (%s, %u, %s, %s, %s, %s)",
$data['products'],
$data['total'],
$data['created_at'],
$data['name'],
$data['email'],
$data['zip'],
$data['address'],
);
$wpdb->query($sql);
echo home_url('complete');
wp_die();
}
// ログインしているユーザー向け関数
add_action( 'wp_ajax_store_order_in_db', 'store_order_in_db' );
// 非ログインユーザー用関数
add_action( 'wp_ajax_nopriv_store_order_in_db', 'store_order_in_db' );
これで実際に動かしてみると、データベースに申し込み情報が保存されます。
完了画面を作成する
最後に完了画面を作成しましょう。
page-complete.php
を作成し、以下のコードを書きます。
<?php get_header(); ?>
<div class="container">
<h1 class="text-md text-center">申し込み完了</h1>
<p>
お申込みありがとうございました。
</p>
</div>
<?php get_footer() ?>
申し込み完了後、画面が表示されれば完了です。
以上です。お疲れ様です。
こんな感じで、簡易的ではありますが、プラグインなしでカート機能を実装することができました。
こうやってプログラミングで自作できると世界が広がるので頑張りましょう。
ではでは、良い1日をお過ごしください。
スポンサードサーチ
人気記事英語学習用SNSをLaravelで作ってみた【システム解説あり】