Slackでの勤怠入力でAPI経由でOutlookの予定表にイベントを追加するメモ

#できたことダイジェスト
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223443/972da619-dff2-7b14-b86d-9ae8da64da94.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223443/71351302-74d1-8e5b-0511-d9b90f399543.png)


#ここまでのお話
前回といってもかなり前の話ですが、[Slackで勤怠管理して可視化する](https://qiita.com/celery/items/046a6495574cd03ce414)ということをやって、会社のグループ単位でYellowfin(BIツール)のダッシュボードを確認して勤怠を確認できるようにしました。(もし下記にあるコードを参照するならこの記事も確認してもらえると読みやすいと思います。)
その時スケジュール管理ツールにaipoを使っていたので、休日や半休等の場合、aipoのDBに直接アクセスして予定をinsert,update,deleteしていたのですが、オープンソースでの提供が終わりセキュリティ的にも懸念があることからOutlookへ移行しました。
一覧でグループの人の予定が見れないとか色々ありましたが、先述したSlack投稿での勤怠管理での休み登録で予定表に追加されないため手動でOutlookの予定表に休みを登録しなければならずめんどい、という声が多数でどうにかならないかなと感じでいたところでした。
そこでOutlookにもなにかAPIがあるはずで予定の登録くらいはできるだろうと調べ始めましたが、ドキュメントが見分けられずかなり大変でした。
実際には予定の登録ができたのですが、未検証の部分も多いので個人用のメモとして残しておきます。


#Outlook予定表にイベントを登録するまでの課題(めんどかったこと)
大体一覧にすると、こんな感じです。
・Microsoftの関連ページがありすぎてどれを参考にしていいかわからない。
・認証方式やAPIの種類が豊富すぎて何を使えばいいかわからない。
・公式のページが多すぎてQiitaの記事や個人のBlogなどの情報が見つかりづらい
と、やる前から萎える感じの状態なのですが、AccessToken取得してPOSTするだけ(だと思う)なのになんでこんな分かりづらいんだと、たくさんの公式ドキュメントで関係ありそうなところを見まくりました。

#必要だったこと
前置きが長くなりましたが、ここからAPIを通じてイベントを予定表に追加するための準備です。

##Azure Active Directoryでアプリを登録する(Microsoft Azure Portalにログイン後) 。
ここでアプリの登録→新規登録を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223443/9817e552-c321-8780-9317-d279a758a0a2.png)
名前をつけて登録ボタンを押下します。ここではリダイレクトURIはいらないと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223443/310964fc-829b-b59c-27f5-77c4d389948a.png)



##アプリの登録画面に戻ると追加したアプリケーションが表示されているので選択する。
ここで重要なのはアプリケーション(クライアントID)とディレクトリ(テナント)です。後に使うのでコピーしておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223443/c57a6bbf-b9b4-dcf6-7070-97a0bcf23e92.png)


##証明書とシークレットを押下してクライアントシークレットを作成する。
新しいクライアントシークレットを押下して説明を入力し、有効期限を選択して追加を行う。
この時に生成されるクライアントシークレットの値も後に使うのでコピーしておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223443/04e4e02a-acc4-6a43-1668-9780a4f3a7c1.png)

##APIのアクセス許可を押下して今回必要なカレンダー(予定表)へのアクセスができるようにする。
ここでアクセス許可の追加を押下してCalendars.ReadWriteを追加する。この時Azure,Office365の管理者ではない場合、管理者にこのアクセス許可の内容について同意が必要になるので適宜申請なりして許可をとってください。
※他にもCalendars.ReadやMail.Send、User.Readを追加していますが今回はおそらくいりません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223443/32218a7e-0aa1-0269-2f64-3f3d9e3f875a.png)
アクセス許可の追加押下後、Microsoft Graphを選択する。その後、アクセス許可の種類を選択する画面がでますが、今回はサインインするユーザーなしで多人数のカレンダーに予定を追加するので、アプリケーションの許可を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223443/a5abf6c8-8dfc-6069-fab2-dc930ab0ac7c.png)
その後、アクセス許可の種類を選択する画面がでますが、今回はサインインするユーザーなしで多人数のカレンダーに予定を追加するので、アプリケーションの許可を選択します。
ここで必要な権限Calendars.ReadWriteにチェックを入れアクセス許可の追加をします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223443/fa754955-a48a-4bba-5474-e6d5435cc01a.png)

