问题
Quickstart: Run a Drive App in PHP seems to be using an outdated version of [google-api-php-client][2], also it doesn't provide example on how to upload a file using Service Account.
In my project I'll need to add a file (spreadsheet) using Service Account, add a spreadsheet, share the spreadsheet with another user and add rows to the spreadsheet.
I'm able to get an empty list of spreadsheets, but I couldn't figure it out how to upload a file using Service Account.
The PHP Class google-api-php-client/examples/spreadsheet.php:
<?php
use Google\Spreadsheet\DefaultServiceRequest;
use Google\Spreadsheet\ServiceRequestFactory;
use Google\Spreadsheet\SpreadsheetService;
define('JPATH_PLATFORM',1);
require_once realpath(dirname(__FILE__) . '/../autoload.php');
class GoogleSpreadSheet
{
protected $data;
protected $client_id = null ; //Client ID
protected $service_account_name = null; //Email Address
protected $key_file_location = null; //key.p12
protected $client = null;
protected $aplication_name = null;
protected $_errors = array();
protected $scopes = array( 'https://www.googleapis.com/auth/drive'
, 'https://www.googleapis.com/auth/drive.appdata'
, 'https://www.googleapis.com/auth/drive.apps.readonly'
, 'https://spreadsheets.google.com/feeds'
);
/**
* Class constructor
* @param mixed $properties Either and associative array or another
* object to set the initial properties of the object.
*/
public function __construct($properties = null){
if ($properties !== null)
{
$this->setProperties($properties);
}
$this->data = new stdClass();
$this->getClient();
}
/**
* Get Google_Client object
* @return mixed Google_Client object if client ID and application_name are set OR null
*/
public function getClient(){
if(empty($this->client) AND $this->get('client_id') AND $this->get('aplication_name')){
$this->client = new Google_Client();
$this->client->setApplicationName($this->get('aplication_name'));
$this->client->setClientId($this->get('client_id'));
}
return $this->client;
}
/**
* Get Service AccessToken
* @return AccessToken
*/
public function getToken()
{
$key = file_get_contents($this->get('key_file_location'));
$cred = new Google_Auth_AssertionCredentials(
$this->service_account_name,
$this->get('scopes', array()),
$key
);
$this->client->setAssertionCredentials($cred);
if($this->client->getAuth()->isAccessTokenExpired()) {
$this->client->getAuth()->refreshTokenWithAssertion($cred);
}
$service_token = json_decode($this->client->getAccessToken());
return $service_token->access_token;
}
/**
* Get Available Spreadsheets
*/
public function getSpreadsheetsList()
{
$accessToken = $this->getToken();
$serviceRequest = new DefaultServiceRequest($accessToken);
ServiceRequestFactory::setInstance($serviceRequest);
$spreadsheetService = new SpreadsheetService();
$spreadsheetFeed = $spreadsheetService->getSpreadsheets();
echo '<pre>'; print_r($spreadsheetFeed);
foreach($spreadsheetFeed as $item) {
$spreadsheets[basename($item->getId())] = $item->getTitle();
}
$this->data->spreadsheets = isset($spreadsheets) ? $spreadsheets : false;
}
/**
* Insert new file.
*
* @param string $title Title of the file to insert, including the extension.
* @param string $description Description of the file to insert.
* @param string $parentId Parent folder's ID.
* @param string $mimeType MIME type of the file to insert.
* @param string $filename Filename of the file to insert.
* @return Google_DriveFile The file that was inserted. NULL is returned if an API error occurred.
*/
public function insertFile($title, $description, $mimeType='application/vnd.google-apps.spreadsheet', $filename=null, $parentId=null) {
//Google_DriveService $service Drive API service instance.
$service = new Google_Service_Drive($this->getClient());
$file = new Google_Service_Drive_DriveFile();
$file->setTitle($title);
$file->setDescription($description);
$file->setMimeType($mimeType);
// Set the parent folder.
if ($parentId != null) {
$parent = new Google_ParentReference();
$parent->setId($parentId);
$file->setParents(array($parent));
}
try {
if(!empty($filename)){
$data = file_get_contents($filename);
}else{
$data = '';
}
$createdFile = $service->files->insert($file, array(
'data' => $data,
'mimeType' => $mimeType,
));
// Uncomment the following line to print the File ID
// print 'File ID: %s' % $createdFile->getId();
return $createdFile;
} catch (Exception $e) {
$this->setError($e->getMessage());
print "An error occurred: " . $e->getMessage();
}
}
/**
* Sets a default value if not alreay assigned
* @param string $property The name of the property.
* @param mixed $default The default value.
* @return mixed
*/
public function def($property, $default = null)
{
$value = $this->get($property, $default);
return $this->set($property, $value);
}
/**
* Returns a property of the object or the default value if the property is not set.
* @param string $property The name of the property.
* @param mixed $default The default value.
* @return mixed The value of the property.
*/
public function get($property, $default = null)
{
if (isset($this->$property))
{
return $this->$property;
}
return $default;
}
/**
* Returns an associative array of object properties.
* @param boolean $public If true, returns only the public properties.
* @return array
*/
public function getProperties($public = true)
{
$vars = get_object_vars($this);
if ($public)
{
foreach ($vars as $key => $value)
{
if ('_' == substr($key, 0, 1))
{
unset($vars[$key]);
}
}
}
return $vars;
}
/**
* Get the most recent error message.
* @param integer $i Option error index.
* @param boolean $toString Indicates if JError objects should return their error message.
* @return string Error message
*/
public function getError($i = null, $toString = true)
{
// Find the error
if ($i === null)
{
// Default, return the last message
$error = end($this->_errors);
}
elseif (!array_key_exists($i, $this->_errors))
{
// If $i has been specified but does not exist, return false
return false;
}
else
{
$error = $this->_errors[$i];
}
// Check if only the string is requested
if ($error instanceof Exception && $toString)
{
return (string) $error;
}
return $error;
}
/**
* Return all errors, if any.
* @return array Array of error messages or JErrors.
*/
public function getErrors()
{
return $this->_errors;
}
/**
* Modifies a property of the object, creating it if it does not already exist.
* @param string $property The name of the property.
* @param mixed $value The value of the property to set.
* @return mixed Previous value of the property.
*/
public function set($property, $value = null)
{
$previous = isset($this->$property) ? $this->$property : null;
$this->$property = $value;
return $previous;
}
/**
* Set the object properties based on a named array/hash.
* @param mixed $properties Either an associative array or another object.
* @return boolean
*/
public function setProperties($properties)
{
if (is_array($properties) || is_object($properties))
{
foreach ((array) $properties as $k => $v)
{
// Use the set function which might be overridden.
$this->set($k, $v);
}
return true;
}
return false;
}
/**
* Add an error message.
* @param string $error Error message.
* @return void
*/
public function setError($error)
{
array_push($this->_errors, $error);
}
}
$opt = array();
$opt['key_file_location'] = realpath(dirname(__FILE__)).'/key_ss.p12';
$opt['client_id'] = '*********.apps.googleusercontent.com';
$opt['service_account_name']= '*********@developer.gserviceaccount.com';
$opt['aplication_name'] = 'MY APPLICATION NAME';
$GoogleSS = new GoogleSpreadSheet($opt);
$GoogleSS->insertFile('test', 'description');
$GoogleSS->getSpreadsheetsList();
I also have a modified version of /google-api-php-client/autoload.php:
<?php
function google_api_php_client_autoload($className) {
$classPath = explode('_', $className);
if ($classPath[0] != 'Google') {
return;
}
if (count($classPath) > 3) {
// Maximum class file path depth in this project is 3.
$classPath = array_slice($classPath, 0, 3);
}
$filePath = dirname(__FILE__) . '/src/' . implode('/', $classPath) . '.php';
if (file_exists($filePath)) {
require_once($filePath);
}
}
function google_spreadsheet_api_php_client_autoload($className) {
$classPath = explode('\\', $className);
if (count($classPath) == 1) {
$filePath = dirname(__FILE__) . '/src/Google/Spreadsheet/' . $className . '.php';
}else{
$filePath = dirname(__FILE__) . '/src/' . implode('/', $classPath) . '.php';
}
if (file_exists($filePath)) {
require_once($filePath);
}
}
spl_autoload_register('google_api_php_client_autoload');
spl_autoload_register('google_spreadsheet_api_php_client_autoload');
When I execute the code I get:
Notice: Undefined index: uploadType in /var/www/google-api-php-client/src/Google/Service/Resource.php on line 175
Notice: Undefined index: uploadType in /var/www/google-api-php-client/src/Google/Service/Resource.php on line 175
An error occurred: Error calling POST https://www.googleapis.com/drive/v2/files: (401) Login Required
Google\Spreadsheet\SpreadsheetFeed Object
( .... )
In order to reproduce the problem you will also need to download php-google-spreadsheet-client and upload the the Spreadsheet directory to /google-api-php-client/src/Google/
I've tried to provide as much details as possible, but please let me know if something is unclear.
回答1:
I think your problem is access token something like this : setAccessToken($accessToken) , check your credentials.
Also i have added google drive upload script. I am using this script.
Regards.
<?php
/*******************************************************************************
* file: google_drive_upload.php
* author: ben servoz
* description: script to upload files to Google Drive
* notes: instructions at http://ben.akrin.com/?p=2080
*******************************************************************************/
// configuration
// google oauth
$client_id = "your client ID here" ;
$client_secret = "your client secret here" ;
$refresh_token = "your refresh token here" ;
// chunk size (this HAS to be a multiple of 256KB so only change the last integer)
$chunk_size = 256 * 1024 * 400 ; // this will upload files 100MB at a time
// miscellaneous
$verbose = true ;
$file_binary = "/usr/bin/file" ; // used for detecting mime type
// md5
$check_md5_after_upload = true ;
$md5sum_binary = "/usr/bin/md5sum" ;
// todo: grant URL follow thingy if it hasn't been done already
if( count($argv)<2 || count($argv)>3 || in_array("-h", $argv) || in_array("--help", $argv) ) {
echo "usage: {$argv[0]} <file_name> [folder_id]\n\n where <file_name> is the full path to the file that you want to upload to Google Drive.\n and [folder_id] is the the folder where you want to upload the file (optional, defaults to root)\n\n" ;
exit( 1 ) ;
}
$file_name = $argv[1] ;
if( !file_exists($file_name) ) {
echo "ERROR: {$file_name} is not found on the filesystem\n" ;
exit( 1 ) ;
}
$mime_type = get_mime_type( $file_name ) ;
if( $verbose ) { echo " > mime type detected: {$mime_type}\n" ; }
$folder_id = "" ;
if( count($argv)>2 ) {
$folder_id = $argv[2] ;
}
// retrieving current access token
$access_token = get_access_token() ;
// we create the file that we will be uploading to with google
if( $verbose ) { echo "> creating file with Google\n" ; }
$location = create_google_file( $file_name ) ;
$file_size = filesize( $file_name ) ;
if( $verbose ) { echo "> uploading {$file_name} to {$location}\n" ; }
if( $verbose ) { echo "> file size: " . (string)($file_size / pow(1024, 2)) . "MB\n" ; }
if( $verbose ) { echo "> chunk size: " . (string)($chunk_size / pow(1024, 2)) . "MB\n\n" ; }
$last_response_code = false ;
$final_output = null ;
$last_range = false ;
$transaction_counter = 0 ;
$average_upload_speed = 0 ;
$do_exponential_backoff = false ;
$exponential_backoff_counter = 0 ;
while( $last_response_code===false || $last_response_code=='308' ) {
$transaction_counter++ ;
if( $verbose ) { echo "> request {$transaction_counter}\n" ; }
if( $do_exponential_backoff ) {
$sleep_for = pow( 2, $exponential_backoff_counter ) ;
if( $verbose ) { echo "> exponential backoff kicked in, sleeping for {$sleep_for} and a bit\n" ; }
sleep( $sleep_for ) ;
usleep( rand(0, 1000) ) ;
$exponential_backoff_counter++ ;
if( $exponential_backoff_counter>5 ) {
// we've waited too long as per Google's instructions
echo "ERROR: reached time limit of exponential backoff\n" ;
exit( 1 ) ;
}
}
// determining what range is next
$range_start = 0 ;
$range_end = min( $chunk_size, $file_size - 1 ) ;
if( $last_range!==false ) {
$last_range = explode( '-', $last_range ) ;
$range_start = (int)$last_range[1] + 1 ;
$range_end = min( $range_start + $chunk_size, $file_size - 1 ) ;
}
if( $verbose ) { echo "> range {$range_start}-{$range_end}/{$file_size}\n" ; }
$ch = curl_init() ;
curl_setopt( $ch, CURLOPT_URL, "{$location}" ) ;
curl_setopt( $ch, CURLOPT_PORT , 443 ) ;
curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, "PUT" ) ;
curl_setopt( $ch, CURLOPT_BINARYTRANSFER, 1 ) ;
// grabbing the data to send
$to_send = file_get_contents( $file_name, false, NULL, $range_start, ($range_end - $range_start + 1) ) ;
curl_setopt( $ch, CURLOPT_POSTFIELDS, $to_send ) ;
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1) ;
curl_setopt( $ch, CURLOPT_HEADER, true ) ;
curl_setopt( $ch, CURLOPT_HTTPHEADER, array("Authorization: Bearer {$access_token}",
"Content-Length: " . (string)($range_end - $range_start + 1),
"Content-Type: {$mime_type}",
"Content-Range: bytes {$range_start}-{$range_end}/{$file_size}") ) ;
$response = parse_response( curl_exec($ch) ) ;
$post_transaction_info = curl_getinfo( $ch ) ;
curl_close( $ch ) ;
$do_exponential_backoff = false ;
if( isset($response['code']) ) {
// checking for expired credentials
if( $response['code']=="401" ) { // todo: make sure that we also got an invalid credential response
if( $verbose ) { echo "> access token expired, getting a new one\n" ; }
$access_token = get_access_token( true ) ;
$last_response_code = false ;
} else if( $response['code']=="308" ) {
$last_response_code = $response['code'] ;
$last_range = $response['headers']['range'] ;
// todo: verify x-range-md5 header to be sure, although I can't seem to find what x-range-md5 is a hash of exactly...
$exponential_backoff_counter = 0 ;
} else if( $response['code']=="503" ) { // Google's letting us know we should retry
$do_exponential_backoff = true ;
$last_response_code = false ;
} else if( $response['code']=="200" ) { // we are done!
$last_response_code = $response['code'] ;
$final_output = $response ;
} else {
echo "ERROR: I have no idea what to do so here's a variable dump & have fun figuring it out.\n" ;
echo "post_transaction_info\n" ;
print_r( $post_transaction_info ) ;
echo "response\n" ;
print_r( $response ) ;
exit( 1 ) ;
}
$average_upload_speed += (int)$post_transaction_info['speed_upload'] ;
if( $verbose ) { echo "> uploaded {$post_transaction_info['size_upload']}B\n" ; }
} else {
$do_exponential_backoff = true ;
$last_response_code = false ;
}
}
if( $last_response_code!="200" ) {
echo "ERROR: there's no way we should reach this point\n" ;
exit( 1 ) ;
}
if( $verbose ) { echo "\n> all done!\n" ; }
$average_upload_speed /= $transaction_counter ;
if( $verbose ) { echo "\n> average upload speed: " . (string)($average_upload_speed / pow(1024, 2)) . "MB/s\n" ; }
$final_output =json_decode( $final_output['body'] ) ;
if( $check_md5_after_upload ) {
if( $verbose ) { echo "> md5 hash verification " ; }
$result = exec( "{$md5sum_binary} {$file_name}" ) ;
$result = trim( $result ) ;
$result = explode( " ", $result ) ;
$result = $result[0] ;
if( $result!=$final_output->md5Checksum ) {
if( $verbose ) { echo "FAIL\n" ; }
echo "ERROR: md5 mismatch; local:{$result}, google:{$final_output->md5Checksum}\n" ;
exit( 1 ) ;
} else {
if( $verbose ) { echo "OK\n" ; }
}
}
echo $final_output->selfLink, "\n" ;
// we made it
exit( 0 ) ;
function get_mime_type( $file_name ) {
global $file_binary ;
$result = exec( "{$file_binary} -i -b {$file_name}" ) ;
$result = trim( $result ) ;
$result = explode( ";", $result ) ;
$result = $result[0] ;
return $result ;
}
function parse_response( $raw_data ) {
$parsed_response = array( 'code'=>-1, 'headers'=>array(), 'body'=>"" ) ;
$raw_data = explode( "\r\n", $raw_data ) ;
$parsed_response['code'] = explode( " ", $raw_data[0] ) ;
$parsed_response['code'] = $parsed_response['code'][1] ;
for( $i=1 ; $i<count($raw_data) ; $i++ ) {
$raw_datum = $raw_data[$i] ;
$raw_datum = trim( $raw_datum ) ;
if( $raw_datum!="" ) {
if( substr_count($raw_datum, ':')>=1 ) {
$raw_datum = explode( ':', $raw_datum, 2 ) ;
$parsed_response['headers'][strtolower($raw_datum[0])] = trim( $raw_datum[1] ) ;
} else {
echo "ERROR: we're in the headers section of parsing an HTTP section and no colon was found for line: {$raw_datum}\n" ;
exit( 1 ) ;
}
} else {
// we've moved to the body section
if( ($i+1)<count($raw_data) ) {
for( $j=($i+1) ; $j<count($raw_data) ; $j++ ) {
$parsed_response['body'] .= $raw_data[$j] . "\n" ;
}
}
// we don't need to continue the $i loop
break ;
}
}
return $parsed_response ;
}
function get_access_token( $force_refresh=false ) {
global $client_id, $client_secret, $refresh_token, $verbose ;
if( $verbose ) { echo "> retrieving access token\n" ; }
$token_filename = "/tmp/access_token_" . md5( $client_id . $client_secret . $refresh_token ) ;
$access_token = "" ;
if( !file_exists($token_filename) || $force_refresh===true ) {
// no previous access token, let's get one
if( $verbose ) { echo "> getting new one\n" ; }
$ch = curl_init() ;
curl_setopt( $ch, CURLOPT_URL, "https://accounts.google.com/o/oauth2/token" ) ;
curl_setopt( $ch, CURLOPT_PORT , 443 ) ;
curl_setopt( $ch, CURLOPT_POST, 1 ) ;
curl_setopt( $ch, CURLOPT_POSTFIELDS, "client_id={$client_id}&client_secret={$client_secret}&refresh_token={$refresh_token}&grant_type=refresh_token" ) ;
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ) ;
curl_setopt( $ch, CURLOPT_HEADER, true ) ;
curl_setopt( $ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded') ) ;
$response = curl_exec( $ch ) ;
$response = parse_response( $response ) ;
// todo: make sure that we got a valid response before retrieving the access token from it
$access_token = json_decode( $response['body'] ) ;
$access_token = $access_token->access_token ;
file_put_contents( $token_filename, $access_token ) ;
} else {
// we already have something cached, with some luck it's still valid
$access_token = file_get_contents( $token_filename ) ;
if( $verbose ) { echo "> from cache\n" ; }
}
if( $access_token=="" ) {
echo "ERROR: problems getting an access token\n" ;
exit( 1 ) ;
}
return $access_token ;
}
function create_google_file ( $file_name ) {
global $access_token, $folder_id, $mime_type ;
// todo: make mimeType universal
if( $folder_id=="" ) {
$post_fields = "{\"title\":\"{$file_name}\",
\"mimeType\":\"{$mime_type}\"}" ;
} else {
$post_fields = "{\"title\":\"{$file_name}\",
\"mimeType\":\"{$mime_type}\",
\"parents\": [{\"kind\":\"drive#fileLink\",\"id\":\"{$folder_id}\"}]}" ;
}
$ch = curl_init() ;
curl_setopt( $ch, CURLOPT_URL, "https://www.googleapis.com/upload/drive/v2/files?uploadType=resumable" ) ;
curl_setopt( $ch, CURLOPT_PORT , 443 ) ;
curl_setopt( $ch, CURLOPT_POST, 1 ) ;
curl_setopt( $ch, CURLOPT_POSTFIELDS, $post_fields ) ;
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ) ;
curl_setopt( $ch, CURLOPT_HEADER, true ) ;
curl_setopt( $ch, CURLOPT_HTTPHEADER, array("Authorization: Bearer {$access_token}",
"Content-Length: " . strlen($post_fields),
"X-Upload-Content-Type: {$mime_type}",
"X-Upload-Content-Length: " . filesize($file_name),
"Content-Type: application/json; charset=UTF-8") ) ;
$response = curl_exec( $ch ) ;
$response = parse_response( $response ) ;
// access token expired, let's get a new one and try again
if( $response['code']=="401" ) { // todo: make sure that we also got an invalid credential response
$access_token = get_access_token( true ) ;
return create_google_file( $file_name ) ; // todo: implement recursion depth limit so we don't rescurse to hell
}
// error checking
if( $response['code']!="200" ) {
echo "ERROR: could not create resumable file\n" ;
print_r( $response ) ;
exit( 1 ) ;
}
if( !isset($response['headers']['location']) ) {
echo "ERROR: not location header gotten back\n" ;
print_r( $response ) ;
exit( 1 ) ;
}
return $response['headers']['location'] ;
}
?>
回答2:
You get a 401 error. So, you have a problem with your credentials. Look at the errors documentation for more details. Maybe your token has been expired, or it is invalid.
来源:https://stackoverflow.com/questions/27133141/how-to-upload-a-file-to-google-drive-using-service-account-in-php