* Googleカレンダー連携 [#zaec426f]
#setlinebreak(on);

OAuth認証後に Google Calendar APIを使用してカレンダー情報を取得する簡単なWebアプリケーションを作成する。
ここではPHPで実装するが、他の言語でも基本的な流れは同じ。

エンドポイントに対して直接リクエストを発行する場合のインターフェースは以下のURLを参照。
https://developers.google.com/google-apps/calendar/v3/reference/

&br;

#contents

// サービスアカウント版は別途記載予定。

** 概要 [#a76b6172]
#html(<div style="padding-left:10px">)

以下の通り、カレンダーAPIからのデータ取得を行う簡単なアプリケーションを作成する。

#TODO(フォルダ構成、サンプル概要)

|URI|クライアントAPIの言語|h
|http://localhost/test/google-calendar/|PHP|
※Google認証後のリダイレクトページも同じURIを使用する

#html(</div>)

** プロジェクトの作成と認証情報の設定/取得 [#h9ad68bf]
#html(<div style="padding-left:10px">)

*** プロジェクトの登録 [#sfdc52cf]
#html(<div style="padding-left:10px">)

Google API Console から プロジェクトを作成する
//https://console.developers.google.com/?hl=ja
// https://console.developers.google.com/flows/enableapi?apiid=calendar&hl=ja
https://console.developers.google.com/project

#html(</div>)

*** APIの有効化 [#u005b2fe]
#html(<div style="padding-left:10px">)

(1) 対象のプロジェクトを選択して「APIとサービスの有効化」をクリック。
(2) 検索ボックスに ”Calendar” を入力。
(3) "Google Calendar API"を選択し、「有効にする」をクリック。

#html(</div>)

*** 認証情報の作成 [#kd46634e]
#html(<div style="padding-left:10px">)

(1)「認証情報の作成」から「OAuthクライアントID」をクリックして以下の通り入力して「作成」。

アプリケーションの種類: ウェブ アプリケーション
名前: カレンダー連携テストクライアント
承認済みのリダイレクト URI: http://localhost/test/google-calendar/

(2) ”認証情報” の OAuth同意画面を以下の通り入力し、「保存」

ユーザーに表示するサービス名: カレンダー連携テストサービス
※利用規約やプライバシーポリシーの URLを入力する場合はここで入力。

(3) "認証情報" から作成したOAuthクライアントを選択し、「JSONをダウンロード」 
※client_secret.json にリネーム。

#html(</div>)

#html(</div>)

** Google クライアントライブラリをインストールする [#tf7d968f]
#html(<div style="padding-left:10px">)

ここでは composer でインストールする

#code(myterm2 nolinenums){{
cd /path/to/www/
mkdir google-calendar && cd google-calendar
composer require google/apiclient:^2.0
}}
※GitHubからダウンロードする場合はダウンロード後、上記ディレクトリ(/path/to/www/google-calendar配下)に解凍
https://github.com/google/google-api-php-client/releases

*** 参考 [#l6288920]
#html(<div style="padding-left:10px">)
https://developers.google.com/api-client-library/php
https://developers.google.com/google-apps/calendar/quickstart/php?hl=ja
https://developers.google.com/api-client-library/php/start/installation?hl=ja
https://github.com/google/google-api-php-client#download-the-release
#html(</div>)

#html(</div>)

** 連携処理の作成 [#r31062d3]
#html(<div style="padding-left:10px">)

#TODO(登録/更新/削除)

/path/to/www/test/google-calendar/index.php
#code(mycode2){{
&lt;?php
date_default_timezone_set('Asia/Tokyo');

//error_reporting(E_ALL);
require_once 'vendor/autoload.php';

// OAuthクライアント認証用のJSONファイル
$oauth_credentials = "/path/to/client_secret.json";  // 上記でダウンロードしたJSONファイルのPATH

// Google認証後のリダイレクト先(「http://localhost/test/google-calendar/?code=アクセストークン」 という形でリダイレクトされる)
$redirect_uri = "http://localhost/test/google-calendar/";

// トークンの退避用 TODO: テスト用(本番時はDB等に退避する)
$token_file = "/tmp/google-calendar-api-token-cache.txt";

session_start();

// セッションを破棄する(テスト用)
// ※退避しておいたトークンを使用して認証が通る事の確認用
if (isset($_REQUEST['logout'])) {
  session_destroy();
  session_start();
  header("Location: " . $redirect_uri);
  exit;
}

// トークンを破棄する(テスト用)
// ※アカウントの再選択を行いたい場合
if (isset($_REQUEST['remove-token'])) {
  session_destroy();
  session_start();
  file_put_contents($token_file, "");
  header("Location: " . $redirect_uri);
  exit;
}

$msg = "";

// 取得済みのトークンがある場合はセッションにセット TODO: 本番時はDBから取得
if (empty($_SESSION['google-calendar-api-token'])) {
  if (file_exists($token_file)) {
    $tokenText = file_get_contents($token_file);
    if (trim($tokenText) !== "") {
        $msg = "トークンをファイルから取得しました。";
        $_SESSION['google-calendar-api-token'] = unserialize(file_get_contents($token_file));
    }   
  }
}

// Google API Client
$client = new Google_Client();
$client->setAuthConfig($oauth_credentials);
$client->setRedirectUri($redirect_uri);
$client->addScope(Google_Service_Calendar::CALENDAR);
$client->setAccessType("offline");   // トークンの自動リフレッシュ
$client->setApprovalPrompt("force"); // これがないと初回以外はリフレッシュトークンが得られない
$authUrl = $client->createAuthUrl();

// 認証後のリダイレクトの場合
if (isset($_GET['code'])) {
  $token = $client->fetchAccessTokenWithAuthCode($_GET['code']);
  $_SESSION['google-calendar-api-token'] = $token;
  header("Location: ${redirect_uri}");
  exit;
}

// トークンの有効期限が切れている場合はリフレッシュトークンを使用して最新のトークンを得る
if (!empty($_SESSION['google-calendar-api-token'])) {
  $client->setAccessToken($_SESSION['google-calendar-api-token']);
  If ($client->isAccessTokenExpired()) {
    if (isset($_SESSION['google-calendar-api-token']["refresh_token"])) {
      $client->refreshToken($_SESSION['google-calendar-api-token']["refresh_token"]);
    } else {
      unset($_SESSION['google-calendar-api-token']);
    }
  }
}

// トークンが無効な場合は認証ページにリダイレクト
$accessToken = $client->getAccessToken();
if (!$accessToken) {
  header("Location: ${authUrl}");
  exit;
}

$_SESSION['google-calendar-api-token'] = $accessToken;

// トークンを退避  TODO: 本番時はDBなどに退避する
$tokenText = serialize($_SESSION['google-calendar-api-token']);
file_put_contents($token_file, $tokenText);

// カレンダーAPI用のインスタンス生成
$cal_service  = new Google_Service_Calendar($client);

// カレンダ一覧を取得 
$calendar_list = array();
$calendarList = $cal_service->calendarList->listCalendarList();
while(true) {
  foreach ($calendarList->getItems() as $i => $calendarListEntry) {
    $rec = $calendarListEntry;
    $calendar_list[] = $rec;
  }
  $pageToken = $calendarList->getNextPageToken();
  if ($pageToken) {
    $optParams = array('pageToken' => $pageToken);
    $calendarList = $service->calendarList->listCalendarList($optParams);
  } else {
    break;
  }
}

// イベント情報
$calender_idx = 0;
$calender_id = "primary";
if (isset($_REQUEST["calender_idx"])){
  if (isset($calendar_list[$_REQUEST["calender_idx"]])){
    $calender_idx = $_REQUEST["calender_idx"];
    $calender_id = $calendar_list[$calender_idx]["id"];
  }
}

$event_list = array();

$optParams = array();
/* 日付指定する場合 */
$optParams["timeMin"]  = "2017-01-01T00:00:00+0900";  // "2017-01-01T00:00:00Z";
$optParams["timeMax"]  = "2017-12-31T23:59:59+0900";
$optParams["timeZone"] = "Asia/Tokyo";
$optParams["singleEvents"] = true;
$optParams["orderBy"]  = "startTime";     // orderBy指定する場合は singleEvents=true でないと怒られる

$events = $cal_service->events->listEvents($calender_id, $optParams);
while(true) {
  foreach ($events->getItems() as $event) {
    $rec = array();
    $rec["id"]      = $event->getId();
    $rec["start"]   = $event->getStart()->date ? $event->getStart()->date : $event->getStart()->dateTime;
    $rec["end"]     = $event->getEnd()->date ? $event->getEnd()->date : $event->getEnd()->dateTime;
    $rec["summary"] = $event->getSummary();
    $event_list[] = $rec;
  }
  $pageToken = $events->getNextPageToken();
  if ($pageToken) {
    $optParams['pageToken'] = $pageToken;
    $events = $cal_service->events->listEvents($calender_id, $optParams);
  } else {
    break;
  }
}

// 描画
require_once("view/calendar.php");
?&gt;
}}
#html(</div>)

** Viewの作成 [#r31062d3]
#html(<div style="padding-left:10px">)

/path/to/www/test/google-calendar/view/calendar.php
#code(myhtml2){{
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<style>
* {
    font-size: 16px;
}
h1,h2,h3,h4,h5 {
    margin: 0;
}
.tbl {
    border-collapse: collapse;
    border-spacing: 0;
}
.tbl th {
    background: #ccc;
}
.tbl th, 
.tbl td{ 
    padding: 1px 10px;
    border: 1px solid #333;
}
html,body {
    height: 100%;
}
#loading_layer {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0,0,0,0.7);
}
#loading_inner {
    position: fixed;
    top: 50%;
    left: 50%;
    width: 200px;
    height: 50px;
    margin-left: -100px;
    margin-top: -25px;
    background: #fff;
    border-radius: 4px;
    overflow-y: hidden;
}
#loading_msg {
    padding-top: 10px;
    padding-bottom: 10px;
    text-align: center;
    vertical-align: middle;
}
</style>
</head>
<body>

<div>&lt;?= $msg ?&gt;</div>

<div style="position:relative;">
    <div style="position:absolute;top:0;right:10px;">
        <a href="./?logout=true">セッション破棄</a>
        <a href="./?remove-token=true" style="margin-left:10px;">トークン破棄</a>
    </div>
    <div style="display:inline-block">
        カレンダー :
        <select id="calendar_id">
        <?php foreach ($calendar_list as $idx => $rec) {?>
        <option value="<?= $idx ?>" <?= $calender_idx == $idx ? "selected" : "" ?>><?= $rec["summary"] ?></option>
        <?php } ?>
        </select>
    </div>
</div>

<table class="tbl" style="margin-top:4px">
<thead>
    <tr>
        <th>Id</th>
        <th>Start</th>
        <th>End</th>
        <th>Summary</th>
    </tr>
</thead>
<tbody>
<?php foreach ($event_list as $rec) { ?>
    <tr>
        <td><?= $rec["id"] ?></td>
        <td><?= $rec["start"] ?></td>
        <td><?= $rec["end"] ?></td>
        <td><?= $rec["summary"] ?></td>
    <tr>
<?php } ?>
</tbody>
</table>

<div id="loading_layer" style="display:none">
    <div id="loading_inner"><div id="loading_msg">Now Loading...</div></div>
</div>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script>
jQuery(function($){
    $("#calendar_id").on("change", function(){
        $("#loading_layer").show();
        location.href = "./?calender_idx=" + $(this).val();
    });
});
</script>

</body>
</html>
}}
#html(</div>)


*** APIリファレンス [#me3105f5]
#html(<div style="padding-left:10px;">)

以下はAPIのエンドポイントを直叩きする場合用の記載になっているが、クライアントAPIを利用する場合でもオプション引数などはそのまま使える。
https://developers.google.com/google-apps/calendar/v3/reference/
#html(</div>)

*** サンプルソース [#t8c0d4d0]
#html(<div style="padding-left:10px;">)
https://github.com/google/google-api-php-client
https://github.com/google/google-api-php-client/tree/master/examples
#html(</div>)


トップ   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS