Send large attachment with Gmail API - php

According to Gmail API to send an email with large attachments bigger then 5MB you need to use the instructions here:
https://developers.google.com/gmail/api/guides/uploads
The API is a not very clear about the details and I tried to use the explanation I found here:
Gmail API PHP Client Library - How do you send large attachments using the PHP client library?
I get "Entity too large" error message every time.
Someone can succeded to send an attachment bigger than 5MB with Gmail API, and help me understand what I'm doing wrong?
My composer file:
{
"require": {
"google/apiclient": "^2.0",
"pear/mail_mime": "^1.10"
}
}
My code after reading everything is this:
<?php
set_time_limit(100);
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/vendor/autoload.php';
require_once 'client.php';
$googleClient = getClient();
$mailService = new Google_Service_Gmail($googleClient);
$message = new Google_Service_Gmail_Message;
$file = __DIR__ . '/pexels-photo.jpg';
$mime = new Mail_mime;
$mime->addTo('mymail#domain.com');
$mime->setTXTBody('');
$mime->setSubject('test');
$mailMessage = base64_encode($mime->getMessage());
$message->setRaw($mailMessage);
$request = $mailService->users_messages->send(
'me',
$message,
array( 'uploadType' => 'resumable' )
);
$googleClient->setDefer(true);
$media = new Google_Http_MediaFileUpload(
$googleClient,
$request,
'message/rfc822',
$mailMessage,
$resumable = true,
$chunkSizeBytes = 1 * 1024 * 1024
);
$media->setFileSize(filesize($file));
$status = false;
$handle = fopen($file, 'rb');
while (! $status && ! feof($handle)) {
$chunk = fread($handle, $chunkSizeBytes);
$status = $media->nextChunk($chunk);
}
fclose($handle);
$googleClient->setDefer(false);

#MP In my case, the problem is that I needed to set the unencoded message size, not the actual file. like this:
$media->setFileSize(strlen($message));

Related

Google API client (YouTube) errors

