I want to read draft grade marks from Google Classroom using API for a project. But I can't find out the draft grade. Already I've added some code to the quickstart.php file:
require __DIR__ . '/vendor/autoload.php';
// if (php_sapi_name() != 'cli') {
// throw new Exception('This application must be run on the command line.');
// }
/**
* Returns an authorized API client.
* #return Google_Client the authorized client object
*/
function getClient()
{
$client = new Google_Client();
$client->setApplicationName('Google Classroom API PHP Quickstart');
$client->setScopes(array(
Google_Service_Classroom::CLASSROOM_COURSES,
Google_Service_Classroom::CLASSROOM_STUDENT_SUBMISSIONS_STUDENTS_READONLY,
Google_Service_Classroom::CLASSROOM_ROSTERS)
);
$client->setAuthConfig(__DIR__ .'/credentials.json');
$client->setAccessType('offline');
$client->setPrompt('select_account consent');
// Load previously authorized token from a file, if it exists.
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
$tokenPath = 'token.json';
if (file_exists($tokenPath)) {
$accessToken = json_decode(file_get_contents($tokenPath), true);
$client->setAccessToken($accessToken);
}
// If there is no previous token or it's expired.
if ($client->isAccessTokenExpired()) {
// Refresh the token if possible, else fetch a new one.
if ($client->getRefreshToken()) {
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
} else {
// Request authorization from the user.
$authUrl = $client->createAuthUrl();
printf("Open the following link in your browser:\n%s\n", $authUrl);
print 'Enter verification code: ';
$authCode = trim(fgets(STDIN));
// Exchange authorization code for an access token.
$accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
$client->setAccessToken($accessToken);
// Check to see if there was an error.
if (array_key_exists('error', $accessToken)) {
throw new Exception(join(', ', $accessToken));
}
}
// Save the token to a file.
if (!file_exists(dirname($tokenPath))) {
mkdir(dirname($tokenPath), 0700, true);
}
file_put_contents($tokenPath, json_encode($client->getAccessToken()));
}
return $client;
}
$optParams = array(
'pageSize' => 1000
);
// Copyright 2021 Google LLC.
// SPDX-License-Identifier: Apache-2.0
// Get the API client and construct the service object.
$client = getClient();
$service = new Google_Service_Classroom($client);
// set these parameters:
// 328776504166 <- It is my course id
// 339429593407 <- It is my course work id
$courseId = "328776504166";
$courseWorkId = "339429593407";
$results = $service->courses_courseWork_studentSubmissions->listCoursesCourseWorkStudentSubmissions($courseId, $courseWorkId);
foreach ($results->studentSubmissions as $r => $submission) {
$student = $service->courses_students->get($courseId, $submission->userId);
$studentName = $student->profile->name->fullName;
print("<br>Student Name: ".$studentName . ": ");
print("<br>Draft Grade: ".$submission->draftGrade. "\n");
print("<br>Course Work Id: ".$submission->courseWorkId. "\n");
echo '<pre>';
print_r($submission);
}
Then when I run quickstart.php at localhost the following problems can be seen:
Fatal error: Uncaught Google_Service_Exception: { "error": { "code": 400, "message": "Precondition check failed.", "errors": [ { "message": "Precondition check failed.", "domain": "global", "reason": "failedPrecondition" } ], "status": "FAILED_PRECONDITION" } } in C:\xampp\htdocs\api\vendor\google\apiclient\src\Google\Http\REST.php:118 Stack trace: #0
I can't find my wrong. How to solve this problem? please give me some suggestions.
As stated in the documentation:
Students cannot see draft grades
This is why a student who intents to retrieve a draft grade will obtain a
403 - "Insufficient Permission" error.
Related
I want to obtain users' phone numbers via Google sign-in on my website. In JavaScript for the "sign in with Google" button, I'm including scope 'https://www.googleapis.com/auth/user.phonenumbers.read' for permission to read the user's phone number. Maybe instead of this scope, I need to use 'https://www.googleapis.com/auth/contacts.readonly'. In any case, how do I obtain a signed-in user's phone number in PHP or JavaScript? When a user clicks on the sign-in button then because of the scope Google does ask permission to share a phone number. In Google API Console -> Edit app registration -> Scopes, I've included this phone number scope. Also, I've enabled People API in the Google project. I've installed
composer require google/apiclient
From the front end i'm receiving id-token for the signed-in user. My PHP is:
<?php
require_once 'vendor/autoload.php';
$id_token = $_POST['idtoken'];
$client = new Google_Client(['client_id' => '349001386451-bpovja3t7soabdu3cbhnig12fqlr20o0.apps.googleusercontent.com']);
$payload = $client->verifyIdToken($id_token);
if ($payload) {
$userid = $payload['sub'];
echo "Userid: $userid";
} else {
echo "Invalid ID token";
}
( The above code has been edited from https://developers.google.com/identity/sign-in/web/backend-auth )
I'm a newbie to this. I've got my client-id, client-secret and user's id-token. I'm able to show the userid in the above code, how to display the phone number?
Edit: I downloaded my client_secret.json and tried another method:
index.php
<?php
require_once __DIR__.'/vendor/autoload.php';
session_start();
$client = new Google\Client();
$client->setAuthConfig('client_secret.json');
$client->setScopes(array('https://www.googleapis.com/auth/user.phonenumbers.read', 'https://www.googleapis.com/auth/contacts.readonly', 'profile'));
if (isset($_SESSION['access_token']) && $_SESSION['access_token']) {
$client->setAccessToken($_SESSION['access_token']);
$service = new Google_Service_PeopleService( $client );
$optParams = ['personFields' => 'phoneNumbers'];
$profile = $service->people->get( 'people/me', $optParams );
var_export($profile);
var_export( $profile->getPhoneNumbers() );
} else {
$redirect_uri = 'https://' . $_SERVER['HTTP_HOST'] . '/testing/oauth2callback.php';
header('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL));
}
--
oauth2callback.php
<?php
require_once __DIR__.'/vendor/autoload.php';
session_start();
$client = new Google\Client();
$client->setAuthConfigFile('client_secret.json');
$client->setRedirectUri('https://' . $_SERVER['HTTP_HOST'] . '/testing/oauth2callback.php');
$client->addScope(Google_Service_PeopleService::USER_PHONENUMBERS_READ);
if (! isset($_GET['code'])) {
$auth_url = $client->createAuthUrl();
header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL));
} else {
$client->authenticate($_GET['code']);
$_SESSION['access_token'] = $client->getAccessToken();
$redirect_uri = 'https://' . $_SERVER['HTTP_HOST'] . '/testing/';
header('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL));
}
But when I'm running index.php it's giving error:
"error": { "code": 403, "message": "The caller does not have
permission to request "people/me". Request requires one of the
following scopes: [profile]."
But I do have included the profile scope in index.php
The phone number used for password reset will not be possible access:
It has been determined that we will not return the account recovery
phone number. The account recovery phone number is only intended for
specific usage like recovery the account when locked out. In the
interest of protecting user privacy this will not be returned in the
3rd party API.
I'm successfully getting phone number using a new 3rd method as given here:
https://developers.google.com/people/api/rest/v1/people/get?apix=true&apix_params=%7B%22resourceName%22%3A%22people%2Fme%22%2C%22personFields%22%3A%22phoneNumbers%22%7D
I copied the JavaScript code given in this link, removed all scopes except one, replaced YOUR_API_KEY and YOUR_CLIENT_ID, ran it on my server, in Firefox and it worked!
<script src="https://apis.google.com/js/api.js"></script>
<script>
/**
* Sample JavaScript code for people.people.get
* See instructions for running APIs Explorer code samples locally:
* https://developers.google.com/explorer-help/guides/code_samples#javascript
*/
function authenticate() {
return gapi.auth2.getAuthInstance()
.signIn({scope: "https://www.googleapis.com/auth/user.phonenumbers.read"})
.then(function() { console.log("Sign-in successful"); },
function(err) { console.error("Error signing in", err); });
}
function loadClient() {
gapi.client.setApiKey("YOUR_API_KEY");
return gapi.client.load("https://people.googleapis.com/$discovery/rest?version=v1")
.then(function() { console.log("GAPI client loaded for API"); },
function(err) { console.error("Error loading GAPI client for API", err); });
}
// Make sure the client is loaded and sign-in is complete before calling this method.
function execute() {
return gapi.client.people.people.get({
"resourceName": "people/me",
"personFields": "phoneNumbers"
})
.then(function(response) {
// Handle the results here (response.result has the parsed body).
console.log("Response", response);
},
function(err) { console.error("Execute error", err); });
}
gapi.load("client:auth2", function() {
gapi.auth2.init({client_id: "YOUR_CLIENT_ID"});
});
</script>
<button onclick="authenticate().then(loadClient)">authorize and load</button>
<button onclick="execute()">execute</button>
But it only reads the phone number(s) added in Google account's "About me": https://myaccount.google.com/profile
And not the phone number of Google account which is used for password reset. I actually want this number but don't know whether possible.
I've been searching for this for hours, I hope someone here can help.
I built a subscription based site on Laravel and PayPal subscriptions using the PayPal PHP SDK.
Everything works perfectly except on thing:
I created a webhook for when a user cancels the payment on his end. I'm getting this error:
Got Http response code 400 when accessing https://api.sandbox.paypal.com/v1/notifications/verify-webhook-signature.{"name":"VALIDATION_ERROR","message":"Invalid data provided","debug_id":"7225cebfec35a","information_link":"https://developer.paypal.com/docs/api/webhooks/#errors","details":[{"field":"webhook_id","location":"body","issue":"Required field cannot be blank"}],"links":[]}
Here is my code:
public function webhook()
{
/**
* Receive the entire body that you received from PayPal webhook.
*/
$bodyReceived = file_get_contents('php://input');
// Receive HTTP headers that you received from PayPal webhook.
$headers = getallheaders();
/**
* Uppercase all the headers for consistency
*/
$headers = array_change_key_case($headers, CASE_UPPER);
$signatureVerification = new \PayPal\Api\VerifyWebhookSignature();
$signatureVerification->setWebhookId(env('PAYPAL_WEBHOOK_ID'));
$signatureVerification->setAuthAlgo($headers['PAYPAL-AUTH-ALGO']);
$signatureVerification->setTransmissionId($headers['PAYPAL-TRANSMISSION-ID']);
$signatureVerification->setCertUrl($headers['PAYPAL-CERT-URL']);
$signatureVerification->setTransmissionSig($headers['PAYPAL-TRANSMISSION-SIG']);
$signatureVerification->setTransmissionTime($headers['PAYPAL-TRANSMISSION-TIME']);
$webhookEvent = new \PayPal\Api\WebhookEvent();
$webhookEvent->fromJson($bodyReceived);
$signatureVerification->setWebhookEvent($webhookEvent);
$request = clone $signatureVerification;
try {
$output = $signatureVerification->post($this->apiContext);
} catch(\Exception $ex) {
//This is where it fails
print_r($ex->getMessage());
exit(1);
}
$verificationStatus = $output->getVerificationStatus();
$responseArray = json_decode($request->toJSON(), true);
$event = $responseArray['webhook_event']['event_type'];
if ($verificationStatus == 'SUCCESS')
{
switch($event)
{
case 'BILLING.SUBSCRIPTION.CANCELLED':
case 'BILLING.SUBSCRIPTION.SUSPENDED':
case 'BILLING.SUBSCRIPTION.EXPIRED':
case 'BILLING_AGREEMENTS.AGREEMENT.CANCELLED':
// $user = User::where('payer_id',$responseArray['webhook_event']['resource']['payer']['payer_info']['payer_id'])->first();
$this->updateStatus($responseArray['webhook_event']['resource']['payer']['payer_info']['payer_id'], 0,1);
break;
}
}
echo $verificationStatus;
exit(0);
}
And here is the $this->apiContext:
trait PayPalApiCredentialsTrait {
private $apiContext;
public function setCredentials()
{
$this->apiContext = new \PayPal\Rest\ApiContext(
new \PayPal\Auth\OAuthTokenCredential(
env('PAYPAL_CLIENT_ID'), // ClientID
env('PAYPAL_CLIENT_SECRET') // ClientSecret
)
);
$this->apiContext->setConfig(
array(
'mode' => env("PAYPAL_MODE"),
'log.LogEnabled' => true,
'log.FileName' => '../PayPal.log',
'log.LogLevel' => 'INFO', // PLEASE USE `INFO` LEVEL FOR LOGGING IN LIVE ENVIRONMENTS
)
);
}
}
This is the error I get from the paypal.log:
[01-09-2020 15:54:18] PayPal\Core\PayPalHttpConnection : INFO: POST https://api.sandbox.paypal.com/v1/oauth2/token
[01-09-2020 15:54:18] PayPal\Core\PayPalHttpConnection : INFO: Response Status : 200
[01-09-2020 15:54:18] PayPal\Core\PayPalHttpConnection : INFO: POST https://api.sandbox.paypal.com/v1/notifications/verify-webhook-signature
[01-09-2020 15:54:19] PayPal\Core\PayPalHttpConnection : INFO: Response Status : 400
[01-09-2020 15:54:19] PayPal\Core\PayPalHttpConnection : ERROR: Got Http response code 400 when accessing https://api.sandbox.paypal.com/v1/notifications/verify-webhook-signature. {"name":"VALIDATION_ERROR","message":"Invalid data provided","debug_id":"26b12ee43cddd","information_link":"https://developer.paypal.com/docs/api/webhooks/#errors","details":[{"field":"webhook_id","location":"body","issue":"Required field cannot be blank"}],"links":[]}
I must mention that everything else works fine.
Creating plans, agreements, cancelling the both, showing active plans, and more...
Everything works smoothly.
This is the only thing that I can't seem to fix.
If anyone could figure this out for me, I'd really appreciate it.
Thank you!
The PayPal-PHP-SDK is deprecated and no longer maintained; it should not be used for new integrations. Its implementation of billing plans and subscriptions is old, and not compatible with the current version of the Subscriptions API.
In its place, a direct HTTPS integration (no SDK) should be used for Subscriptions, and for verifying webhooks.
(For the one time payment use case, there is a new v2 Checkout-PHP-SDK https://developer.paypal.com/docs/api/rest-sdks/ )
I'm using stripe in React and processing the charge through an AJAX call.
I've tried to strip down the Stripe.card.createToken function to the bare essentials for this question.
The if else statement checks if the response has any errors.
I use the 4100000000000019 card number to ensure a card declined error but the else statement(the successful charge) fires regardless which card number is entered.
Entering the 4100000000000019 card number results in a blocked charge in the Stripe dashboard. An error definitely gets generated:
{
"error": {
"message": "Your card was declined.",
"type": "card_error",
"code": "card_declined",
"decline_code": "generic_decline",
"charge": "ch_19gMBaIWHxnqld7LCdbCtdNz"
}
}
But the if(response.error) is ignored and runs the else statement.
Stripe.card.createToken({
number: $('.card-number').val(),
cvc: $('.card-cvc').val(),
exp_month: $('.card-expiry-month').val(),
exp_year: $('.card-expiry-year').val(),
name: $('.first-name').val()
}, function(status, response){
if (response.error) {
this.reportError(response.error.message);
} else { // No errors, submit the form.
var token = response.id;
$.ajax({
type: 'POST',
url: 'components/charge.php',
data : {
stripeToken: token
},
success: function(data,response) {
paymentSuccessful();
},
error: function(data,textStatus) {
console.log("Ajax Error!");
}
});//$.ajax
}//else
});//Stripe.card.createToken
Any help is much appreciated.
Moe
UPDATE: Thanks to this awesome tutorial by Larry Ullman and it's section on stripe error handling, I came up with a fairly good solution.
http://www.larryullman.com/2013/01/30/handling-stripe-errors/
So I added the if else statment inside the AJAX success function.
success: function(data,response) {
if(data == "success"){
$('#payment-error-copy').text("Your payment was successful. Thank you for ordering!");
paymentSuccessful();
} else {
$('#payment-error-copy').text(data);
}
};//success function
Inside the charge we can return the error response and the precise reason for the error.
my charge.php file
<?php
require_once('vendor/autoload.php');
// Get the payment token submitted by the form:
$token = $_POST['stripeToken'];
try{
\Stripe\Stripe::setApiKey("<secret_KEY>");
$customer = \Stripe\Customer::create(array(
"source" => $token,
"email" => $email
)
);
// Charge the Customer instead of the card
\Stripe\Charge::create(array(
"amount" => $price, // amount in cents, again
"currency" => "aud",
"description" => $email." ".$first_name,
"customer" => $customer->id
)
);
echo "success";
} catch (\Stripe\Error\ApiConnection $e) {
// Network problem, perhaps try again.
$e_json = $e->getJsonBody();
$error = $e_json['error'];
echo "Sorry, your charge couldn't be processed. Reason: ".$error['message'];
} catch (\Stripe\Error\InvalidRequest $e) {
// You screwed up in your programming. Shouldn't happen!
$e_json = $e->getJsonBody();
$error = $e_json['error'];
echo "Sorry, your charge couldn't be processed. Reason: ".$error['message'];
} catch (\Stripe\Error\Api $e) {
// Stripe's servers are down!
$e_json = $e->getJsonBody();
$error = $e_json['error'];
echo "Sorry, your charge couldn't be processed. Reason: ".$error['message'];
} catch (\Stripe\Error\Card $e) {
// Card was declined.
$e_json = $e->getJsonBody();
$error = $e_json['error'];
echo "Sorry, your charge couldn't be processed. Reason: ".$error['message'];
}
?>
The try catch statement returns a precise message about the charge error and returns it to our success function, so if the charge is anything other than success it runs the error function.
Although my question remains unanswered, the build works as expected and still remains safe for the user to enter their billing info.
Thanks to the other SO users for their input, it is really appreciated.
I think this particular card number only gives an error when Stripe actually tries to use it.
Just built a change-card-function for my SaaS and noticed that when I update my StripeCustomer default source with the token received, it fails.
I would like to read Google Docs and Google Sheets shared by users with a specific user (myapp) created by me for my application. I have implemented the Google hybrid server slide flow (offline access) yo use Google services on behalf of this user when he is offline.
I store the refresh token in my database and use it to refresh the access token. With the access token I can query the API. For example, the following code correctly returns the files on the "myapp" drive:
// Get the API client
$client = new Google_Client();
$client->setClientId($this->clientId);
$client->setClientSecret($this->clientSecret);
$client->setAccessType('offline');
...
$client->addScope([
'https://spreadsheets.google.com/feeds',
'https://docs.google.com/feeds',
Google_Service_Drive::DRIVE
]);
// Construct the service object
$service = new Google_Service_Drive($client);
$params = array(
'pageSize' => 10,
'fields' => "nextPageToken, files(id, name)"
);
$results = $service->files->listFiles($params);
foreach ($results->getFiles() as $file) {
printf("%s (%s)\n", $file->getName()); // OK
}
...works fine!
Some of the files are shared by other users to "myapp".
Now I would like to get content of a shared Spreadsheet:
$fileId = "1GRTldB2....";
$result = $service->files->get($fileId, [
'fields' => 'name,md5Checksum,size,createdTime,modifiedTime,ownedByMe,properties,shared,sharedWithMeTime,webContentLink,webViewLink'
]);
$url = $result['webViewLink'];
//$url = 'https://www.googleapis.com/drive/v3/files/'.$fileId.'?alt=media';
$method = 'GET';
$headers = ["Authorization" => "Bearer $accessToken", "GData-Version" => "3.0"];
$httpClient = new GuzzleHttp\Client(['headers' => $headers]);
$resp = $httpClient->request($method, $url);
$body = $resp->getBody()->getContents();
$code = $resp->getStatusCode();
$reason = $resp->getReasonPhrase();
echo "$code : $reason\n\n";
echo "$body\n";
This code gives an error:
Fatal error: Uncaught exception 'GuzzleHttp\Exception\ClientException'
with message ' in
C:\wamp\www\core\vendor\guzzlehttp\guzzle\src\Exception\RequestException.php
on line 107 ( ! ) GuzzleHttp\Exception\ClientException: Client error:
GET
https://www.googleapis.com/drive/v3/files/1GRTldB2KDFGmFZgFST28-MaHKs7y7eqelbzDpdxuJBg?alt=media
resulted in a 401 Unauthorized response: { "error": { "errors": [ {
"domain": "global", "reason": "authError", "message": "Invalid
Credentials" (truncated...) in
C:\wamp\www\core\vendor\guzzlehttp\guzzle\src\Exception\RequestException.php
on line 107
authError / InvalidCredentials
Any ideas?
Is the Shared-Sheet ...being shared with the "user" that you are using on the Google api? You know when you right click on the actual document and say "Share..."
Does the contacts API still work for anyone? A while back I started getting 403 errors from the API. I run my backup script once a week. Every user that I can't back up, I retry every hour until the next week. Over the span of the week I will end up with a couple of contacts entries, but not a significant amount. This leads me to believe that my code still works since I get some contacts. Anyone have any insight?
Note: I also use the same code/framework to backup Google Drive and Google Calendars for my organization and have not had any issues.
function retrieveAllUserContacts($user)
{
$nextLink = "https://www.google.com/m8/feeds/contacts/$user/full";
$params = array('xoauth_requestor_id' => $user);
while($nextLink != '')
{
$header = array('GData-Version: 3.0');
$result = sendOAuthRequest('GET', $nextLink, $params, $header);
$params = array('xoauth_requestor_id' => $user);
$nextLink = '';
libxml_use_internal_errors(true);
$xmlObj = simplexml_load_string($result);
if($xmlObj === false)
{
echo "adding $user to retry list. Result : " . print_r($result, true) . "\n";
addUserToRetryList($user);
exit(1);
}
foreach($xmlObj->link as $link)
{
if($link['rel'] == 'next')
{
$nextLink = $link['href'];
}
if($nextLink != '')
{
$urlSplit = explode('?', $nextLink);
$nextLink = $urlSplit[0];
$urlParams = explode('&', $urlSplit[1]);
foreach($urlParams as $urlParam)
{
$urlParamSplit = explode('=', $urlParam);
$params[$urlParamSplit[0]] = $urlParamSplit[1];
}
break;
}
}
foreach($xmlObj->entry as $entry)
{//get contacts
...
}
function sendOAuthRequest($httpMethod, $url, $parameters, $header=array())
{
global $CONSUMER_KEY;
global $CONSUMER_SECRET;
$consumer = new OAuthConsumer($CONSUMER_KEY, $CONSUMER_SECRET, NULL);
$request = OAuthRequest::from_consumer_and_token($consumer, NULL, $httpMethod, $url, $parameters);
// Sign the constructed OAuth request using HMAC-SHA1
$request->sign_request(new OAuthSignatureMethod_HMAC_SHA1(), $consumer, NULL);
// Make signed OAuth request to the Contacts API server
if(count($parameters))
{
if(strpos($url, '?') === false)
{
$url .= '?' . implode_assoc('=', '&', $parameters);
}
else
{
$url .= '&' . implode_assoc('=', '&', $parameters);
}
}
$header[] = $request->to_header();
return send_request($request->get_normalized_http_method(), $url, $header);
}
Here is the OAuth Class code : http://pastebin.com/hH4SM9nn I can say that it works for Google Drive and Google Calendar APIs and also that this code worked for over a year without issue.
A number of problems could be causing this. Since your question isn't specific and gives no code sample, here are some of those potential problems:
You have not moved to HTTPS as opposed to HTTP
your GET request specifies a "default" user, where the authority is not always given
You are trying to get too many contacts
There are also issues with authorisation and permissions as of late, one "fix" as such is documented in this StackOverflow answer. It concerns adding not only a google account, but the gmail address associated with that account to the analytics service, via this link.