##認証を押下してトークンの種類を選択する。
アクセストークン、IDトークンにチェックを入れます。また、パブリッククライアント フローを許可するではいを選択します。
※実際はアクセストークンのチェックだけでよいかもしれません。。またパブリッククライアントフローの許可も厳密に検証していません。すみません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223443/fcc54b7d-d9f2-a973-b802-9a568a644b58.png)

ここまできたらアプリ自体の登録は完了です！

#実際のコーディング
ここまでの準備でAccessTokenの取得や、イベントの追加をAPI経由でできるようになっているので、そこにアクセスするためのコーディングを行います。
逐次説明が大変なので、コメントでフォローしました。クライアントID、クライアントシークレット、テナントとDB情報は置き換えてください。
途中でDBに接続してSlackの名前から、メールアドレス(ログインID)とOffice365の表示名を取得しています。
イベント追加時のURLですが、公式ドキュメントでは「POST /users/{id | userPrincipalName}/events」となっており、userPrincipalNameというのがログインID(メールアドレス)であるため、それをイベントを追加する人ごとに分ける感じです。

```php:addEvent.php
/**
 * Outlookへの休日登録
 * @param  string  $text		確認する文字列
 * @param  string  $user		Slackユーザー名
 * @param  date	$date	   対象日付(Y-m-d)
 * @param  time	$hhdstart  対象日時(H:i:00)
 * @param  time	$hhdend	   対象日時(H:i:00)
 *
 * @return boolean
 */

function toOutlook($text,$user,$date,$hhdstart,$hhdend){
	global $wdsn,$wdbuser,$wpass;//PDOを使ったDBアクセスをしているので設定を適宜置き換えてください
	$CURLERR = NULL;

	$data = array(
		'client_id' => 'XXXXX-XXXXX-XXXXX-XXXXX-XXXXX',//クライアントID
		'scope' => 'https://graph.microsoft.com/.default',
		'client_secret' => 'XXXXXXXXXXXXXXXXXXXXXXXXX',//クライアントシークレット
		'grant_type' => 'client_credentials',
	);

	$url = 'https://login.microsoftonline.com/XXXXXXXXXXXXXXXXXXXXXXX/oauth2/v2.0/token';//XXXをテナントで置換する

	$ch = curl_init($url);

	curl_setopt($ch, CURLOPT_POST, TRUE);                            //POSTで送信
	curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));    //データをセット
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);                    //受け取ったデータを変数に
	curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
	$html = curl_exec($ch);

	if(curl_errno($ch)){        //curlでエラー発生
		$CURLERR .= 'curl_errno：' . curl_errno($ch) . "\n";
		$CURLERR .= 'curl_error：' . curl_error($ch) . "\n";
		$CURLERR .= '▼curl_getinfo' . "\n";
		foreach(curl_getinfo($ch) as $key => $val){
			$CURLERR .= '■' . $key . '：' . $val . "\n";
		}
		echo nl2br($CURLERR);
	}
	curl_close($ch);

	$res = json_decode($html,true);//レスポンスJsonにアクセスできるようにする。$res['access_token']がアクセストークン

	$name = $user;
	$loginId = '';
	$fullname = '';

	try {
		$upsert_pdo = new PDO(
			$wdsn,
			$wdbuser,
			$wpass,
			array(
				PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
				PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
				PDO::ATTR_EMULATE_PREPARES => false,
			)
		);

		$sql = 'SELECT mailaddress,ename FROM ユーザーテーブル名 WHERE nname = \''.$name.'\'';//Slackの登録名からmailaddressとOffice365の登録名を取得する
		$result = $upsert_pdo->query($sql);
		foreach ($result as $row) {

			$loginId = $row['mailaddress'];
			$fullname = $row['ename'];

			$CURLERR = NULL;

			$data = array(
				'subject' => "${text}",
				'body' => array('contentType' => 'HTML','content' => "${text}です。"),
				'start' => array('dateTime' => "${date}T${hhdstart}",'timeZone' => 'Asia/Tokyo'),
				'end' => array('dateTime' => "${date}T${hhdend}",'timeZone' => 'Asia/Tokyo'),
				'attendees' => array(array('emailAddress' => array('address' => $loginId,'name' => $fullname)),array('type' => 'required')),
			);
			$data = json_encode($data,JSON_UNESCAPED_UNICODE);
			$url = 'https://graph.microsoft.com/v1.0/users/'.$loginId.'/events';//登録するユーザーのメールアドレスがURLに必要になります。

			$ch = curl_init($url);

			curl_setopt($ch, CURLOPT_POST, TRUE);                            //POSTで送信
			curl_setopt($ch, CURLOPT_POSTFIELDS, $data);    //データをセット
			curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);                    //受け取ったデータを変数に
			curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json','Authorization: Bearer '.$res['access_token']));
			$html = curl_exec($ch);//ここでAPIを通じてイベントが登録されます。

			if(curl_errno($ch)){        //curlでエラー発生
				$CURLERR .= 'curl_errno：' . curl_errno($ch) . "\n";
				$CURLERR .= 'curl_error：' . curl_error($ch) . "\n";
				$CURLERR .= '▼curl_getinfo' . "\n";
				foreach(curl_getinfo($ch) as $key => $val){
					$CURLERR .= '■' . $key . '：' . $val . "\n";
				}
				error_log(nl2br($CURLERR),0);
			}
			curl_close($ch);
		
		}
	} catch (PDOException $e) {
		// 外側のTryブロックに対してスロー
		throw $e;
		return FALSE;
	}

}

```

