こんにちは、ruckです。
先日、別のサイトでWordPress上にWebアプリ「住所表示ツール」を作りました。
実は、WordPressというシステムは、少しPHPなどを勉強すれば簡易的な計算アプリぐらいなら作れちゃいます。
そこで今回は、WordPress上で簡単計算アプリを作る方法を紹介します。
1.プラグインを使用してCSPを適切に管理
WordPressの管理画面で プラグイン > 新規追加 から 「CSP Manager」をインストール・有効化しましょう。
2025年1月現在ですと、”Content Security Policy Manager” by Patrick Sletvoldを推奨します。
CSP Managerの設定
設定 > CSP Manager で以下のポリシーを設定 default-src ‘self’; script-src ‘self’ ‘unsafe-inline’ ‘unsafe-eval’ https://cdnjs.cloudflare.com https://*.openstreetmap.org; style-src ‘self’ ‘unsafe-inline’ https://cdnjs.cloudflare.com; img-src ‘self’ data: https://*.tile.openstreetmap.org https://*.openstreetmap.org; connect-src ‘self’ https://geoapi.heartrails.com https://*.openstreetmap.org;
htaccessの修正
WordPressのルートディレクトリにある.htaccessファイルに以下を追加します。
# BEGIN Custom Headers
<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin "*"
Header set X-Content-Type-Options "nosniff"
Header set X-XSS-Protection "1; mode=block"
Header set X-Frame-Options "SAMEORIGIN"
</IfModule>
# END Custom Headers
2.Webアプリ用の固定ページを追加
まずは、Webアプリ用の固定ページを作成します。
WordPress管理画面の「固定ページ」から「新規作成」をクリックします。
Webアプリのタイトルを入力し、スラッグを入力します。
(スラッグの入力欄が無い時は、WordPress画面の上部に表示オプションがありますので、スラッグにチェックが入っているか確認してください。 )
スラッグは、この後のfunctions.phpの編集で使いますので必ず設定しておいてください。

