Google Picker
Integrating the Google Drive Picker
This guide explains how to implement a custom button that allows users to select a file from their Google Drive and save a shareable link into a form field. This is a powerful feature for linking external documents without uploading them to the server.
1. Configuration in Google Cloud & config.php
Before you can use the Google Drive Picker, you must configure your Google Cloud Project and add the credentials to the application's configuration file.
- Create a Google Cloud Project: Go to the Google Cloud Console, create a new project, and enable the "Google Picker API" and the "Google Drive API" for it.
- Create an API Key: In your project, go to "APIs & Services > Credentials" and create an "API Key".
- Create an OAuth 2.0 Client ID: In the same section, create an "OAuth 2.0 Client ID" of type "Web application".
- Set Keys in
config.php: Openconfig.phpand set the following constants:define('GOOGLE_API_KEY', 'YOUR_API_KEY'); define('GOOGLE_CLIENT_ID', 'YOUR_CLIENT_ID.apps.googleusercontent.com'); define('GOOGLE_APP_ID', 'YOUR_NUMERIC_PROJECT_ID'); - Configure Authorized Origins: In your OAuth 2.0 Client ID settings, add your application's domain to the "Authorized JavaScript origins" list (e.g.,
https://your-app.com). For local development, you must addhttp://localhost.
2. Implementation Steps
To use the picker, you need a target field in your table and a custom button to trigger the action.
Step 2.1: Prepare the Target Field
In your table definition (e.g., a "Documents" table), create a field to store the URL. A field of type Web (URL) is ideal. Let's assume its technical name is url_verknuepfung.
Step 2.2: Create the Custom Button
Go to Settings -> Custom Buttons and create a new button with the following properties:
- Button Name:
Select from Google Drive - URL / Script Path:
custom_buttons/google_drive_picker_action.php?target_field=url_verknuepfung - Display Context:
Record FormorBoth(to show it in the record edit form). - Icon:
fa-google-drive(from Font Awesome Brands) - Color:
btn-primary
The target_field parameter in the URL tells the script which form field to populate with the selected file's URL.
3. Code Explanation: google_drive_picker_action.php
When the custom button is clicked, it fetches and executes the content of google_drive_picker_action.php. This script is designed to run entirely on the client-side.
<?php
// ... Access control and config loading ...
// Get target field from URL
$target_field_name = htmlspecialchars($_GET['target_field'] ?? 'url_verknuepfung');
?>
<script>
// This function is called by the Google API script once it's loaded
function loadPicker() {
gapi.load('picker', onPickerApiLoad);
gapi.load('auth2', onAuthApiLoad);
}
function onAuthApiLoad() { /* Handles authentication */ }
function onPickerApiLoad() { /* Prepares the picker */ }
function pickerCallback(data) {
if (data.action === google.picker.Action.PICKED) {
var file = data.<?php
// ... Access control and config loading ...
// Get target field from URL and API keys from config
$target_field_name = htmlspecialchars($_GET['target_field'] ?? 'url_verknuepfung', ENT_QUOTES, 'UTF-8');
$api_key = defined('GOOGLE_API_KEY') ? GOOGLE_API_KEY : '';
$client_id = defined('GOOGLE_CLIENT_ID') ? GOOGLE_CLIENT_ID : '';
?>
<script>
(async () => {
const GOOGLE_API_KEY = "= $api_key ?>";
const GOOGLE_CLIENT_ID = "= $client_id ?>";
let tokenClient;
let accessToken;
// 1. Hilfsfunktion zum dynamischen Laden von Skripten
function loadScript(src, id) {
return new Promise((resolve, reject) => {
if (document.getElementById(id)) return resolve();
const script = document.createElement('script');
script.src = src;
script.id = id;
script.async = true;
script.defer = true;
script.onload = () => resolve();
script.onerror = () => reject(new Error(`Konnte Skript nicht laden: ${src}`));
document.body.appendChild(script);
});
}
// 2. Initialisiert die Google APIs (gapi für Picker, gsi für Auth)
async function loadGoogleApis() {
await loadScript('https://apis.google.com/js/api.js', 'google-api-script');
await new Promise(resolve => gapi.load('client:picker', resolve));
await gapi.client.load('https://www.googleapis.com/discovery/v1/apis/drive/v3/rest');
await loadScript('https://accounts.google.com/gsi/client', 'google-gsi-script');
tokenClient = google.accounts.oauth2.initTokenClient({
client_id: GOOGLE_CLIENT_ID,
scope: 'https://www.googleapis.com/auth/drive.readonly',
callback: (tokenResponse) => {
accessToken = tokenResponse.access_token;
createPicker(); // Picker wird nach erfolgreicher Auth angezeigt
},
});
}
// 3. Erstellt und zeigt den Picker an
function createPicker() {
const view = new google.picker.View(google.picker.ViewId.DOCS);
const picker = new google.picker.PickerBuilder()
.setOAuthToken(accessToken)
.setDeveloperKey(GOOGLE_API_KEY)
.addView(view)
.setCallback(pickerCallback)
.build();
picker.setVisible(true);
}
// 4. Callback nach Dateiauswahl
function pickerCallback(data) { /* ... (siehe Code in der PHP-Datei) ... */ }
// 5. Hauptlogik: APIs laden und dann Authentifizierung anfordern
try {
await loadGoogleApis();
tokenClient.requestAccessToken({ prompt: 'consent' });
} catch (error) {
showDialog({ title: 'Fehler', text: 'Der Google Drive Picker konnte nicht initialisiert werden.', okText: 'OK' });
}
})();
</script>
<?php
exit();
Key Concepts:
- Dynamic API Loading: Instead of relying on globally loaded scripts, this action dynamically injects the Google API (`gapi`) and Google Sign-In (`gsi`) scripts into the page when the button is clicked.
- Asynchronous Flow with `async/await`: The entire logic is wrapped in an `async` function. This ensures that the scripts are fully loaded and initialized (`await loadGoogleApis()`) before attempting to request an access token.
- Modern Authentication (GSI): The script uses the newer Google Identity Services (GSI) library to handle authentication. It initializes a `tokenClient` which manages the OAuth 2.0 flow. The picker is only created inside the `callback` after a valid token is received.
- Dynamic Target Field: The
$target_field_namefrom the button's URL is embedded into the JavaScript, making the script reusable for different fields. - Picker Callback: When a user picks a file, the
pickerCallbackfunction is called. It retrieves the file's shareable URL and usesdocument.querySelectorto find the correct input field in the form. - Event Dispatching: After setting the value,
dispatchEvent(new Event('change'))is called. This is crucial for notifying other parts of the application (like dependent fields or validation logic) that the field's value has been updated programmatically.
Key Concepts:
- Client-Side Execution: The PHP script's main purpose is to generate a piece of JavaScript that runs in the user's browser.
- API Loading: The script defines a global `loadPicker` function, which is called by the Google API script tag in the main page's header. This ensures the picker logic only runs after the necessary Google libraries are loaded.
- Authentication: The script handles the OAuth 2.0 flow to get the user's permission to access their Google Drive files.
- Picker Callback: When a user picks a file, the `pickerCallback` function is called. It retrieves the file's shareable URL.
- Dynamic Target Field: The `$target_field_name` from the button's URL is embedded into the JavaScript, making the script reusable for different fields.
- Event Dispatching: After setting the value, `dispatchEvent(new Event('change'))` is called. This is crucial for notifying other parts of the application (like dependent fields or validation logic) that the field's value has been updated programmatically.