I'm trying to implement uploading video to YouTube from the server but I'm having problems with that. I'm using Laravel 9 and Google API client for php. The code is like this, pretty much the same as a google example:
public function goToAuthUrl() {
$client = new Client();
$client->setApplicationName('Test');
$client->setScopes([
YouTube::YOUTUBE_UPLOAD,
]);
$client->setAuthConfig('client_secret_***.apps.googleusercontent.com.json');
$client->setAccessType('offline');
$authUrl = $client->createAuthUrl();
return redirect()->away($authUrl);
}
public function youtubeHandle(Request $request) {
session_start();
$htmlBody = '';
$client = new Google_Client();
$client->setAuthConfigFile('client_secret_***.apps.googleusercontent.com.json');
$client->setRedirectUri('https://***/youtube');
$client->addScope(YouTube::YOUTUBE_UPLOAD);
if (!isset($request->code)) {
$auth_url = $client->createAuthUrl();
} else {
$accessToken = $client->fetchAccessTokenWithAuthCode($request->code);
$client->setAccessToken($accessToken);
try{
$videoPath = url('storage/images/rain.mp4');
// Define an object that will be used to make all API requests.
$youtube = new Google_Service_YouTube($client);
$snippet = new Google_Service_YouTube_VideoSnippet();
$snippet->setTitle("Test title");
$snippet->setDescription("Test description");
$snippet->setTags(array("test"));
// Numeric video category.
$snippet->setCategoryId(27);
// Set the video's status to "public". Valid statuses are "public",
// "private" and "unlisted".
$status = new Google_Service_YouTube_VideoStatus();
$status->privacyStatus = "unlisted";
// Associate the snippet and status objects with a new video resource.
$video = new Google_Service_YouTube_Video();
$video->setSnippet($snippet);
$video->setStatus($status);
// Specify the size of each chunk of data, in bytes. Set a higher value for
// reliable connection as fewer chunks lead to faster uploads. Set a lower
// value for better recovery on less reliable connections.
$chunkSizeBytes = 1 * 1024 * 1024;
// Setting the defer flag to true tells the client to return a request which can be called
// with ->execute(); instead of making the API call immediately.
$client->setDefer(true);
// Create a request for the API's videos.insert method to create and upload the video.
$insertRequest = $youtube->videos->insert("status,snippet", $video);
// Create a MediaFileUpload object for resumable uploads.
$media = new Google_Http_MediaFileUpload(
$client,
$insertRequest,
'video/*',
null,
true,
$chunkSizeBytes
);
$media->setFileSize(Storage::size('public/images/rain.mp4'));
// Read the media file and upload it chunk by chunk.
$status = false;
$handle = fopen($videoPath, "rb");
while (!$status && !feof($handle)) {
$chunk = fread($handle, $chunkSizeBytes);
$status = $media->nextChunk($chunk);
}
fclose($handle);
// If you want to make other calls after the file upload, set setDefer back to false
$client->setDefer(false);
$htmlBody .= "<h3>Video Uploaded</h3><ul>";
$htmlBody .= sprintf('<li>%s (%s)</li>',
$status['snippet']['title'],
$status['id']);
$htmlBody .= '</ul>';
} catch (Google_Service_Exception $e) {
$htmlBody .= sprintf('<p>A service error occurred: <code>%s</code></p>',
htmlspecialchars($e->getMessage()));
} catch (Google_Exception $e) {
$htmlBody .= sprintf('<p>An client error occurred: <code>%s</code></p>',
htmlspecialchars($e->getMessage()));
}
$_SESSION['token'] = $client->getAccessToken();
}
echo $htmlBody;
So, oauth process goes well, first I run goToAuthUrl() function, give the permissions, it redirects me back to the website and runs youtubeHandle() function. And here are the problems. It throws an error
Invalid request. The number of bytes uploaded is required to be equal or greater than 262144, except for the final request (it's recommended to be the exact multiple of 262144). The received request contained 16098 bytes, which does not meet this requirement.
and points to this line $status = $media->nextChunk($chunk);.
I tried to find the solutions and change the code, like changing $insertRequest variable to this:
$insertRequest = $youtube->videos->insert("status,snippet", $video, [
'data' => file_get_contents(url('storage/images/rain.mp4')),
'mimeType' => 'video/*',
'uploadType' => 'multipart'
]);
This way it throws another error
Failed to start the resumable upload (HTTP 200)
and video isn't being created on the channel.
Could you tell me where's the problem?
Once again I make sure that to make a question is a half way to get an answer. I found a solution, the example I used is old but it's there in the documentation. If someone meet this problem - it's all about chunks and there's a function to get it:
private function readVideoChunk($handle, $chunkSize) {
$byteCount = 0;
$giantChunk = "";
while (!feof($handle)) {
// fread will never return more than 8192 bytes if the stream is read
// buffered and it does not represent a plain file
$chunk = fread($handle, 8192);
$byteCount += strlen($chunk);
$giantChunk .= $chunk;
if ($byteCount >= $chunkSize) {
return $giantChunk;
}
}
return $giantChunk;
}
So uploading should look like this:
// Read the media file and upload it chunk by chunk.
$status = false;
$handle = fopen($videoPath, "rb");
while (!$status && !feof($handle)) {
$chunk = $this->readVideoChunk($handle, $chunkSizeBytes);
$status = $media->nextChunk($chunk);
}

Error 413: Request Entity Too Large with PHP

I'm using Gmail's PHP API to send emails. Using those resources, I can send messages with attachments upto 5 mb.
But I can't figure out how to send attachments larger than 5 MB. I've found that it is necessary to use multipart uploadtype, but I can not figure out exactly how to implement that based on what I tried is:
$service->users_messages->send($userId, $message, 'uploadType' => 'resumable']);
$service->users_messages->send($userId, $message, 'uploadType' => 'multipart']);
still getting Error 413: Request Entity Too Large
Already researched on internet but not able to make it working.
Edit: Below codes give me Request is too large. even for 5 mb file
$mail->preSend();
$mime = $mail->getSentMIMEMessage();
$raw = rtrim(strtr(base64_encode($mime), '+/', '-_'), '=');
$message = new Google_Service_Gmail_Message();
$message->setRaw($raw);
$message->setThreadId($threadId); //only for reply
$sendOptions = [
'uploadType' => 'resumable'
];
// size of chunks we are going to send to google
$chunkSizeBytes = 1 * 1024 * 1024;
$client->setDefer(true);
$response = $service->users_messages->send($userId, $message, $sendOptions);
// create mediafile upload
$media = new Google_Http_MediaFileUpload(
$client,
$response,
'message/rfc822',
$raw,
true,
$chunkSizeBytes
);
$media->setFileSize(strlen($raw));
// Upload the various chunks. $status will be false until the process is complete.
$status = false;
while (! $status) {
$status = $media->nextChunk();
echo $status ."<br>";
}
// Reset to the client to execute requests immediately in the future.
$client->setDefer(false);
// Get sent email message id
$googleMessageId = $status->getId();
Here they suggest to Remove $message->setRaw($raw); . If I remove this line than I get Recipient address required error.
How I fixed it:
$mail = new PHPMailer();
$mail->CharSet = 'UTF-8';
$mail->Subject = $subject;
$mail->Body = $body;
$mail->IsHTML(true);
$mail->addAddress($to);
$mail->AddCC($cc);
$mail->AddBCC($bcc);
$mail->preSend();
$mime = $mail->getSentMIMEMessage();
$sendOptions = [ 'uploadType' => 'resumable' ];
$client->setDefer(true);
$chunkSizeBytes = 1 * 1024 * 1024;
// create mediafile upload
$media = new Google_Http_MediaFileUpload(
$client,
$response,
'message/rfc822',
$mime,
true,
$chunkSizeBytes
);
$response = $service->users_messages->send($userId, $message);
$media->setFileSize(strlen($mime));
// Upload the various chunks. $status will be false until the process is complete.
$status = false;
while (! $status) {
$status = $media->nextChunk();
}
//Reset to the client to execute requests immediately in the future.
$client->setDefer(false);
// Get sent email message id
$googleMessageId = $status->getId();

