В этой статье вы узнаете, как работать с API Календаря Google в PHP. Вы сделаете это, создав приложение календаря, которое позволит пользователям добавлять новые календари, добавлять события и синхронизировать календари в Календарь Google. Если вы хотите следовать, я рекомендую вам настроить Homestead, чтобы вы могли легко получить среду для запуска приложения.
Настройка проекта консоли Google
Первое, что вам нужно сделать, это создать новый проект в консоли разработчиков Google .
Как только проект будет создан, нажмите на ссылку включить и управлять API на панели инструментов.
На странице API Google выберите и включите Календарь API и Google+ API.
После включения вернитесь на страницу API Google и нажмите ссылку Credentials , затем кнопку add credentials и выберите идентификатор клиента OAuth 2.0 .
Это попросит вас настроить экран согласия. Нажмите на экран настройки согласия, чтобы сделать это.
Выберите предпочитаемый адрес электронной почты, добавьте название продукта, затем нажмите « Сохранить» .
Создать веб-приложение .
Это даст вам идентификатор клиента и секрет клиента
Сборка приложения
Мы будем использовать Laravel через Composer .
Установка зависимостей
composer create-project --prefer-dist laravel/laravel kalendaryo
Это создаст новую папку с именем kalendaryo
которая будет служить каталогом вашего проекта.
Давайте установим некоторые другие зависимости:
composer require nesbot/carbon google/apiclient
Вы будете использовать клиент Google для общения с Google+ API для входа в систему и Google Calendar API для работы с Календарем Google.
Настройка приложения
Откройте файл .env
в корне каталога проекта и .env
недостающие детали, например:
APP_ENV=local APP_DEBUG=true APP_KEY=base64:iZ9uWJVHemk5wa8disC8JZ8YRVWeGNyDiUygtmHGXp0= APP_URL=http://localhost DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=kalendaryo DB_USERNAME=root DB_PASSWORD=secret SESSION_DRIVER=file APP_TITLE=Kalendaryo APP_TIMEZONE="Asia/Manila" GOOGLE_CLIENT_ID="YOUR GOOGLE CLIENT ID" GOOGLE_CLIENT_SECRET="YOUR GOOGLE CLIENT SECRET" GOOGLE_REDIRECT_URL="http://kalendaryo.dev/login" GOOGLE_SCOPES="email,profile,https://www.googleapis.com/auth/calendar" GOOGLE_APPROVAL_PROMPT="force" GOOGLE_ACCESS_TYPE="offline"
Необходимо добавить следующие значения конфигурации: DB_DATABASE
, DB_USERNAME
, DB_PASSWORD
, APP_TIMEZONE
, GOOGLE_CLIENT_ID
, GOOGLE_CLIENT_SECRET
и GOOGLE_REDIRECT_URL
.
Для APP_TIMEZONE
вы можете использовать любое значение со страницы часовых APP_TIMEZONE
PHP .
Для получения подробной информации о базе данных создайте новую базу данных MySQL и используйте имя базы данных в качестве значения для DB_DATABASE
. DB_USERNAME
и DB_PASSWORD
— учетные данные для входа в эту базу данных. Если на Homestead Improved , вы можете просто использовать предварительно созданную homestead
БД с учетными данными homestead
/ secret
.
Для конфигурации Google замените значение для GOOGLE_CLIENT_ID
, GOOGLE_CLIENT_SECRET
и GOOGLE_REDIRECT_URL
учетными данными, которые вы получили из консоли Google ранее. GOOGLE_SCOPES
— это разрешения, которые GOOGLE_SCOPES
вашему приложению. То, что вы там указали, появится на экране согласия. Пользователь должен согласиться, чтобы приложение имело доступ к конкретным данным, которые вы запрашиваете.
Если вам интересно, где я их взял, вы можете проверить oauthplayground и выбрать API Google+ и API Календаря Google. URL-адреса, которые отображаются в раскрывающемся списке, являются в основном разрешениями. email
и profile
— это просто сокращение для https://www.googleapis.com/auth/userinfo.email
и https://www.googleapis.com/auth/userinfo.profile
. Каждое разрешение разделяется запятой, потому что вы будете преобразовывать его в массив позже.
Создание сервисного контейнера для клиента Google
Далее взгляните на сервисный контейнер для клиента Google. Создайте файл app/Googl.php
и добавьте следующее:
<?php namespace App; class Googl { public function client() { $client = new \Google_Client(); $client->setClientId(env('GOOGLE_CLIENT_ID')); $client->setClientSecret(env('GOOGLE_CLIENT_SECRET')); $client->setRedirectUri(env('GOOGLE_REDIRECT_URL')); $client->setScopes(explode(',', env('GOOGLE_SCOPES'))); $client->setApprovalPrompt(env('GOOGLE_APPROVAL_PROMPT')); $client->setAccessType(env('GOOGLE_ACCESS_TYPE')); return $client; } }
В приведенном выше коде вы задаете идентификатор клиента, секрет клиента, URL-адрес перенаправления, области (разрешения), запрос на утверждение и тип доступа. Вы загружаете все эти значения из файла .env
который вы создали ранее. Как только все будет установлено, верните новый экземпляр. Позже вы просто client
метод client
для инициализации клиента Google.
Маршруты
Откройте файл app/Http/routes.php
затем добавьте маршруты для разных страниц в приложении:
Route::group( ['middleware' => ['admin']], function(){ Route::get('/dashboard', 'AdminController@index'); Route::get('/calendar/create', 'AdminController@createCalendar'); Route::post('/calendar/create', 'AdminController@doCreateCalendar'); Route::get('/event/create', 'AdminController@createEvent'); Route::post('/event/create', 'AdminController@doCreateEvent'); Route::get('/calendar/sync', 'AdminController@syncCalendar'); Route::post('/calendar/sync', 'AdminController@doSyncCalendar'); Route::get('/events', 'AdminController@listEvents'); Route::get('/logout', 'AdminController@logout'); }); Route::get('/', 'HomeController@index'); Route::get('/login', 'HomeController@login');
В приведенном выше коде вы используете пользовательское промежуточное ПО под названием admin
. Вы будете создавать это в ближайшее время. Использование промежуточного программного обеспечения в группе маршрутов означает, что оно будет активировано для каждого маршрута в группе.
Route::group( ['middleware' => ['admin']], function(){ ... } );
Внутри функции обратного вызова у вас есть различные маршруты, которые принадлежат группе. Они все говорят сами за себя. Маршрут, отвечающий на запросы POST
, используется для выполнения операций записи, а маршруты GET доступны только для чтения.
Route::get('/dashboard', 'AdminController@index'); Route::get('/calendar/create', 'AdminController@createCalendar'); Route::post('/calendar/create', 'AdminController@doCreateCalendar'); Route::get('/event/create', 'AdminController@createEvent'); Route::post('/event/create', 'AdminController@doCreateEvent'); Route::get('/calendar/sync', 'AdminController@syncCalendar'); Route::post('/calendar/sync', 'AdminController@doSyncCalendar'); Route::get('/events', 'AdminController@listEvents'); Route::get('/logout', 'AdminController@logout');
Промежуточное программное обеспечение Admin Route
Как упоминалось ранее, промежуточное программное обеспечение маршрута используется для выполнения кода при доступе к определенному маршруту. В случае промежуточного программного обеспечения admin
оно используется для проверки того, вошел ли пользователь в данный момент.
Создайте промежуточное ПО администратора в app/Http/Middleware/AdminMiddleware.php
и добавьте следующее:
<?php namespace App\Http\Middleware; use Closure; class AdminMiddleware { public function handle($request, Closure $next) { if ($request->session()->has('user')) { return $next($request); } return redirect('/') ->with( 'message', ['type' => 'danger', 'text' => 'You need to login'] ); } }
У большинства промежуточных программ маршрутизации есть одна общая черта: у них есть метод handle
используемый для обработки запроса. Это выполняется перед любым методом ответа на маршрут. Внутри метода вы проверяете, установлен ли user
в текущем сеансе. Если это так, перейдите к обработке запроса.
if ($request->session()->has('user')) { return $next($request); }
В противном случае перенаправьте пользователя на домашнюю страницу и передайте в сеанс сообщение о том, что ему необходимо войти в систему.
return redirect('/') ->with( 'message', ['type' => 'danger', 'text' => 'You need to login'] );
Сделайте промежуточное программное обеспечение доступным для использования, добавив его в файл app/Http/Kernel.php
в массиве $routeMiddleware
:
'admin' => \App\Http\Middleware\AdminMiddleware::class
База данных
Приложение использует три таблицы: пользователь , календарь и событие . Чтобы сэкономить время, вы можете просто перенести миграции из репозитория и вставить их в тот же путь в своем собственном проекте. Выполните php artisan migrate
в корне вашего проекта, чтобы фактически создать таблицы в базе данных.
После этого скопируйте содержимое User.php
, Calendar.php
и Event.php
в каталог app
. Эти файлы являются моделями, которые вы будете использовать позже для общения с базой данных.
Домашние страницы
Создайте файл app/Http/Controllers/HomeController.php
и добавьте следующее:
<?php namespace App\Http\Controllers; use App\Googl; use App\User; use App\Calendar; use Illuminate\Http\Request; class HomeController extends Controller { public function index() { return view('login'); } public function login(Googl $googl, User $user, Request $request) { $client = $googl->client(); if ($request->has('code')) { $client->authenticate($request->get('code')); $token = $client->getAccessToken(); $plus = new \Google_Service_Plus($client); $google_user = $plus->people->get('me'); $id = $google_user['id']; $email = $google_user['emails'][0]['value']; $first_name = $google_user['name']['givenName']; $last_name = $google_user['name']['familyName']; $has_user = $user->where('email', '=', $email)->first(); if (!$has_user) { //not yet registered $user->email = $email; $user->first_name = $first_name; $user->last_name = $last_name; $user->token = json_encode($token); $user->save(); $user_id = $user->id; //create primary calendar $calendar = new Calendar; $calendar->user_id = $user_id; $calendar->title = 'Primary Calendar'; $calendar->calendar_id = 'primary'; $calendar->sync_token = ''; $calendar->save(); } else { $user_id = $has_user->id; } session([ 'user' => [ 'id' => $user_id, 'email' => $email, 'first_name' => $first_name, 'last_name' => $last_name, 'token' => $token ] ]); return redirect('/dashboard') ->with('message', ['type' => 'success', 'text' => 'You are now logged in.']); } else { $auth_url = $client->createAuthUrl(); return redirect($auth_url); } } }
Разбивая код выше, у вас есть метод index
который возвращает представление входа в систему.
public function index() { return view('login'); }
Вы можете создать вид входа в систему в файле resources/views/login.blade.php
:
@extends('layouts.default') @section('content') <form method="GET" action="/login"> <button>Login with Google</button> </form> @stop
Представление входа в систему представляет собой форму с кнопкой входа в систему, которая используется для входа в Google. Это представление расширяет шаблон по умолчанию ( resources/views/layouts/default.blade.php
), который содержит следующее:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{{ env('APP_TITLE') }}</title> <link rel="stylesheet" href="{{ url('assets/lib/picnic/picnic.min.css') }}"> <link rel="stylesheet" href="{{ url('assets/lib/picnic/plugins.min.css') }}"> <link rel="stylesheet" href="{{ url('assets/css/style.css') }}"> </head> <body> <div class="container"> <header> <h1>{{ env('APP_TITLE') }}</h1> </header> @include('partials.alert') @yield('content') </div> </body> </html>
В приведенном выше шаблоне CSS для пикника используется для украшения вещей. Таблицы стилей хранятся в каталоге public/assets
. Установите его вручную или с помощью чего-нибудь хорошего, например, BowerPHP
Часть, которая имеет @yield('content')
— это место, где отображается форма входа.
Вы также включаете partials.alert
, который вы можете определить в файле resources/views/partials/alert.blade.php
. Это используется для отображения сообщений и формирования ошибок проверки пользователю:
@if(session('message')) <div class="alert alert-{{ session('message.type') }}"> {{ session('message.text') }} </div> @endif @if($errors->any()) <div class="alert alert-danger"> <ul> @foreach($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif
Возвращаясь к HomeController
, следующий метод login
в login
. Подсказка типа используется для того, чтобы вы могли использовать сервисный контейнер для клиента Google. То же самое делается с моделью User
.
public function login (Googl $googl, User $user) { ... }
Внутри метода login
в login
создайте новый экземпляр клиента Google, а затем проверьте, передается ли код в качестве параметра запроса на URL-адрес.
Если вы ранее не использовали OAuth ни в одном из своих проектов, то он работает так, что пользователь должен щелкнуть ссылку, ведущую на страницу входа в службу. В данном случае это Google, поэтому пользователь перенаправляется на страницу входа Google. После входа в систему у пользователя запрашиваются определенные разрешения. Как только пользователь соглашается, они перенаправляются обратно на сайт с уникальным кодом в URL. Затем он используется для получения токена доступа, который может использоваться приложением для выполнения запросов к API.
Код, который вы проверяете здесь, является кодом, используемым для обмена на токен доступа. Если он не существует, перенаправьте пользователя на страницу входа в Google:
$client = $googl->client(); if ($request->has('code')) { ... } else { $auth_url = $client->createAuthUrl(); return redirect($auth_url); }
Если он существует, аутентифицируйте пользователя с этим кодом:
$client->authenticate($request->get('code'));
Это позволяет получить токен доступа и инициализировать запрос к сервису Google+:
$token = $client->getAccessToken(); $plus = new \Google_Service_Plus($client);
Затем, получите текущего зарегистрированного пользователя и извлеките его информацию:
$google_user = $plus->people->get('me'); $id = $google_user['id']; $email = $google_user['emails'][0]['value']; //email $first_name = $google_user['name']['givenName']; //first name $last_name = $google_user['name']['familyName']; //last name
Проверьте, существует ли пользователь в базе данных:
$has_user = $user->where('email', '=', $email)->first();
Если пользователь не существует, создайте нового пользователя и сохраните запись в базе данных. Также создайте календарь по умолчанию для пользователя. Это основной календарь в календаре Google. Вы в основном делаете копию, чтобы у событий (встреч) был контейнер, как только вы начнете синхронизировать данные из службы календаря Google.
if (!$has_user) { //not yet registered $user->email = $email; $user->first_name = $first_name; $user->last_name = $last_name; $user->token = $token; $user->save(); $user_id = $user->id; //create primary calendar $calendar = new Calendar; $calendar->user_id = $user_id; $calendar->title = 'Primary Calendar'; $calendar->calendar_id = 'primary'; $calendar->sync_token = ''; $calendar->save(); }
Если пользователь уже существует, присвойте значение $user_id
чтобы вы могли установить его в сеансе:
$user_id = $has_user->id;
Сохраните данные пользователя в сеансе:
session([ 'user' => [ 'id' => $user_id, 'email' => $email, 'first_name' => $first_name, 'last_name' => $last_name, 'token' => $token ] ]);
Наконец, перенаправьте пользователя на страницу панели администратора:
return redirect('/dashboard') ->with('message', ['type' => 'success', 'text' => 'You are now logged in.']);
Страницы администратора
Затем создайте контроллер для страниц администратора в файле app/Http/Controllers/AdminController.php
:
<?php namespace App\Http\Controllers; use App\Googl; use App\User; use App\Calendar; use App\Event; use Carbon\Carbon; use Illuminate\Http\Request; class AdminController extends Controller { private $client; public function __construct(Googl $googl) { $this->client = $googl->client(); $this->client->setAccessToken(session('user.token')); } public function index(Request $request) { return view('admin.dashboard'); } public function createCalendar(Request $request) { return view('admin.create_calendar'); } public function doCreateCalendar(Request $request, Calendar $calendar) { $this->validate($request, [ 'title' => 'required|min:4' ]); $title = $request->input('title'); $timezone = env('APP_TIMEZONE'); $cal = new \Google_Service_Calendar($this->client); $google_calendar = new \Google_Service_Calendar_Calendar($this->client); $google_calendar->setSummary($title); $google_calendar->setTimeZone($timezone); $created_calendar = $cal->calendars->insert($google_calendar); $calendar_id = $created_calendar->getId(); $calendar->user_id = session('user.id'); $calendar->title = $title; $calendar->calendar_id = $calendar_id; $calendar->save(); return redirect('/calendar/create') ->with('message', [ 'type' => 'success', 'text' => 'Calendar was created!' ]); } public function createEvent(Calendar $calendar, Request $request) { $user_id = session('user.id'); $calendars = $calendar ->where('user_id', '=', $user_id)->get(); $page_data = [ 'calendars' => $calendars ]; return view('admin.create_event', $page_data); } public function doCreateEvent(Event $evt, Request $request) { $this->validate($request, [ 'title' => 'required', 'calendar_id' => 'required', 'datetime_start' => 'required|date', 'datetime_end' => 'required|date' ]); $title = $request->input('title'); $calendar_id = $request->input('calendar_id'); $start = $request->input('datetime_start'); $end = $request->input('datetime_end'); $start_datetime = Carbon::createFromFormat('Y/m/d H:i', $start); $end_datetime = Carbon::createFromFormat('Y/m/d H:i', $end); $cal = new \Google_Service_Calendar($this->client); $event = new \Google_Service_Calendar_Event(); $event->setSummary($title); $start = new \Google_Service_Calendar_EventDateTime(); $start->setDateTime($start_datetime->toAtomString()); $event->setStart($start); $end = new \Google_Service_Calendar_EventDateTime(); $end->setDateTime($end_datetime->toAtomString()); $event->setEnd($end); //attendee if ($request->has('attendee_name')) { $attendees = []; $attendee_names = $request->input('attendee_name'); $attendee_emails = $request->input('attendee_email'); foreach ($attendee_names as $index => $attendee_name) { $attendee_email = $attendee_emails[$index]; if (!empty($attendee_name) && !empty($attendee_email)) { $attendee = new \Google_Service_Calendar_EventAttendee(); $attendee->setEmail($attendee_email); $attendee->setDisplayName($attendee_name); $attendees[] = $attendee; } } $event->attendees = $attendees; } $created_event = $cal->events->insert($calendar_id, $event); $evt->title = $title; $evt->calendar_id = $calendar_id; $evt->event_id = $created_event->id; $evt->datetime_start = $start_datetime->toDateTimeString(); $evt->datetime_end = $end_datetime->toDateTimeString(); $evt->save(); return redirect('/event/create') ->with('message', [ 'type' => 'success', 'text' => 'Event was created!' ]); } public function syncCalendar(Calendar $calendar) { $user_id = session('user.id'); $calendars = $calendar->where('user_id', '=', $user_id) ->get(); $page_data = [ 'calendars' => $calendars ]; return view('admin.sync_calendar', $page_data); } public function doSyncCalendar(Request $request) { $this->validate($request, [ 'calendar_id' => 'required' ]); $user_id = session('user.id'); $calendar_id = $request->input('calendar_id'); $base_timezone = env('APP_TIMEZONE'); $calendar = Calendar::find($calendar_id); $sync_token = $calendar->sync_token; $g_calendar_id = $calendar->calendar_id; $g_cal = new \Google_Service_Calendar($this->client); $g_calendar = $g_cal->calendars->get($g_calendar_id); $calendar_timezone = $g_calendar->getTimeZone(); $events = Event::where('id', '=', $calendar_id) ->lists('event_id') ->toArray(); $params = [ 'showDeleted' => true, 'timeMin' => Carbon::now() ->setTimezone($calendar_timezone) ->toAtomString() ]; if (!empty($sync_token)) { $params = [ 'syncToken' => $sync_token ]; } $googlecalendar_events = $g_cal->events->listEvents($g_calendar_id, $params); while (true) { foreach ($googlecalendar_events->getItems() as $g_event) { $g_event_id = $g_event->id; $g_event_title = $g_event->getSummary(); $g_status = $g_event->status; if ($g_status != 'cancelled') { $g_datetime_start = Carbon::parse($g_event->getStart()->getDateTime()) ->tz($calendar_timezone) ->setTimezone($base_timezone) ->format('Ymd H:i:s'); $g_datetime_end = Carbon::parse($g_event->getEnd()->getDateTime()) ->tz($calendar_timezone) ->setTimezone($base_timezone) ->format('Ymd H:i:s'); //check if event id is already in the events table if (in_array($g_event_id, $events)) { //update event $event = Event::where('event_id', '=', $g_event_id)->first(); $event->title = $g_event_title; $event->calendar_id = $g_calendar_id; $event->event_id = $g_event_id; $event->datetime_start = $g_datetime_start; $event->datetime_end = $g_datetime_end; $event->save(); } else { //add event $event = new Event; $event->title = $g_event_title; $event->calendar_id = $g_calendar_id; $event->event_id = $g_event_id; $event->datetime_start = $g_datetime_start; $event->datetime_end = $g_datetime_end; $event->save(); } } else { //delete event if (in_array($g_event_id, $events)) { Event::where('event_id', '=', $g_event_id)->delete(); } } } $page_token = $googlecalendar_events->getNextPageToken(); if ($page_token) { $params['pageToken'] = $page_token; $googlecalendar_events = $g_cal->events->listEvents('primary', $params); } else { $next_synctoken = str_replace('=ok', '', $googlecalendar_events->getNextSyncToken()); //update next sync token $calendar = Calendar::find($calendar_id); $calendar->sync_token = $next_synctoken; $calendar->save(); break; } } return redirect('/calendar/sync') ->with('message', [ 'type' => 'success', 'text' => 'Calendar was synced.' ]); } public function listEvents() { $user_id = session('user.id'); $calendar_ids = Calendar::where('user_id', '=', $user_id) ->lists('calendar_id') ->toArray(); $events = Event::whereIn('calendar_id', $calendar_ids)->get(); $page_data = [ 'events' => $events ]; return view('admin.events', $page_data); } public function logout(Request $request) { $request->session()->flush(); return redirect('/') ->with('message', ['type' => 'success', 'text' => 'You are now logged out']); } }
Давайте разберем приведенный выше код.
Внутри контроллера переменная $client
хранит ссылку на новый клиент Google, который вы инициализировали в конструкторе. Таким образом, вам не нужно инициализировать его каждый раз, когда вам это нужно.
private $client; public function __construct(Googl $googl) { $this->client = $googl->client(); $this->client->setAccessToken(session('user.token')); }
Метод index
возвращает представление панели администратора ( resources/views/admin/dashboard.blade.php
).
public function index(){ return view('admin.dashboard'); }
Панель администратора содержит ссылки на разные страницы, которые позволяют пользователю выполнять различные операции:
@extends('layouts.admin') @section('content') <h3>What do you like to do?</h3> <ul> <li><a href="/calendar/create">Create Calendar</a></li> <li><a href="/event/create">Create Event</a></li> <li><a href="/calendar/sync">Sync Calendar</a></li> <li><a href="/events">Events</a></li> <li><a href="/logout">Logout</a></li> </ul> @stop
Это представление расширяет следующий макет администратора ( resources/views/layouts/admin.blade.php
):
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{{ env('APP_TITLE') }}</title> <link rel="stylesheet" href="{{ url('assets/lib/picnic/picnic.min.css') }}"> <link rel="stylesheet" href="{{ url('assets/lib/picnic/plugins.min.css') }}"> @yield('jquery_datetimepicker_style') <link rel="stylesheet" href="{{ url('assets/css/style.css') }}"> </head> <body> <div class="container"> <header> <h1>{{ env('APP_TITLE') }}</h1> </header> @include('partials.alert') @yield('content') </div> @yield('attendee_template') @yield('jquery_script') @yield('handlebars_script') @yield('jquery_datetimepicker_script') @yield('create_event_script') </body> </html>
В приведенном выше коде вы получаете несколько шаблонов помимо основного контента. Эти шаблоны передаются из разных представлений. Он просто ничего не возвращает, если представление не определило секцию, которую он дает.
Создание календаря
Возвращаясь к AdminController
, вот метод, который возвращает представление для создания нового календаря:
public function createCalendar(Request $request) { return view('admin.create_calendar'); }
Представление создания календаря содержит форму, которая запрашивает название календаря. Есть также скрытое поле, используемое для предотвращения CSRF (Подделка межсайтовых запросов).
@extends('layouts.admin') @section('content') <form method="POST"> <input type="hidden" name="_token" value="{{{ csrf_token() }}}" /> <p> <label for="title">Title</label> <input type="text" name="title" id="title" value="{{ old('title') }}"> </p> <button>Create Calendar</button> </form> @stop
После doCreateCalendar
метод doCreateCalendar
. Внутри метода проверьте, есть ли значение, указанное в поле заголовка, и если оно имеет минимум 4 символа.
$this->validate($request, [ 'title' => 'required|min:4' ]);
Инициализируйте клиент календаря Google:
$cal = new \Google_Service_Calendar($this->client);
Создать новый календарь. Установите заголовок на заголовок, предоставленный пользователем, а часовой пояс — на часовой пояс приложения по умолчанию:
$google_calendar = new \Google_Service_Calendar_Calendar($this->client); $google_calendar->setSummary($title); $google_calendar->setTimeZone($timezone); $created_calendar = $cal->calendars->insert($google_calendar);
Получить идентификатор календаря:
$calendar_id = $created_calendar->getId();
Сохраните вновь созданный календарь в базу данных:
$calendar->user_id = session('user.id'); $calendar->title = $title; $calendar->calendar_id = $calendar_id; $calendar->save();
Перенаправьте пользователя обратно на страницу для создания нового календаря:
return redirect('/calendar/create') ->with('message', [ 'type' => 'success', 'text' => 'Calendar was created!' ]);
Создание события
Далее следует метод, который возвращает представление для создания нового события. Здесь вы передаете календари, которые пользователь уже создал, потому что пользователь должен выбрать, в какой календарь добавить событие.
public function createEvent(Calendar $calendar, Request $request) { $user_id = session('user.id'); $calendars = $calendar ->where('user_id', '=', $user_id)->get(); $page_data = [ 'calendars' => $calendars ]; return view('admin.create_event', $page_data); }
Представление admin.create_event
( resources/views/admin/create_event.blade.php
) включает в себя jQuery , рули, а также стили и сценарии jQuery datetimepicker .
Вы используете jQuery для прослушивания событий в DOM, рули для создания нового HTML и jQuery datetimepicker для превращения полей даты в указатели даты и времени. Внутри формы у вас есть поле для ввода заголовка события, выбора календаря для добавления события, даты и времени начала и окончания, а также полей для ввода имени и адреса электронной почты участника. Ниже формы у вас есть шаблон руля для строки участника. Эта строка будет добавлена к контейнеру участников ( <div id="attendees">...</div>
).
@extends('layouts.admin') @section('jquery_datetimepicker_style') <link rel="stylesheet" href="{{ url('assets/lib/jquery-datetimepicker/jquery.datetimepicker.min.css') }}"> @stop @section('content') <form method="POST"> <input type="hidden" name="_token" value="{{{ csrf_token() }}}" /> <p> <label for="title">Title</label> <input type="text" name="title" id="title" value="{{ old('title') }}"> </p> <p> <label for="calendar_id">Calendar</label> <select name="calendar_id" id="calendar_id"> @foreach($calendars as $cal) <option value="{{ $cal->calendar_id }}">{{ $cal->title }}</option> @endforeach </select> </p> <p> <label for="datetime_start">Datetime Start</label> <input type="text" name="datetime_start" id="datetime_start" class="datetimepicker" value="{{ old('datetime_start') }}"> </p> <p> <label for="datetime_end">Datetime End</label> <input type="text" name="datetime_end" id="datetime_end" class="datetimepicker" value="{{ old('datetime_end') }}"> </p> <div id="attendees"> Attendees <div class="attendee-row"> <input type="text" name="attendee_name[]" class="half-input name" placeholder="Name"> <input type="text" name="attendee_email[]" class="half-input email" placeholder="Email"> </div> </div> <button>Create Event</button> </form> @stop @section('attendee_template') <script id="attendee-template" type="text/x-handlebars-template"> <div class="attendee-row"> <input type="text" name="attendee_name[]" class="half-input name" placeholder="Name"> <input type="text" name="attendee_email[]" class="half-input email" placeholder="Email"> </div> </script> @stop @section('jquery_script') <script src="{{ url('assets/lib/jquery.min.js') }}"></script> @stop @section('handlebars_script') <script src="{{ url('assets/lib/handlebars.min.js') }}"></script> @stop @section('jquery_datetimepicker_script') <script src="{{ url('assets/lib/jquery-datetimepicker/jquery.datetimepicker.min.js') }}"></script> @stop @section('create_event_script') <script src="{{ url('assets/js/create_event.js') }}"></script> @stop
В приведенном выше шаблоне вы также create_event.js
файл create_event.js
. Этот файл отвечает за преобразование полей даты в средства выбора даты и времени, а также за прослушивание события размытия в текстовом поле электронной почты участника. После запуска события размытия и заполнения пользователем имени и адреса электронной почты для этой строки будет сгенерирована новая строка, в которую пользователь может ввести другого участника.
var attendee_template = Handlebars.compile($('#attendee-template').html()); $('.datetimepicker').datetimepicker(); $('#attendees').on('blur', '.email', function(){ var attendee_row = $('.attendee-row:last'); var name = attendee_row.find('.name').val(); var email = attendee_row.find('.email').val(); if(name && email){ $('#attendees').append(attendee_template()); } });
Далее следует метод, который будет выполнен после отправки формы создания события:
public function doCreateEvent(Event $evt, Request $request) { ... }
Внутри метода проверьте форму и получите данные из полей:
$this->validate($request, [ 'title' => 'required', 'calendar_id' => 'required', 'datetime_start' => 'required|date', 'datetime_end' => 'required|date' ]); $title = $request->input('title'); $calendar_id = $request->input('calendar_id'); $start = $request->input('datetime_start'); $end = $request->input('datetime_end');
Преобразуйте начальную и конечную дату-дату в дату, чтобы ее можно было легко отформатировать:
$start_datetime = Carbon::createFromFormat('Y/m/d H:i', $start); $end_datetime = Carbon::createFromFormat('Y/m/d H:i', $end);
Создайте новое событие Календаря Google и установите в его сводке заголовок, введенный пользователем:
$event = new \Google_Service_Calendar_Event(); $event->setSummary($title);
Установите время начала и окончания. Обратите внимание, что API Календаря Google ожидает, что дата и время будут отформатированы как строка атома, поэтому вы используете метод toAtomString()
предоставленный Carbon, для форматирования $start_datetime
и $end_datetime
.
$start = new \Google_Service_Calendar_EventDateTime(); $start->setDateTime($start_datetime->toAtomString()); $event->setStart($start); $end = new \Google_Service_Calendar_EventDateTime(); $end->setDateTime($end_datetime->toAtomString()); $event->setEnd($end);
Проверьте, добавил ли пользователь каких-либо участников:
if ($request->has('attendee_name')) { ... }
Если это так, присвойте их переменным:
$attendees = []; $attendee_names = $request->input('attendee_name'); $attendee_emails = $request->input('attendee_email');
Перейдите к каждому из них, и если пользователь ввел имя и адрес электронной почты, создайте нового участника мероприятия в календаре Google и задайте его адрес электронной почты и имя. Затем поместите созданного участника в массив $attendees
.
foreach ($attendee_names as $index => $attendee_name) { $attendee_email = $attendee_emails[$index]; if (!empty($attendee_name) && !empty($attendee_email)) { $attendee = new \Google_Service_Calendar_EventAttendee(); $attendee->setEmail($attendee_email); $attendee->setDisplayName($attendee_name); $attendees[] = $attendee; } }
После циклического прохождения каждого из участников, прикрепите массив $attendees
к участникам мероприятия:
$event->attendees = $attendees;
Сохраните событие в Google Calendar:
$created_event = $cal->events->insert($calendar_id, $event);
Также сохраните событие в базе данных:
$evt->title = $title; $evt->calendar_id = $calendar_id; $evt->event_id = $created_event->id; $evt->datetime_start = $start_datetime->toDateTimeString(); $evt->datetime_end = $end_datetime->toDateTimeString(); $evt->save();
Перенаправить обратно на страницу создания события:
return redirect('/event/create') ->with('message', [ 'type' => 'success', 'text' => 'Event was created!' ]);
Синхронизация календаря
Далее следует функция, которая возвращает представление для синхронизации календаря ( resources/views/admin/sync_calendar.blade.php
). Здесь вы передаете календари, созданные пользователем, в качестве данных для этого представления.
public function syncCalendar(Calendar $calendar) { $user_id = session('user.id'); $calendars = $calendar->where('user_id', '=', $user_id) ->get(); $page_data = [ 'calendars' => $calendars ]; return view('admin.sync_calendar', $page_data); }
Ниже приведен код для представления admin.sync_calendar
. Это позволяет пользователю выбрать календарь для синхронизации. После этого добавляются все события, которые не были добавлены в базу данных, удаляются отмененные события и обновляются обновленные с момента последней синхронизации.
@extends('layouts.admin') @section('content') <form method="POST"> <input type="hidden" name="_token" value="{{{ csrf_token() }}}" /> <p> <label for="calendar_id">Calendar</label> <select name="calendar_id" id="calendar_id"> @foreach($calendars as $cal) <option value="{{ $cal->id }}">{{ $cal->title }}</option> @endforeach </select> </p> <button>Sync Calendar</button> </form> @stop
Как только пользователь отправляет форму синхронизации календаря, doSyncCalendar
метод doSyncCalendar
:
public function doSyncCalendar(Request $request) { ... }
Внутри функции найдите календарь в базе данных:
$calendar = Calendar::find($calendar_id); $sync_token = $calendar->sync_token; $g_calendar_id = $calendar->calendar_id;
Создайте новый экземпляр службы календаря Google и получите календарь на основе его идентификатора. Также получите часовой пояс Календаря. Это позволяет вам установить, в каком часовом поясе указываются даты и время событий. Таким образом, вы можете легко конвертировать в часовой пояс, используемый приложением.
$g_cal = new \Google_Service_Calendar($this->client); $g_calendar = $g_cal->calendars->get($g_calendar_id); $calendar_timezone = $g_calendar->getTimeZone();
Получить идентификаторы событий, которые принадлежат календарю, выбранному пользователем. Это будет использоваться для проверки, было ли определенное событие уже добавлено ранее. Оттуда вы можете определить, что делать с событием. Если он уже находится в базе данных и статус не отменен, то требуется обновление, в противном случае удалите его из базы данных. Если это еще не в базе данных, то это требует добавления.
$events = Event::where('id', '=', $calendar_id) ->lists('event_id') ->toArray();
Укажите фильтр по умолчанию для первой синхронизации. Опция showDeleted
позволяет вам возвращать удаленные события, а опция timeMin
позволяет вам указать минимальную timeMin
и время, timeMin
будут использоваться в запросе. Таким образом, прошлые события не вернутся.
$params = [ 'showDeleted' => true, 'timeMin' => Carbon::now() ->setTimezone($calendar_timezone) ->toAtomString() ];
Синхронизация Календаря Google работает с использованием токена синхронизации, который изменяется каждый раз, когда в календарь вносятся изменения (например, добавляется событие). Затем этот токен необходимо сохранить в базе данных, так что если токен на сервере Google совпадает с имеющимся у вас токеном, это означает, что ничего не изменилось. Если это не так, это единственный раз, когда вы выполняете обновление. После обновления токен также необходимо обновить в базе данных.
Если токен синхронизации не пуст, используйте его вместо фильтров по умолчанию. Это позволяет вам возвращать только те события, которые были обновлены или добавлены.
if (!empty($sync_token)) { $params = [ 'syncToken' => $sync_token ]; }
Получить события:
$googlecalendar_events = $g_cal->events->listEvents($g_calendar_id, $params);
Далее идет бесконечный цикл, выполнение которого прекращается только после того, как вы выбрали все обновления для календаря, выбранного пользователем.
while (true) { ... }
Внутри бесконечного цикла переберите события, возвращаемые API, и извлеките все соответствующие данные:
foreach ($googlecalendar_events->getItems() as $g_event) { $g_event_id = $g_event->id; $g_event_title = $g_event->getSummary(); $g_status = $g_event->status; ... }
Проверьте статус события. Если оно отменено, то удалите событие из базы данных:
if ($g_status != 'cancelled') { ... } else { //delete event if (in_array($g_event_id, $events)) { Event::where('event_id', '=', $g_event_id)->delete(); } }
Если событие не отменено, извлеките дату и время начала и окончания события и преобразуйте его в часовой пояс приложения.
$g_datetime_start = Carbon::parse($g_event->getStart() ->getDateTime()) ->tz($calendar_timezone) ->setTimezone($base_timezone) ->format('Ymd H:i:s'); $g_datetime_end = Carbon::parse($g_event->getEnd()->getDateTime()) ->tz($calendar_timezone) ->setTimezone($base_timezone) ->format('Ymd H:i:s');
Если идентификатор события найден в массиве идентификаторов, это означает, что событие было обновлено, поэтому вы также выполняете обновление базы данных:
if (in_array($g_event_id, $events)) { //update event $event = Event::where('event_id', '=', $g_event_id)->first(); $event->title = $g_event_title; $event->calendar_id = $g_calendar_id; $event->event_id = $g_event_id; $event->datetime_start = $g_datetime_start; $event->datetime_end = $g_datetime_end; $event->save(); }
Если его еще нет в массиве, это означает, что это новое событие, поэтому вы добавляете новую запись в базу данных:
else { //add event $event = new Event; $event->title = $g_event_title; $event->calendar_id = $g_calendar_id; $event->event_id = $g_event_id; $event->datetime_start = $g_datetime_start; $event->datetime_end = $g_datetime_end; $event->save(); }
После просмотра событий найдите токен для следующей страницы. Обратите внимание, что это не то же самое, что токен синхронизации из предыдущего. Маркер страницы используется исключительно для нумерации страниц. Здесь вы просто проверяете, есть ли еще какие-либо страницы в текущем наборе результатов.
$page_token = $googlecalendar_events->getNextPageToken();
Если у него есть следующая страница, установите его как pageToken
для следующего запроса, а затем продолжите запрос на получение событий:
if ($page_token) { $params['pageToken'] = $page_token; $googlecalendar_events = $g_cal->events->listEvents('primary', $params); }
Если их нет, это означает, что у вас есть все результаты, поэтому выполните запрос, чтобы получить следующий токен синхронизации, а затем обновите базу данных этим значением. В этот момент вы можете выйти из бесконечного цикла.
else { $next_synctoken = str_replace('=ok', '', $googlecalendar_events->getNextSyncToken()); //update next sync token $calendar = Calendar::find($calendar_id); $calendar->sync_token = $next_synctoken; $calendar->save(); break; //exit out of the inifinite loop }
Перенаправьте на страницу синхронизации календаря и сообщите пользователю, что календарь был синхронизирован:
return redirect('/calendar/sync') ->with('message', [ 'type' => 'success', 'text' => 'Calendar was synced.' ]);
События листинга
Далее следует метод для отображения всех событий, которые пользователь синхронизировал:
public function listEvents() { $user_id = session('user.id'); $calendar_ids = Calendar::where('user_id', '=', $user_id) ->lists('calendar_id') ->toArray(); $events = Event::whereIn('calendar_id', $calendar_ids)->get(); $page_data = [ 'events' => $events ]; return view('admin.events', $page_data); }
Представление событий ( resources/views/admin/events.blade.php
) содержит следующее:
@extends('layouts.admin') @section('content') @if(count($events) > 0) <table> <thead> <tr> <th>Title</th> <th>Datetime Start</th> <th>Datetime End</th> </tr> </thead> <tbody> @foreach($events as $event) <tr> <td>{{ $event->title }}</td> <td>{{ $event->datetime_start }}</td> <td>{{ $event->datetime_end }}</td> </tr> @endforeach </tbody> </table> @endif @stop @stop
Выйти
Наконец, есть метод logout
из logout
который выполняется, когда пользователь нажимает на ссылку выхода из системы на панели администратора. Это удаляет все данные сеанса и перенаправляет пользователя обратно на домашнюю страницу.
public function logout(Request $request) { $request->session()->flush(); return redirect('/') ->with('message', ['type' => 'success', 'text' => 'You are now logged out']); }
Вывод
Это оно! В этом руководстве вы познакомились с тем, как создать приложение PHP, которое интегрируется с API Календаря Google. Таким образом, вы научились управлять календарем Google с помощью PHP.
Использовали ли вы API-интерфейсы Google для создания собственного календарного приложения? Легко ли / трудно ли было следовать этим инструкциям? Все отзывы приветствуются!