#感想
最初はとてつもなく大きなものに立ち向かっていく感があったのですが、こうして書いてみると、わかっていればすぐなんでしょうね。。。もうちょっとドキュメントの読解力を磨きます。

#参考にしたサイト様
[Outlook カレンダー API の概要](https://docs.microsoft.com/ja-jp/graph/outlook-calendar-concept-overview)
[イベントを作成する](https://docs.microsoft.com/ja-jp/graph/api/user-post-events?view=graph-rest-1.0&tabs=http)
[dateTimeTimeZone リソースの種類](https://docs.microsoft.com/ja-jp/graph/api/resources/datetimetimezone?view=graph-rest-1.0#additional-time-zones)
[event リソースの種類](https://docs.microsoft.com/ja-jp/graph/api/resources/event?view=graph-rest-1.0)
[ユーザーなしでアクセスを取得](https://docs.microsoft.com/ja-jp/graph/auth-v2-service)
[Microsoft ID プラットフォーム エンドポイントでのアクセス許可と同意](https://docs.microsoft.com/ja-jp/azure/active-directory/develop/v2-permissions-and-consent)
[Microsoft ID プラットフォームのアプリケーションの種類](https://docs.microsoft.com/ja-jp/azure/active-directory/develop/v2-app-types?WT.mc_id=Portal-Microsoft_AAD_RegisteredApps)
[Microsoft Outlook API で遊ぶ](https://qiita.com/propella/items/8c9702d91dc9d767703b)
[PostMan を使った Graph API の始め方](https://qiita.com/shmiki/items/6e2fed52603fbd225e6c)
[AzureでAccessTokenを取得する方法](https://blog.kokoni.jp/entry/2018/10/08/azure%25e3%2581%25a7accesstoken%25e3%2582%2592%25e5%258f%2596%25e5%25be%2597%25e3%2581%2599%25e3%2582%258b%25e6%2596%25b9%25e6%25b3%2595/)