※今回は、スラッグは、 「postal-distance」と入力しています。
次に以下のHTMLを本文(テキスト)に追加:
<div id="postal-distance-app"></div>
追加したコードがVisual(ビジュアル)タブに切り替えても、このdivタグが自動的に段落タグ(<p>)に変換されたりしていないことを確認するのと、スペースや改行が余分に入っていないことも併せて確認します。
タイトルと本文、スラッグを入力したら、公開を押します。
ページの内容は、functions.php上に書くので、ここでは未記入でOKです。
3.ファイルの配置
下記のJavaScriptコードをpostal-distance.jsとして保存します。
// postal-distance.js
console.log('Postal Distance App Loaded');
document.addEventListener('DOMContentLoaded', function() {
// HTMLの挿入
const container = document.getElementById('postal-distance-app');
container.innerHTML = `
<div class="postal-app-container" style="max-width: 800px; margin: 0 auto; padding: 20px;">
<div class="input-section" style="background: #f8f0ff; padding: 20px; border-radius: 8px; margin-bottom: 20px;">
<h2 style="color: #5a3b8c; margin-bottom: 20px;">郵便番号距離計算</h2>
<div class="input-group" style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; color: #5a3b8c;">出発地の郵便番号</label>
<input type="text" id="postal1" placeholder="例:123-4567"
style="width: 100%; padding: 8px; border: 1px solid #9c7ac4; border-radius: 4px;">
<div id="address1" style="margin-top: 5px; color: #5a3b8c;"></div>
</div>
<div class="input-group" style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; color: #5a3b8c;">目的地の郵便番号</label>
<input type="text" id="postal2" placeholder="例:123-4567"
style="width: 100%; padding: 8px; border: 1px solid #9c7ac4; border-radius: 4px;">
<div id="address2" style="margin-top: 5px; color: #5a3b8c;"></div>
</div>
<button id="calculate" style="width: 100%; padding: 10px; background: #5a3b8c; color: white;
border: none; border-radius: 4px; cursor: pointer;">計算する</button>
<div id="error-message" style="color: #ff0000; margin-top: 10px; display: none;">
入力に誤りがあります。
</div>
</div>
<div id="result" style="display: none; background: white; padding: 20px; border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px;">
<h3 style="color: #5a3b8c; margin-bottom: 15px;">計算結果</h3>
<div id="distance-result" style="color: #5a3b8c; margin-bottom: 10px;"></div>
<div id="time-result" style="color: #5a3b8c;"></div>
</div>
<div id="map" style="height: 400px; border-radius: 8px; margin-bottom: 20px;"></div>
<p style="color: #666; text-align: center; font-size: 14px;">
*大まかな時間と距離を把握するためのツールとしてご利用下さい。
</p>
</div>
`;
// 地図の初期化
let map = L.map('map').setView([35.6812, 139.7671], 5);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
let markers = [];
let polyline = null;
// 郵便番号のバリデーション
function isValidPostal(postal) {
return /^\d{3}-?\d{4}$/.test(postal);
}
// 距離の計算
function calculateDistance(lat1, lon1, lat2, lon2) {
const R = 6371; // 地球の半径(km)
const dLat = (lat2 - lat1) * Math.PI / 180;
const dLon = (lon2 - lon1) * Math.PI / 180;
const a =
Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
Math.sin(dLon/2) * Math.sin(dLon/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return R * c;
}
// 時間の計算(55km/h基準)
function calculateTime(distanceKm) {
const timeHours = distanceKm / 55;
return {
hours: Math.floor(timeHours),
minutes: Math.round((timeHours - Math.floor(timeHours)) * 60)
};
}
// マーカーの更新
function updateMap(locations) {
// 既存のマーカーとラインを削除
markers.forEach(marker => map.removeLayer(marker));
markers = [];
if (polyline) map.removeLayer(polyline);
// 新しいマーカーを追加
locations.forEach(loc => {
const marker = L.marker([loc.lat, loc.lon]).addTo(map);
markers.push(marker);
});
// 2点間を線で結ぶ
if (locations.length === 2) {
polyline = L.polyline([
[locations[0].lat, locations[0].lon],
[locations[1].lat, locations[1].lon]
], {color: '#5a3b8c'}).addTo(map);
// 地図の表示範囲を調整
const bounds = L.latLngBounds([
[locations[0].lat, locations[0].lon],
[locations[1].lat, locations[1].lon]
]);
map.fitBounds(bounds, {padding: [50, 50]});
}
}
// 計算ボタンのクリックイベント
document.getElementById('calculate').addEventListener('click', async function() {
const postal1 = document.getElementById('postal1').value;
const postal2 = document.getElementById('postal2').value;
const errorElement = document.getElementById('error-message');
const resultElement = document.getElementById('result');
// バリデーション
if (!isValidPostal(postal1) || !isValidPostal(postal2)) {
errorElement.style.display = 'block';
resultElement.style.display = 'none';
return;
}
errorElement.style.display = 'none';
try {
// APIリクエスト
const response1 = await fetch(`https://geoapi.heartrails.com/api/json?method=searchByPostal&postal=${postal1.replace('-', '')}`);
const response2 = await fetch(`https://geoapi.heartrails.com/api/json?method=searchByPostal&postal=${postal2.replace('-', '')}`);
const data1 = await response1.json();
const data2 = await response2.json();
if (!data1.response.location || !data2.response.location) {
throw new Error('Invalid postal code');
}
const location1 = data1.response.location[0];
const location2 = data2.response.location[0];
// 住所の表示
document.getElementById('address1').textContent =
`${location1.prefecture}${location1.city}${location1.town}`;
document.getElementById('address2').textContent =
`${location2.prefecture}${location2.city}${location2.town}`;
// 距離の計算
const distance = calculateDistance(
parseFloat(location1.latitude),
parseFloat(location1.longitude),
parseFloat(location2.latitude),
parseFloat(location2.longitude)
);
// 時間の計算
const time = calculateTime(distance);
// 結果の表示
document.getElementById('distance-result').textContent =
`直線距離: ${distance.toFixed(1)} km`;
document.getElementById('time-result').textContent =
`推定所要時間: ${time.hours}時間 ${time.minutes}分`;
resultElement.style.display = 'block';
// 地図の更新
updateMap([
{lat: parseFloat(location1.latitude), lon: parseFloat(location1.longitude)},
{lat: parseFloat(location2.latitude), lon: parseFloat(location2.longitude)}
]);
} catch (error) {
errorElement.style.display = 'block';
resultElement.style.display = 'none';
}
});
});
次に保存したpostal-distance.jsをサーバのテーマフォルダ内のjsディレクトリに配置します。
(例:wp-content/themes/cocoon-child-master/js/postal-distance.js)
データが大きければCyberduckなどのファイルマネージャーを使用してアップロードしましょう。
postal-distance.jsのデータのフォルダの属性も設定は以下としましょう。

JavaScriptファイル(postal-distance.js)は「644」で作成します。
この数値の意味を分解すると:
– 6: 所有者の権限(読み書き = 4+2 = 6)
– 4: グループの権限(読み込みのみ = 4)
– 4: その他の権限(読み込みのみ = 4)
「644」を選ぶ理由:
1. 所有者(自分)に読み書きの権限を与える(修正が必要な場合に備えて)
2. グループとその他のユーザー(Webサーバー含む)には読み込みのみの権限を与える
3. 実行権限は不要(JavaScriptファイルは実行ファイルではないため)
この設定は、Webサーバー上のJavaScriptファイルとして標準的で安全な設定です。
ファイルが読み取り可能でありながら、不必要な権限は制限されています。
尚、jsフォルダがなければ作成しましょう。

フォルダの属性は「705」です。
この数値の意味を分解すると:
– 7: 所有者の権限(読み書き実行 = 4+2+1 = 7)
– 0: グループの権限(なし = 0)
– 5: その他の権限(読み込みと実行 = 4+1 = 5)
「705」を選ぶ理由:
1. 所有者(自分)に全ての権限(読み書き実行)を与える
2. グループメンバーにはアクセス権を与えない
3. その他のユーザー(Webサーバー)には読み込みと実行の権限を与える
これはWebサーバー上のJavaScriptファイルを含むフォルダとして、セキュリティと機能性のバランスが取れた標準的な設定です。
これにより、親テーマの更新があっても、カスタマイズした内容が上書きされることなく維持されます。
4.functions.phpにコードを追加
functions.phpへのアクセス方法
- WordPressの管理画面にログインします。
- 左側メニューから「外観」→「テーマファイルエディター」をクリックします。
- 右側のファイル一覧から「functions.php」を探して選択します。
- 通常は子テーマ(cocoon-child-master)のfunctions.phpを編集します
コードの追加方法
functions.phpの一番下(?>の前)に以下のコードを追加しますが?>がなくても気にしないでください。むしろ無い方が安全設計です。
また、コードを追加する前に必ずfunctions.phpの内容をバックアップをとる事をお勧めします。(何かあった時の為)
住所アプリの概要
次に、functions.phpに計算処理や、入力フォームのコードを入力していきます。
その前に、今回作る住所アプリの簡単な概要を説明します。
まずはWebクライアント上で、住所を入力します。
入力したものをPOSTメソッドというものを使って、Webサーバー上へ渡します。
Webサーバー上で住所情報を検索したら、検索結果のHTMLをWebクライアントに返すという流れです。
コード
functions.phpに以下のコードをコピペしてください。
// postal-distance.js
document.addEventListener('DOMContentLoaded', function() {
// HTMLの挿入
const container = document.getElementById('postal-distance-app');
container.innerHTML = `
<div class="postal-app-container" style="max-width: 800px; margin: 0 auto; padding: 20px;">
<div class="input-section" style="background: #f8f0ff; padding: 20px; border-radius: 8px; margin-bottom: 20px;">
<h2 style="color: #5a3b8c; margin-bottom: 20px;">郵便番号距離計算</h2>
<div class="input-group" style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; color: #5a3b8c;">出発地の郵便番号</label>
<input type="text" id="postal1" placeholder="例:123-4567"
style="width: 100%; padding: 8px; border: 1px solid #9c7ac4; border-radius: 4px;">
<div id="address1" style="margin-top: 5px; color: #5a3b8c;"></div>
</div>
<div class="input-group" style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; color: #5a3b8c;">目的地の郵便番号</label>
<input type="text" id="postal2" placeholder="例:123-4567"
style="width: 100%; padding: 8px; border: 1px solid #9c7ac4; border-radius: 4px;">
<div id="address2" style="margin-top: 5px; color: #5a3b8c;"></div>
</div>
<button id="calculate" style="width: 100%; padding: 10px; background: #5a3b8c; color: white;
border: none; border-radius: 4px; cursor: pointer;">計算する</button>
<div id="error-message" style="color: #ff0000; margin-top: 10px; display: none;">
入力に誤りがあります。
</div>
</div>
<div id="result" style="display: none; background: white; padding: 20px; border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px;">
<h3 style="color: #5a3b8c; margin-bottom: 15px;">計算結果</h3>
<div id="distance-result" style="color: #5a3b8c; margin-bottom: 10px;"></div>
<div id="time-result" style="color: #5a3b8c;"></div>
</div>
<div id="map" style="height: 400px; border-radius: 8px; margin-bottom: 20px;"></div>
<p style="color: #666; text-align: center; font-size: 14px;">
*大まかな時間と距離を把握するためのツールとしてご利用下さい。
</p>
</div>
`;
// 地図の初期化
let map = L.map('map').setView([35.6812, 139.7671], 5);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
let markers = [];
let polyline = null;
// 郵便番号のバリデーション
function isValidPostal(postal) {
return /^\d{3}-?\d{4}$/.test(postal);
}
// 距離の計算
function calculateDistance(lat1, lon1, lat2, lon2) {
const R = 6371; // 地球の半径(km)
const dLat = (lat2 - lat1) * Math.PI / 180;
const dLon = (lon2 - lon1) * Math.PI / 180;
const a =
Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
Math.sin(dLon/2) * Math.sin(dLon/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return R * c;
}
// 時間の計算(55km/h基準)
function calculateTime(distanceKm) {
const timeHours = distanceKm / 55;
return {
hours: Math.floor(timeHours),
minutes: Math.round((timeHours - Math.floor(timeHours)) * 60)
};
}
// マーカーの更新
function updateMap(locations) {
// 既存のマーカーとラインを削除
markers.forEach(marker => map.removeLayer(marker));
markers = [];
if (polyline) map.removeLayer(polyline);
// 新しいマーカーを追加
locations.forEach(loc => {
const marker = L.marker([loc.lat, loc.lon]).addTo(map);
markers.push(marker);
});
// 2点間を線で結ぶ
if (locations.length === 2) {
polyline = L.polyline([
[locations[0].lat, locations[0].lon],
[locations[1].lat, locations[1].lon]
], {color: '#5a3b8c'}).addTo(map);
// 地図の表示範囲を調整
const bounds = L.latLngBounds([
[locations[0].lat, locations[0].lon],
[locations[1].lat, locations[1].lon]
]);
map.fitBounds(bounds, {padding: [50, 50]});
}
}
// 計算ボタンのクリックイベント
document.getElementById('calculate').addEventListener('click', async function() {
const postal1 = document.getElementById('postal1').value;
const postal2 = document.getElementById('postal2').value;
const errorElement = document.getElementById('error-message');
const resultElement = document.getElementById('result');
// バリデーション
if (!isValidPostal(postal1) || !isValidPostal(postal2)) {
errorElement.style.display = 'block';
resultElement.style.display = 'none';
return;
}
errorElement.style.display = 'none';
try {
// APIリクエスト
const response1 = await fetch(`https://geoapi.heartrails.com/api/json?method=searchByPostal&postal=${postal1.replace('-', '')}`);
const response2 = await fetch(`https://geoapi.heartrails.com/api/json?method=searchByPostal&postal=${postal2.replace('-', '')}`);
const data1 = await response1.json();
const data2 = await response2.json();
if (!data1.response.location || !data2.response.location) {
throw new Error('Invalid postal code');
}
const location1 = data1.response.location[0];
const location2 = data2.response.location[0];
// 住所の表示
document.getElementById('address1').textContent =
`${location1.prefecture}${location1.city}${location1.town}`;
document.getElementById('address2').textContent =
`${location2.prefecture}${location2.city}${location2.town}`;
// 距離の計算
const distance = calculateDistance(
parseFloat(location1.latitude),
parseFloat(location1.longitude),
parseFloat(location2.latitude),
parseFloat(location2.longitude)
);
// 時間の計算
const time = calculateTime(distance);
// 結果の表示
document.getElementById('distance-result').textContent =
`直線距離: ${distance.toFixed(1)} km`;
document.getElementById('time-result').textContent =
`推定所要時間: ${time.hours}時間 ${time.minutes}分`;
resultElement.style.display = 'block';
// 地図の更新
updateMap([
{lat: parseFloat(location1.latitude), lon: parseFloat(location1.longitude)},
{lat: parseFloat(location2.latitude), lon: parseFloat(location2.longitude)}
]);
} catch (error) {
errorElement.style.display = 'block';
resultElement.style.display = 'none';
}
});
});
コード解説
このコードを解説します。
add_postal_distance_scriptsという関数を定義- この関数は必要なJavaScriptとCSSを読み込む役割を持ちます
is_page('postal-distance')で判定- ‘postal-distance’という固定ページの時だけスクリプトを読み込みます
- これにより、不要なページでスクリプトを読み込まないようにします
- 必要なファイルの読み込み
- Leaflet(地図表示用)のCSSとJavaScript
- 作成した郵便番号距離計算アプリのJavaScript
add_actionでWordPressのタイミングに合わせて実行wp_enqueue_scriptsというタイミングで実行されます
このコードは:
- 必要なJavaScriptとCSSファイルを読み込む設定
- アプリケーション用のスタイル定義 を追加します。
スタイルはWordPressの管理画面から簡単に調整できるように、別途CSS定義を追加しています。
5.エラーとデバッグ
上記の手順で進めてきても稀に表示されないこともあります。
その場合は、サイトを開発者ツールで開き原因を調べましょう。
開発者ツールはブラウザ上で F12 キーを押すか、右クリック → 「検証」を選択することでひらけます。
問題を修正して更新してもブラウザのキャッシュが残っていて変化がない場合があるので、Shift+Ctrl+Rで更新して変化を確認しましょう。
主要なタブの説明

- Console(コンソール)タブ
- JavaScript のエラーや警告が表示される場所
- 現在表示されているエラー:
- Content Security Policy…: JavaScriptのセキュリティポリシーに関する警告
- Page layout may be unexpected…: ページレイアウトに関する注意
- その他、Cookie関連の警告が複数表示されています
- Elements(要素)タブ
- ページのHTML構造を確認できる場所
- 表示されている要素の編集や確認が可能
エラーチェックの手順
- Consoleタブを開く
- 赤い×マーク(❌): 重大なエラー
- 黄色い三角(⚠️): 警告
- 青い情報マーク(ℹ️): 情報
- エラーメッセージの確認
- クリックすると詳細情報が表示される
- ファイル名と行番号が表示される(例:lockdown-install.js:1)
まずはConsoleタブの赤い×マーク(❌)を解決するところから着手しましょう。
一つずつ問題を解決していけば必ずアプリが正しく機能するようになります。
6.最後に
いかがでしたか?
今回はすごく簡単なアプリでしたが、POSTメソッドの使い方や、PHPのコードを勉強すれば、もう少し複雑なアプリも作れると思います。
WordPressでブログを書くだけじゃなく、色んなWebアプリが作れるようになれば可能性が広がっていきますね。
みなさんもぜひ、WordPress上でWebアプリ作成にチャレンジしてみてください!
それでは。

コメント