共有フォルダ上のCSVをサイトにtableタグで自動挿入させてみた

某事業会社さんでは、Excelで更新した表をHTMLの`<table>`タグに埋め込んでコーポレートサイトにアップロードする、という日次のルーティン業務がありました。

これを半自動化したので記事にします。

# 基本方針
長らく情シスで行っていた業務ですが、事務職で雇用しているパートさんに業務を移管したい。（というか自分はやりたくない）
かと言って、うっかりファイルを壊されたりすると情シスの仕事が増えてしまうので、サーバに専用のディレクトリを作成し、当該ディレクトリのみアクセス可能なFTPアカウントを作成、それをパートさんに渡します。
SFTP/SCPではありません。暗号化されていない従来のFTPです。
このご時世にFTPを使う理由は、Windowsの**ネットワークプレイス機能**でFTPサーバに接続するため。
エクスプローラでは普通の共有フォルダに見えるので難しいと感じさせず、運用をすんなりと受け入れてもらえます。
セキュリティ的にどうなのよ、という意見はあるのですが、そもそも公開データなので、暗号化する意味がありませんし、仮にパスワードが漏れても閉じたディレクトリにしかアクセスできないので、問題は無いと考えました。
ただしグローバルIPアドレスで制限はかけるので社内でしかアクセスできません。

WebDAVは昨今のWindowsで非推奨になっているので採用しませんでした。

# サーバ側の設定
## FTPサーバの設定
`vsftpd`を使いました。
chrootを有効にして特定のディレクトリ配下のみアクセスを認めるようにします。

```conf:/etc/vsftpd.conf
# ホームディレクトリより上層へのアクセスを禁止
chroot_local_user=YES

# ホームディレクトリより上層へのアクセスを許可するユーザのリストを有効に
chroot_list_enable=YES

# リストに記載したユーザは上層へのアクセスを許可
chroot_list_file=/etc/vsftpd.chroot_list

# 匿名接続を禁止
anonymous_enable=NO
```

さらに、アクセスできるIPアドレスを`iptables`で制限しておきます。

# クライアント側の設定
## FTP接続テスト
Windows標準のFTPコマンド、またはWinSCPなどのFTPクライアントソフトで接続できるかテストします。

## ネットワークプレイスの追加
FTPで接続できれば、ネットワークプレイスを追加できるはずです。
Windows10 の手順を示します。Windows11 でもあまり変わらないと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236222/fb9ca97f-5054-ca34-ff8f-dea7eba9f45e.png)
アドレスとユーザ名を入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236222/bf0e3ffc-b261-f343-7a81-31fea3b1de70.png)
名前を任意で入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236222/23a2596c-b1ad-b809-be5e-eda7f83b368a.png)
ここではパスワードを保存することにし「ネットワークの場所」に追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236222/95d25cd2-380b-70dc-07ce-21a672dfe21c.png)

この「ネットワークの場所」に、ファイル名固定で作成してもらうことにしました。
形式は「CSV UTF-8」です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236222/ba9db600-273a-41da-b102-ee64ccc64947.png)
実際には、VBAマクロで自動化されています。

# Webサイトに仕掛けよう
コーポレートサイトは、CMSの定番 WordPress で構築されています。
なので、CSVファイルを読み込みHTMLに変換するPHPコードを、所望の場所に埋め込んであげるだけです。

SQL文は独自テーブルと連携するために使っています。
また、Excelで編集するため、CSVファイルにUTF-8のBOMがあっても動作するようにしています。

入力データは事務員が作成するので、少しでもレイアウトが違っていたら`break`で抜ける（つまり何も表示しない、エラーメッセージも表示しない）ようにしています。

```php:PHP
<div class="center-container">
<?php
while(true) {
	$csv_file = '/home/foo/bar.csv';
	if (!is_file($csv_file)) break;
	$data = array_map('str_getcsv', file($csv_file));
	if (empty($data)) break;
	if (preg_replace('/^\xEF\xBB\xBF/', '', $data[0][0]) !== 'サービス名') break;
	echo '<table class="info"><tr><th>サービス名</th><th>拠点</th><th>残枠</th></tr>';
	foreach ($data as $index => $row) {
		if ($index === 0) continue;
		if ($row[2] == 0) continue;
		echo '<tr>';
		global $wpdb;
		$result = $wpdb->get_row("SELECT * FROM $wpdb->services WHERE code='".$row[0]."'");
		echo '<td class="area">';
		echo htmlspecialchars($row[0]);
		if (empty($result) || trim($result->url)==='') {
		} else {
			echo '<br class="d-md-none"><a class="page" href="' . $result->url . '">案内図</a>';
		}
		echo '</td>';
		echo '<td>' . htmlspecialchars($row[1]) . '</td>';
		echo '<td>' . htmlspecialchars($row[3]) . '</td>';
		echo '</tr>';
	}
	echo '</table>';
	date_default_timezone_set('Asia/Tokyo');
	echo '<p class="update">更新日時：' . date('Y年n月j日 G時i分', filemtime($csv_file)) . '</p>';
	echo '<a class="seemore" href="/serv"><i class="fas fa-angle-double-right"></i>もっと見る</a>';
	break;
}
?>
</div>
```

ついでにSCSSも挙げておきますね。

```scss:SCSS
// ----- デバイスサイズ ----- //
$sp: 767px;
// ----- ベースの値 ----- //
$base_color: #130079;

// ----- メディアクエリ ----- //
@mixin sp {
	@media (max-width: ($sp)) {
		@content;
	}
}

table.info {
	margin: 0;
	border: solid 2px #aaa;
	color: #333;
	font-size: 15px;
	line-height: 1.5;
	letter-spacing: 1;
	tr {
		th, td {
			vertical-align: middle;
			padding: 3px 5px;
		}
		th {
			background: #eee;
		}
		td {
			background: #fff;
			@include sp {
				margin: 0;
				padding: 1px 8px;
				font-size: 12px;
				line-height: 1.2;
				letter-spacing: 0;
			}
		}
		td.area {
			width: 25%;
			a.page {
				display: inline;
				background: $base_color;
				font-size: 13px;
				font-weight: 600;
				line-height: 0;
				letter-spacing: 0;
				color: #fff;
				border-radius: 6px;
				margin: 0 0 0 6px;
				padding: 1px 6px;
				@include sp {
					margin: 0;
					padding: 0 8px;
					font-size: 11px;
					line-height: 0;
					letter-spacing: 1;
				}
			}
		}
	}
}
p.update {
	margin: 0;
	color: #666;
	font-size: 12px;
	text-align: right;
}
a.seemore {
	margin: 0 6px 0 0;
	color: $base_sub_color;
	font-size: 13px;
	font-weight: bold;
	text-align: right;
	letter-spacing: 0;
	i {
		margin-right: 2px;
	}
}
```

以上です。
事業会社さんでは、ありがちな業務ではないかと思い、共有しました。