How to split the oauth logic and create the Google Calendar service - cakephp

I am using php client for Google Calendar API
https://github.com/google/google-api-php-client and CakePHP.
Basically, I just want to manage the events in the calendar.
And I could do it following the example here https://github.com/google/google-api-php-client/blob/master/examples/user-example.php. It worked fine (so, I have no problems with client id, client secret etc).
The only problem is that in the example it's all written in one file - the oauth logic and service specific logic (like retrieving events from calendar). So the redirect uri in google console points to the same file.
The pseudo-code for this is:
//here we create the client which is used for authentication
$client = new Google_Client();
//some of the oauth logic
$client->authenticate($_GET['code']);
//other oauth logic
$service = new Google_Service_Calendar($client);
$events = $service->events->list();
//...
So, as you see, to create a service we need a client (which contains the access token and all the authentication data needed for service).
I want to split it, so that I have the callback where I could retrieve the client and then use it for creating the service. I could have also specify that callback in google console as a redirect uri.
I have a feeling that there should be some standard way which unfortunately I am not aware of. I broke my head thinking about how to do this, so any help is appreciated.
Thanks.
UPDATE
As it often happens, right after posting a question here I managed to do something.
After we get an access token, we need to save it into DB.
Next time, when we want to do something with the api, we create the google client again and just set the access token with the value from the DB. So that we don't need to go through the oauth logic again.
Something like this:
$accessToken = $db->getOne('SELECT token FROM users WHERE id=:id');
$client = new Google_Client();
$client->setClientId($clientId);
$client->setClientSecret($clientSecret);
$client->setRedirectUri($redirectUri);
$client->setAccessType($accessType);
$client->setScopes($scope);
$client->setAccessToken(json_encode($accessToken));
Well, it looks obvious. But still I'd like to get an answer from experts to this question:
is recreating the google client object the right approach?
Thanks.

