update
This commit is contained in:
224
application/libraries/GoogleDriveDocs.php
Normal file
224
application/libraries/GoogleDriveDocs.php
Normal file
@ -0,0 +1,224 @@
|
||||
<?php
|
||||
defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
class GoogleDriveDocs
|
||||
{
|
||||
protected $CI;
|
||||
|
||||
// Scopes
|
||||
const SCOPE_DRIVE = 'https://www.googleapis.com/auth/drive';
|
||||
const SCOPE_DOCS = 'https://www.googleapis.com/auth/documents';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->CI =& get_instance();
|
||||
// Doit être chargé dans le contrôleur ou le modèle avant usage,
|
||||
// mais on le fait “au cas où”
|
||||
if (!isset($this->CI->googleservicejwt)) {
|
||||
$this->CI->load->library('GoogleServiceJWT');
|
||||
}
|
||||
}
|
||||
|
||||
/* -----------------------------
|
||||
* HTTP helpers (Bearer token)
|
||||
* ----------------------------- */
|
||||
private function requestJson(string $method, string $url, string $token, ?string $body = null, array $extraHeaders = [])
|
||||
{
|
||||
$ch = curl_init($url);
|
||||
$headers = array_merge([
|
||||
'Authorization: Bearer ' . $token,
|
||||
'Accept: application/json',
|
||||
], $extraHeaders);
|
||||
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_MAXREDIRS => 3,
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
CURLOPT_CUSTOMREQUEST => strtoupper($method),
|
||||
CURLOPT_HTTPHEADER => $headers,
|
||||
]);
|
||||
|
||||
if ($body !== null) {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
||||
}
|
||||
|
||||
$out = curl_exec($ch);
|
||||
$err = curl_error($ch);
|
||||
$code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($err) {
|
||||
log_message('error', '[GoogleDriveDocs] CURL error '.$err.' for '.$url);
|
||||
throw new Exception('HTTP error: ' . $err);
|
||||
}
|
||||
|
||||
$json = json_decode($out, true);
|
||||
if ($code >= 400) {
|
||||
$msg = is_array($json) ? json_encode($json) : $out;
|
||||
log_message('error', "[GoogleDriveDocs] HTTP $code: $msg");
|
||||
throw new Exception("HTTP $code: $msg");
|
||||
}
|
||||
|
||||
return is_array($json) ? $json : [];
|
||||
}
|
||||
|
||||
public function getJson(string $url, string $token): array
|
||||
{
|
||||
return $this->requestJson('GET', $url, $token, null);
|
||||
}
|
||||
|
||||
public function postJson(string $url, string $token, string $jsonBody): array
|
||||
{
|
||||
return $this->requestJson('POST', $url, $token, $jsonBody, ['Content-Type: application/json']);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------
|
||||
* DRIVE (Shared drives OK) — Service Account JWT
|
||||
* ------------------------------------------------ */
|
||||
|
||||
/** Liste les sous-dossiers directs d’un parent (uniquement folders). */
|
||||
/** Liste les sous-dossiers directs d’un parent (uniquement folders), avec pagination complète. */
|
||||
public function listChildrenFolders(string $parentId, ?string $pageToken = null, int $pageSize = 1000): array
|
||||
{
|
||||
$token = $this->CI->googleservicejwt->getAccessToken(self::SCOPE_DRIVE);
|
||||
|
||||
$files = [];
|
||||
$q = sprintf(
|
||||
"'%s' in parents and mimeType='application/vnd.google-apps.folder' and trashed=false",
|
||||
$parentId
|
||||
);
|
||||
|
||||
do {
|
||||
$params = [
|
||||
'supportsAllDrives' => 'true',
|
||||
'includeItemsFromAllDrives' => 'true',
|
||||
'corpora' => 'allDrives',
|
||||
'q' => $q,
|
||||
'orderBy' => 'name',
|
||||
'pageSize' => $pageSize, // 1000 max par appel
|
||||
'fields' => 'nextPageToken, files(id,name)'
|
||||
];
|
||||
if ($pageToken) {
|
||||
$params['pageToken'] = $pageToken;
|
||||
}
|
||||
|
||||
$url = 'https://www.googleapis.com/drive/v3/files?' . http_build_query($params);
|
||||
$resp = $this->getJson($url, $token);
|
||||
foreach (($resp['files'] ?? []) as $f) {
|
||||
if (!empty($f['id']) && !empty($f['name'])) {
|
||||
$files[] = $f; // on garde le format attendu par GoogleDrive::list_folders()
|
||||
}
|
||||
}
|
||||
$pageToken = $resp['nextPageToken'] ?? null;
|
||||
} while ($pageToken);
|
||||
|
||||
// IMPORTANT : GoogleDrive::list_folders() lit $res['files']
|
||||
return ['files' => $files];
|
||||
}
|
||||
|
||||
/** Métadonnées (id, name, parents, driveId, ownedByMe) d’un fichier/dossier. */
|
||||
public function getFileMeta(string $fileId): array
|
||||
{
|
||||
$token = $this->CI->googleservicejwt->getAccessToken(self::SCOPE_DRIVE);
|
||||
|
||||
$url = 'https://www.googleapis.com/drive/v3/files/' . rawurlencode($fileId) . '?' . http_build_query([
|
||||
'supportsAllDrives' => 'true',
|
||||
'fields' => 'id,name,parents,driveId,ownedByMe'
|
||||
]);
|
||||
|
||||
return $this->getJson($url, $token) ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère (si existe) ou crée un Google Docs par NOM dans un dossier donné.
|
||||
* Retourne le fileId. Création directe dans le dossier => propriété = Drive/Shared drive, pas un utilisateur.
|
||||
*/
|
||||
public function ensureDocInFolderByName(string $folderId, string $name): string
|
||||
{
|
||||
$token = $this->CI->googleservicejwt->getAccessToken(self::SCOPE_DRIVE);
|
||||
|
||||
// 1) Cherche un doc (Google Docs) strictement par nom + parent
|
||||
$qName = addcslashes($name, "'");
|
||||
$listUrl = 'https://www.googleapis.com/drive/v3/files?' . http_build_query([
|
||||
'supportsAllDrives' => 'true',
|
||||
'includeItemsFromAllDrives' => 'true',
|
||||
'q' => "name='{$qName}' and '{$folderId}' in parents and mimeType='application/vnd.google-apps.document' and trashed=false",
|
||||
'fields' => 'files(id,name,driveId)'
|
||||
]);
|
||||
$found = $this->getJson($listUrl, $token);
|
||||
if (!empty($found['files'][0]['id'])) {
|
||||
return $found['files'][0]['id'];
|
||||
}
|
||||
|
||||
// 2) Crée le doc dans ce dossier (donc sous le drive partagé s’il s’agit d’un Shared drive)
|
||||
$createUrl = 'https://www.googleapis.com/drive/v3/files?supportsAllDrives=true';
|
||||
$body = json_encode([
|
||||
'name' => $name,
|
||||
'mimeType' => 'application/vnd.google-apps.document',
|
||||
'parents' => [$folderId],
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$created = $this->postJson($createUrl, $token, $body);
|
||||
if (empty($created['id'])) {
|
||||
throw new Exception('Impossible de créer le document Google Docs.');
|
||||
}
|
||||
return $created['id'];
|
||||
}
|
||||
|
||||
/* -----------------------
|
||||
* DOCS helpers (JWT)
|
||||
* ----------------------- */
|
||||
|
||||
/** Lit un document Docs (pour récupérer endIndex, etc.). */
|
||||
public function getDocument(string $docId): array
|
||||
{
|
||||
$token = $this->CI->googleservicejwt->getAccessToken(self::SCOPE_DOCS);
|
||||
$url = 'https://docs.googleapis.com/v1/documents/' . rawurlencode($docId);
|
||||
return $this->getJson($url, $token) ?: [];
|
||||
}
|
||||
|
||||
/** Envoie un batchUpdate à Docs API. */
|
||||
public function batchUpdateDocument(string $docId, array $requests): array
|
||||
{
|
||||
$token = $this->CI->googleservicejwt->getAccessToken(self::SCOPE_DOCS);
|
||||
$url = 'https://docs.googleapis.com/v1/documents/' . rawurlencode($docId) . ':batchUpdate';
|
||||
$body = json_encode(['requests' => $requests], JSON_UNESCAPED_UNICODE);
|
||||
return $this->postJson($url, $token, $body);
|
||||
}
|
||||
|
||||
/** Efface le contenu (sauf le dernier caractère) pour “réécrire proprement”. */
|
||||
public function clearDocument(string $docId): void
|
||||
{
|
||||
// On lit le document pour connaître l’index de fin
|
||||
$doc = $this->getDocument($docId);
|
||||
$content = $doc['body']['content'] ?? [];
|
||||
if (!$content) {
|
||||
return; // rien à effacer
|
||||
}
|
||||
|
||||
// Google Docs garde un newline terminal non-supprimable
|
||||
$last = $content[count($content) - 1] ?? [];
|
||||
$end = isset($last['endIndex']) ? (int)$last['endIndex'] : null;
|
||||
if (!$end || $end <= 2) {
|
||||
// 1 = tout début, 2 = juste le newline terminal: rien à effacer
|
||||
return;
|
||||
}
|
||||
|
||||
// ⚠️ Ne jamais inclure le newline terminal dans la suppression
|
||||
$safeEnd = max(2, $end - 1);
|
||||
|
||||
$this->batchUpdateDocument($docId, [[
|
||||
'deleteContentRange' => [
|
||||
'range' => [
|
||||
'startIndex' => 1,
|
||||
'endIndex' => $safeEnd
|
||||
]
|
||||
]
|
||||
]]);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user