Insert object to Google Cloud Storage at specific route using PHP

I am amazed by the lack of information at the official documentation of Google Cloud Storage with PHP.
Here we go... I'm trying to manage bucket's uploads and downloads with Google APIs Client Library for PHP, but I can't find anywhere the way to specify to the API where I want to upload my files at google's bucket (I can only upload them at bucket's root).
Here's the code that I'm using for the upload (extracted from https://github.com/guillefd/Backup-manager-gcs/blob/master/application/libraries/Googlecloudstorage.php):
public function media_file_upload($file_path = null, $file_name = null) {
# init
$result = new stdClass();
$result->error = null;
$result->status = null;
$result->exception = null;
# timer
$result->starttime = microtime();
# init gcs api
$gso = new Google_Service_Storage_StorageObject();
$gso->setName($file_name);
$gso->setBucket($this->bucket);
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimetype = finfo_file($finfo,$file_path);
$chunkSizeBytes = 1 * 1024 * 1024;
$this->client->setDefer(true);
$filetoupload = array(
'name'=>$file_name,
'uploadType'=>'resumable'
);
# service
$this->newStorageService();
$status = false;
# try
try {
$request = $this->storageService->objects->insert($this->bucket, $gso, $filetoupload);
$media = new Google_Http_MediaFileUpload($this->client, $request, $mimetype, null, true, $chunkSizeBytes);
$media->setFileSize(filesize($file_path));
$handle = fopen($file_path, "rb");
# loop chunks
while(!$status && !feof($handle)) {
$chunk = fread($handle, $chunkSizeBytes);
$status = $media->nextChunk($chunk);
}
fclose($handle);
$this->client->setDefer(false);
// $result->status = $status;
} catch(Exception $e) {
$result->error = true;
$result->status = 'Google Cloud Service upload failed';
$result->exception = $e;
}
# timer
$result->endtime = microtime();
$result->totaltime = $this->get_totaltime($result);
# verify response
$result->httpcode = http_response_code();
$result->error = isset($status->kind) && $status->kind==self::STORAGE_OBJECT ? false : true;
return $result;
}
The main question that I'm actually asking is: Is there a way to upload files to google's bucket at a specific route, using Google Cloud's PHP API?
Thanks!

Files updates no longer works using the google-api-php-client

I have a very small script that uploads and / or update files (wrote this about 1 year ago, borrowed 80% of the lines from the examples)
No major changes in the code since march (using 1.0.0-alpha) but mid-may the file updates stopped working, raising an Internal Server Error , i upgraded to 1.0.4-beta with no success :
PHP Fatal error: Uncaught exception 'Google_Service_Exception' with message 'Error calling PUT https://www.googleapis.com/upload/drive/v2/ [...] (500) Internal Error' in
google-api-php-client-1.0.4-beta/src/Google/Http/REST.php:80
code:
$client->setDefer(true);
$request = $service->files->update($update_id,$file);
$media = new Google_Http_MediaFileUpload(
$client,
$request,
'text/plain',
null,
true,
$chunkSizeBytes
);
$media->setFileSize(filesize($csvfile))
$status = false;
$handle = fopen($csvfile, "rb");
while (!$status && !feof($handle)) {
$chunk = fread($handle, $chunkSizeBytes);
$status = $media->nextChunk($chunk);
}
File inserts (HTTP POST) are still working (using the same code for uploading the chunks)
any ideas ?
I can update a file ( previously created or copied from a template ) with this code :
(...)
require_once 'src/Google/Client.php';
require_once 'src/Google/Service/Oauth2.php';
require_once 'src/Google/Service/Drive.php';
(...)
$client = new Google_Client();
// set scopes ...
(...)
$GDrive_service = new Google_Service_Drive($client);
(...)
// then I set parameters
(...)
$newTitle = $title ;
$newDescription = $description ;
$newMimeType = 'text/csv' ;
$newFileName = $filename ;
$service = $GDrive_service ;
(...)
$updatedFile = updateFile($service, $fileId, $newTitle, $newDescription, $newMimeType, $newFileName, $newRevision) ;
function updateFile($service, $fileId, $newTitle, $newDescription, $newMimeType, $newFileName, $newRevision) {
try {
// First retrieve the file from the API.
$file = $service->files->get($fileId);
// File's new metadata.
$file->setTitle($newTitle);
$file->setDescription($newDescription);
$file->setMimeType($newMimeType);
// File's new content.
$data = file_get_contents($newFileName);
$convert = 'true' ;
$additionalParams = array(
'uploadType' => 'multipart',
'newRevision' => $newRevision,
'data' => $data,
'mimeType' => $newMimeType,
'convert' => $convert,
);
// Send the request to the API.
$updatedFile = $service->files->update($fileId, $file, $additionalParams);
return $updatedFile;
} catch (Exception $e) {
print "An error occurred: " . $e->getMessage();
}
}
(...)
// this is info of the updated file
$fileId = $updatedFile->getId() ;
// link of file
$cF_link = $updatedFile->alternateLink ;
// pdf version
$enllacos = $updatedFile->exportLinks ;
$cF_PDF_link = $enllacos['application/pdf'] ;
(...)
This code is working with the 1.0.5-beta php client
Sergi

Getting a video from S3 and Uploading to YouTube in PHP

I have some code working that uploads a video file up to YouTube:
$yt = new Zend_Gdata_YouTube($httpClient);
// create a new VideoEntry object
$myVideoEntry = new Zend_Gdata_YouTube_VideoEntry();
// create a new Zend_Gdata_App_MediaFileSource object
$filesource = $yt->newMediaFileSource('file.mov');
$filesource->setContentType('video/quicktime');
// set slug header
$filesource->setSlug('file.mov');
I have videos in S3 and I want to upload them to YouTube. The video in our S3 account is public, so i can use a command like wget. Should I run a command that wgets the video file and downloads it locally before I run this script (shell_exec("wget ".$s3videoURL))?
Or should I try to enter the MediaFileSource as the URL of the S3 file itself?
Mainly, I just need stability (not a solution subject to frequent time-outs); speed and local storage isn't really important (I can locally delete video file once its been uploaded).
What would be the best way to go about this?
Thanks!
Update: I should probably mention that this script is going to be uploading about 5 videos to YouTube per execution.
This is an old question but i believe i have a better answer.
You don't have to write video to HDD and you can't keep the whole thing in RAM (I assume it is a big file).
You can use PHP AWS SDK and Google Client libraries to buffer file from S3 and send it to YouTube on the fly. Use registerStreamWrapper method to register S3 as file system and use resumable uploads from YouTube API. Then all you have to do is reading chunks from S3 with fread and sending them to YouTube. This way you can even limit the RAM usage.
I assume you created the video object ($video in code) from Google_Video class. This is a complete code.
<?php
require_once 'path/to/libraries/aws/vendor/autoload.php';
require_once 'path/to/libraries/google-client-lib/autoload.php';
use Aws\S3\S3Client;
$chunkSizeBytes = 2 * 1024 * 1024; // 2 mb
$streamName = 's3://bucketname/video.mp4';
$s3client = S3Client::factory(array(
'key' => S3_ACCESS_KEY,
'secret' => S3_SECRET_KEY,
'region' => 'eu-west-1' // if you need to set.
));
$s3client->registerStreamWrapper();
$client = new Google_Client();
$client->setClientId(YOUTUBE_CLIENT_ID);
$client->setClientSecret(YOUTUBE_CLIENT_SECRET);
$client->setAccessToken(YOUTUBE_TOKEN);
$youtube = new Google_YoutubeService($client);
$media = new Google_MediaFileUpload('video/*', null, true, $chunkSizeBytes);
$filesize = filesize($streamName); // use it as a reguler file.
$media->setFileSize($filesize);
$insertResponse = $youtube->videos->insert("status,snippet", $video, array('mediaUpload' => $media));
$uploadStatus = false;
$handle = fopen($streamName, "r");
$totalReceived = 0;
$chunkBuffer = '';
while (!$uploadStatus && !feof($handle)) {
$chunk = fread($handle, $chunkSizeBytes);
$chunkBuffer .= $chunk;
$chunkBufferSize = strlen($chunkBuffer);
if($chunkBufferSize > $chunkSizeBytes) {
$fullChunk = substr($chunkBuffer, 0, $chunkSizeBytes);
$leapChunk = substr($chunkBuffer, $chunkSizeBytes);
$uploadStatus = $media->nextChunk($insertResponse, $fullChunk);
$totalSend += strlen($fullChunk);
$chunkBuffer = $leapChunk;
echo PHP_EOL.'Status: '.($totalReceived).' / '.$filesize.' (%'.(($totalReceived / $filesize) * 100).')'.PHP_EOL;
}
$totalReceived += strlen($chunk);
}
$extraChunkLen = strlen($chunkBuffer);
$uploadStatus = $media->nextChunk($insertResponse, $chunkBuffer);
$totalSend += strlen($chunkBuffer);
fclose($handle);
The "MediaFileSource" must be a real file. It won't take a URL, so you will need to copy the videos to your server from S3, before sending them to YouTube.
You can probably get away with the "shell_exec" if your usage is light, but for a variety of reasons its probably better to use either the Zend S3 Service, or cURL to pull files from S3.
I had to make some changes to #previous_developer 's answer to make it work with Youtube Data API V3 (Please upvote him as I could not find any working code except his).
$streamName = 's3://BUCKET-NAME/VIDEO.mp4';
/**
Since I have been using Yii 2. Use the AWS
SDK directly instead.
*/
$aws = Yii::$app->awssdk->getAwsSdk();
$s3client = $aws->createS3();
$s3client->registerStreamWrapper();
$service = new \Google_Service_YouTube($client);
$snippet = new \Google_Service_YouTube_VideoSnippet();
$snippet->setTitle("Test title");
$snippet->setDescription("Test descrition");
$snippet->setTags(array("tag1","tag2"));
$snippet->setCategoryId("22");
$status = new \Google_Service_YouTube_VideoStatus();
$status->privacyStatus = "public";
$video = new \Google_Service_YouTube_Video();
$video->setSnippet($snippet);
$video->setStatus($status);
$client->setDefer(true);
$insertResponse = $service->videos->insert("status,snippet", $video);
$media = new MediaFileUpload(
$client,
$insertResponse,
'video/*',
null,
true,
false
);
$filesize = filesize($streamName); // use it as a reguler file.
$media->setFileSize($filesize);
$chunkSizeBytes = 2 * 1024 * 1024; // 2 mb
$uploadStatus = false;
$handle = fopen($streamName, "r");
$totalSend = 0;
$totalReceived = 0;
$chunkBuffer = '';
while (!$uploadStatus && !feof($handle)) {
$chunk = fread($handle, $chunkSizeBytes);
$chunkBuffer .= $chunk;
$chunkBufferSize = strlen($chunkBuffer);
if($chunkBufferSize > $chunkSizeBytes) {
$fullChunk = substr($chunkBuffer, 0, $chunkSizeBytes);
$leapChunk = substr($chunkBuffer, $chunkSizeBytes);
$uploadStatus = $media->nextChunk($fullChunk);
$totalSend += strlen($fullChunk);
$chunkBuffer = $leapChunk;
echo PHP_EOL.'Status: '.($totalReceived).' / '.$filesize.' (%'.(($totalReceived / $filesize) * 100).')'.PHP_EOL;
}
$totalReceived += strlen($chunk);
}
$extraChunkLen = strlen($chunkBuffer);
$uploadStatus = $media->nextChunk($chunkBuffer);
$totalSend += strlen($chunkBuffer);
fclose($handle);
// If you want to make other calls after the file upload, set setDefer back to false
$client->setDefer(false);
$chunkSizeBytes = 2 * 1024 * 1024; // 2 mb
$s3client = $this->c_aws->getS3Client();
$s3client->registerStreamWrapper();
try {
$client = new \Google_Client();
$client->setAccessType("offline");
$client->setApprovalPrompt('force');
$client->setClientId(GOOGLE_CLIENT_ID);
$client->setClientSecret(GOOGLE_CLIENT_SECRET);
$token = $client->fetchAccessTokenWithRefreshToken(GOOGLE_REFRESH_TOKEN);
$client->setAccessToken($token);
$youtube = new \Google_Service_YouTube($client);
// Create a snippet with title, description, tags and category ID
// Create an asset resource and set its snippet metadata and type.
// This example sets the video's title, description, keyword tags, and
// video category.
$snippet = new \Google_Service_YouTube_VideoSnippet();
$snippet->setTitle($title);
$snippet->setDescription($summary);
$snippet->setTags(explode(',', $keywords));
// Numeric video category. See
// https://developers.google.com/youtube/v3/docs/videoCategories/list
// $snippet->setCategoryId("22");
// Set the video's status to "public". Valid statuses are "public",
// "private" and "unlisted".
$status = new \Google_Service_YouTube_VideoStatus();
$status->privacyStatus = "public";
// Associate the snippet and status objects with a new video resource.
$video = new \Google_Service_YouTube_Video();
$video->setSnippet($snippet);
$video->setStatus($status);
// Setting the defer flag to true tells the client to return a request which can be called
// with ->execute(); instead of making the API call immediately.
$client->setDefer(true);
$insertRequest = $youtube->videos->insert("status,snippet", $video);
$media = new \Google_Http_MediaFileUpload(
$client,
$insertRequest,
'video/*',
null,
true,
$chunkSizeBytes
);
$result = $this->c_aws->getAwsFile($aws_file_path);
$media->setFileSize($result['ContentLength']);
$uploadStatus = false;
// Seek to the beginning of the stream
$result['Body']->rewind();
// Read the body off of the underlying stream in chunks
while (!$uploadStatus && $data = $result['Body']->read($chunkSizeBytes)) {
$uploadStatus = $media->nextChunk($data);
}
$client->setDefer(false);
if ($uploadStatus->status['uploadStatus'] == 'uploaded') {
// Actions to perform for a successful upload
$uploaded_video_id = $uploadStatus['id'];
return ($uploadStatus['id']);
}
}catch (\Google_Service_Exception $exception){
return '';
print_r($exception);
}

Categories