CakePHP 3.x in Google Calendar API
Follow this steps:
Create google calendar api
https://console.developers.google.com/flows/enableapi?apiid=calendar&pli=1
Composer to install:
"google/apiclient": "^2.0"
Required google calendar api integration
https://console.developers.google.com/flows/enableapi?apiid=calendar&pli=1
https://developers.google.com/google-apps/calendar/quickstart/php
Create project and create secret key and client id Project in set
name and redirect URL
NOTE:- redirect URL must be .com and .org
domain If you develop in local machine then create follow type of
virtual host example.com and example.org Virtual host create then
Follow this step:
Set configuration file in app_globle.php
'Google' => [
'ClientID' => '7441260037.apps.googleusercontent.com',
'ClientSecret' => 'kashdjahdkjshdkjhjAs',
'RedirectUrl' => 'http://' . env("HTTP_HOST") . '/oauth2calendars',
'ClientCredentials' => WWW_ROOT . 'files'. DS.'google.txt',
'Scopes' => implode(' ', [Google_Service_Calendar::CALENDAR, Google_Service_Drive::DRIVE, Google_Service_Drive::DRIVE_FILE, Google_Service_Drive::DRIVE_APPDATA, Google_Service_Drive::DRIVE_METADATA]),
]
Route
$routes->connect('/events/add', ['controller' => 'Events', 'action' => 'add', ['_name' => 'add-event']);
$routes->connect('/events/edit/:id', ['controller' => 'Events', 'action' => 'edit', ['id' => '\d+', 'pass' => ['id'], '_name' => 'edit-event']);
$routes->connect('/events/delete/:id', ['controller' => 'Events', 'action' => 'delete', ['id' => '\d+', 'pass' => ['id'], '_name' => 'delete-event']);
Controller
use Google_Client;
use Google_Service_Calendar;
use Google_Service_Calendar_Event;
use Google_Service_Drive;
use Google_Service_Drive_DriveFile;
Authorize the google Oauth thriugh
/**
* get Google calendar client
*/
private function getClient()
{
$client = new Google_Client();
$client->setAccessType("offline");
$client->setClientId(Configure::read('Google.ClientID'));
$client->setClientSecret(Configure::read('Google.ClientSecret'));
$client->setRedirectUri(Configure::read('Google.RedirectUrl'));
$client->setScopes(Configure::read('Google.Scopes'));
$credentialsPath = Configure::read('Google.Credentials');
if (file_exists($credentialsPath)) {
$accessToken = json_decode(file_get_contents($credentialsPath), true);
} else {
// Request authorization from the user.
$authUrl = $client->createAuthUrl();
return $this->redirect($authUrl);
}
$client->setAccessToken($accessToken);
// Refresh the token if it's expired.
if ($client->isAccessTokenExpired()) {
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
file_put_contents($credentialsPath, json_encode($client->getAccessToken()));
}
return $client;
}
/**
* Create Google Calendar event
*/
public function createCredentials()
{
$client = new Google_Client();
$client->setAccessType("offline");
$client->setClientId(Configure::read('Google.ClientID'));
$client->setClientSecret(Configure::read('Google.ClientSecret'));
$client->setRedirectUri(Configure::read('Google.RedirectUrl'));
$client->setScopes(Configure::read('Google.Scopes'));
if (isset($this->request->query['code'])) {
$client->authenticate($this->request->query['code']);
$token = json_encode($client->getAccessToken());
$credentialsPath =WWW_ROOT . 'files'. DS.'google.txt';
if (!file_exists(dirname($credentialsPath))) {
mkdir(dirname($credentialsPath), 0700, true);
}
$file = new File($credentialsPath, true);
$file->write($token);
$client->setAccessToken($token);
return $this->redirect(‘/add-event’);
}
}
Add event functionality
Public function add()
{
$client = $this->getClient();
if ($this->request->is('post')) {
$dateStart = new \DateTime($this->request->data['start_date_time'], new \DateTimeZone('Asia/Kolkata'));
$dateStart->setTimezone(new \DateTimeZone('UTC'));
$startDate = $dateStart->format('Y-m-d H:i:s');
$dateEnd = new \DateTime($this->request->data['end_date_time'], new \DateTimeZone('Asia/Kolkata'));
$dateEnd->setTimezone(new \DateTimeZone('UTC'));
$endDate = $dateEnd->format('Y-m-d H:i:s');
$guests = $this->request->data['guests'];
$eventGuest = [];
$service = new Google_Service_Calendar($client);
foreach ($guests as $key => $value) {
$eventGuest[$key]['email'] = $value;
}
$eventData = [
'summary' => $this->request->data['name'],
'location' => $this->request->data['location'],
'description' => $this->request->data['description'],
'start' => [
'dateTime' => date("c", strtotime($startDate)),
],
'end' => [
'dateTime' => date("c", strtotime($endDate)),
],
'attendees' => $eventGuest,
'reminders' => [
'useDefault' => true,
],
'visibility' => 'private',
'privateCopy' => true,
];
$event = new Google_Service_Calendar_Event($eventData);
$sendNotifications = ['sendNotifications' => true];
$calendarId = 'primary';
$eventDataResponse = $service->events->insert($calendarId, $event, $sendNotifications);
}
}

Related

cakedc/users autologin after registration

I'm using the cakephp 4.2.6 with the cakedc/users plugin. I'd like to automatically login the user after registration without the need to first activate the account by clicking on the link in e-mail.
My idea is to creating an Event-Listener and listening to the Users.Global.afterRegister Event, but then I don't know how to login the user with the new Authentication.
this is what I ended up with and it seems to work so far:
<?php
namespace App\Event;
use Authentication\AuthenticationService;
use Authentication\AuthenticationServiceInterface;
use Authentication\AuthenticationServiceProviderInterface;
use Authentication\Middleware\AuthenticationMiddleware;
use Psr\Http\Message\ServerRequestInterface;
use Cake\Event\EventListenerInterface;
use Cake\Datasource\FactoryLocator;
use Cake\Log\Log;
class UserEventListener implements EventListenerInterface
{
public function implementedEvents(): array
{
return [
'Users.Global.afterRegister' => 'autoLogin',
];
}
public function autoLogin($event)
{
$user = $usersTable->get($event->getData('user')->id);
$request = $event->getSubject()->getRequest();
$response = $event->getSubject()->getResponse();
$authenticationService = $this->getAuthenticationService($request);
$authenticationService->persistIdentity($request, $response, $user);
}
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
$authenticationService = new AuthenticationService([
'unauthenticatedRedirect' => \Cake\Routing\Router::url([
'controller' => 'Users',
'action' => 'login',
'plugin' => null,
'prefix' => null
]),
'queryParam' => 'redirect',
]);
// Load identifiers, ensure we check email and password fields
$authenticationService->loadIdentifier('Authentication.Password', [
'fields' => [
'username' => 'email',
'password' => 'password',
]
]);
// Load the authenticators, you want session first
$authenticationService->loadAuthenticator('Authentication.Session');
// Configure form data check to pick email and password
$authenticationService->loadAuthenticator('Authentication.Form', [
'fields' => [
'username' => 'email',
'password' => 'password',
],
'loginUrl' => \Cake\Routing\Router::url([
'controller' => 'Users',
'action' => 'login',
'plugin' => null,
'prefix' => null
]),
]);
return $authenticationService;
}
}
There is a similar use case covered in the plugin documentation https://github.com/CakeDC/users/blob/master/Docs/Documentation/Events.md
Your approach looks correct to me, using the afterRegister event to create the listener in a controller where you have access to the Authenticatication component,
// note this example is handling auto-login after the user validated the email sent (click the validation token link sent to his email address)
EventManager::instance()->on(
\CakeDC\Users\Plugin::EVENT_AFTER_EMAIL_TOKEN_VALIDATION,
function($event){
$users = $this->getTableLocator()->get('Users');
$user = $users->get($event->getData('user')->id);
$this->Authentication->setIdentity($user);
}
);
or using the request to retrieve the authentication service and set the identity like it's done here https://github.com/cakephp/authentication/blob/master/src/Controller/Component/AuthenticationComponent.php#L273

drupal_mail() returns true but unable to send mail

I have written a drupal module with custom form api which will send email to my inbox on each submit. I have written a condition under drupal_mail which returns true but shows an error message "Unable to send e-mail. Contact the site administrator if the problem persists."
Below my code:
function my_module_name_mail($key, &$message, $params)
{
$headers = array(
'MIME-Version' => '1.0',
'Content-Type' => 'text/html; charset=UTF-8;',
'Content-Transfer-Encoding' => '8Bit',
'X-Mailer' => 'Drupal'
);
foreach ($headers as $key => $value) {
$message['headers'][$key] = $value;
}
$message['subject'] = $params['subject'];
$message['body'] = $params['body'];
}
function my_module_name_form_submit($form, &$form_state)
{
$from = $form_state['values']['email'];
$body= 'Name: '.$name.'<br />Email: '.$email;
$to = "my_mail_id#example.com";
$params = array(
'body' => $body,
'subject' => 'Website Information Request',
);
drupal_mail('my_module_name', 'some_mail_key', $to, language_default(), $params, $from, TRUE);
}
Check your Drupal logs for more clues. Enable devel / devel-admin modules. Once you do, drupal_mail will pipe emails to your temp directory, which will help you narrow down if the problem is with your email server or the config.

can't send attachment with cakeemail

I can't send an attachment with the email. I don't get an error and I do send the message so the email works but no attachment.
Is my filepath not correct as the file exists in this file? Is it because I am using windows with file paths?
This is just a test email below to see if this function actually works for an attachment but it isn't working for me. I checked other answers and this seems to be the way to construct this.
http://book.cakephp.org/2.0/en/core-utility-libraries/email.html
public function sendEmailattach($to,$message,$subject,$attach) {
$Email = new CakeEmail();
$Email->config('gmail3');
$Email->filePaths = array('D:\crm5\app\Attachments');
$Email->attachments =array('Ch9-anna tax.docx');
$to='jXXXXX#gmail.com';//testing real email account
// $Email->from( array('admin#a.com.au' => 'A'));
$Email->from( array('jxxxxx#gmail.com' => 'test'));
$Email->to($to);
$Email->subject($subject);
$Email->send();
// $Email->send($message);
}//public
UPDATE
Tried all 3 methods and no error and no attachment?
// $Email->attachments('D:\AA-website design\crm5\app\Attachments\Ch9-anna tax.docx') ;
$Email->attachments(array('Ch9-anna tax.docx' => array(
'file' => 'D:\AA-website design\crm5\app\Attachments\Ch9-anna tax.docx',
) ));
// $Email->attachments(array('D:\AA-website design\crm5\app\Attachments\Ch9-anna tax.docx'));
$email = new CakeEmail('default');
$attachment = [
'file.pdf' => [
'file' => '/my/absolute/path/on/server/file.pdf',
'mimetype' => 'application/pdf',
'contentId' => uniqid()
]
];
$variables = ['emailHeader' => 'Hello'];
$email->attachments($attachment);
$email->from(['info#example.com' => 'Example'])
->to('recipient#example.com')
->subject('Subject')
->template('template')
->emailFormat('both')
->viewVars($variables)
->send();
Just read the manual instead just looking at the links, honestly I have doubt's you read it at all:
http://book.cakephp.org/2.0/en/core-utility-libraries/email.html#sending-attachments
It clearly shows that attachments is a method and not a property. It even has examples of what the method accepts.

Drupal: another way to fetch the taxonomy tid (apart from URL)

I am using webforms which have as the topic taxonomy terms saved as hidden fields.
Unfortunate, after attaching files using Upload button this terms are lost ('No category'), as ajax is altering the form #action URL from /tid to /file/ajax/submitted/file4/form-gTwVpNbQRszArGEUS1OfuwA8WgSLJOlcWGOuS6r9D5A
From where to read $tid?
Therefore my $tid fail to load from URL
function MYMODULE_form_alter(&$form, &$form_state, $form_id) {
$tid = arg(1);
$term = taxonomy_term_load($tid);
if (!empty($term)) {
$category = i18n_taxonomy_term_name($term, $language->language);
} else {
$category = 'No category';
}
$form['submitted'][$category_fieldkey] = array(
'#type' => 'hidden',
'#value' => $category,
);
}

Testing laravel controllers with JSON request body

I am trying to write a phpunit test for a Laravel controller which expects post requests with a body in JSON format.
A simplified version of the controller:
class Account_Controller extends Base_Controller
{
public $restful = true;
public function post_login()
{
$credentials = Input::json();
return json_encode(array(
'email' => $credentials->email,
'session' => 'random_session_key'
));
}
}
Currently I have a test method which is correctly sending the data as urlencoded form data, but I cannot work out how to send the data as JSON.
My test method (I used the github gist here when writing the test)
class AccountControllerTest extends PHPUnit_Framework_TestCase {
public function testLogin()
{
$post_data = array(
'email' => 'user#example.com',
'password' => 'example_password'
);
Request::foundation()->server->set('REQUEST_METHOD', 'POST');
Request::foundation()->request->add($post_data);
$response = Controller::call('account#login', $post_data);
//check the $response
}
}
I am using angularjs on the frontend and by default, requests sent to the server are in JSON format. I would prefer not to change this to send a urlencoded form.
Does anyone know how I could write a test method which provides the controller with a JSON encoded body?
In Laravel 5, the call() method has changed:
$this->call(
'PUT',
$url,
[],
[],
[],
['CONTENT_TYPE' => 'application/json'],
json_encode($data_array)
);
I think that Symphony's request() method is being called:
http://symfony.com/doc/current/book/testing.html
This is how I go about doing this in Laravel4
// Now Up-vote something with id 53
$this->client->request('POST', '/api/1.0/something/53/rating', array('rating' => 1) );
// I hope we always get a 200 OK
$this->assertTrue($this->client->getResponse()->isOk());
// Get the response and decode it
$jsonResponse = $this->client->getResponse()->getContent();
$responseData = json_decode($jsonResponse);
$responseData will be a PHP object equal to the json response and will allow you to then test the response :)
Here's what worked for me.
$postData = array('foo' => 'bar');
$postRequest = $this->action('POST', 'MyController#myaction', array(), array(), array(), array(), json_encode($postData));
$this->assertTrue($this->client->getResponse()->isOk());
That seventh argument to $this->action is content. See docs at http://laravel.com/api/source-class-Illuminate.Foundation.Testing.TestCase.html#_action
There is a lot easier way of doing this. You can simply set Input::$json property to the object you want to send as post parameter. See Sample code below
$data = array(
'name' => 'sample name',
'email' => 'abc#yahoo.com',
);
Input::$json = (object)$data;
Request::setMethod('POST');
$response = Controller::call('client#create');
$this->assertNotNull($response);
$this->assertEquals(200, $response->status());
I hope this helps you with your test cases
Update : The original article is available here http://forums.laravel.io/viewtopic.php?id=2521
A simple solution would be to use CURL - which will then also allow you to capture the 'response' from the server.
class AccountControllerTest extends PHPUnit_Framework_TestCase
{
public function testLogin()
{
$url = "account/login";
$post_data = array(
'email' => 'user#example.com',
'password' => 'example_password'
);
$content = json_encode($post_data);
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, array("Content-type: application/json"));
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $content);
$json_response = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
$response = json_decode($json_response, true);
// Do some $this->Assert() stuff here on the $status
}
}
CURL will actually simulate the raw HTTP post with JSON - so you know you are truly testing your functionality;
As of Laravel 5.1 there is a much easier way to test JSON controllers via PHPunit. Simply pass an array with the data and it'll get encoded automatically.
public function testBasicExample()
{
$this->post('/user', ['name' => 'Sally'])
->seeJson([
'created' => true,
]);
}
From the docs: http://laravel.com/docs/5.1/testing#testing-json-apis
Since at least Laravel 5.2 there is a json() method in Illuminate\Foundation\Testing\Concerns\MakesHttpRequests therefore you can do the following:
$data = [
"name" => "Foobar"
];
$response = $this->json('POST', '/endpoint', $data);
Also since Laravel 5.3 there are also convenient methods like putJson(), postJson(), etc. Therefore it can be even shortened further to:
$data = [
"name" => "Foobar"
];
$response = $this->postJson('/endpooint', $data);
And then you can do $response->assertJson(...) like:
$response->assertJson(fn (AssertableJson $json) => $json->hasAll(['id', 'name']));

Resources