client = new Google_Client(); // Service account JSON $this->client->setAuthConfig( ROOTPATH . 'nhance-ee8d1-e3c5269b1ec7.json' ); // IMPORTANT for service account $this->client->useApplicationDefaultCredentials(); // Required scopes $this->client->addScope([ Google_Service_Drive::DRIVE, Google_Service_Sheets::SPREADSHEETS, ]); // Init services $this->sheets = new Google_Service_Sheets($this->client); $this->drive = new Google_Service_Drive($this->client); } /* ===================== READ ===================== */ public function read(string $spreadsheetId, string $range = 'RFQ Page') { $response = $this->sheets ->spreadsheets_values ->get($spreadsheetId, $range); return $response->getValues() ?? []; } /* ===================== WRITE ===================== */ public function write(string $spreadsheetId, array $values, string $range = 'Sheet1') { $body = new Google_Service_Sheets_ValueRange([ 'values' => $values, ]); $this->sheets ->spreadsheets_values ->update( $spreadsheetId, $range, $body, ['valueInputOption' => 'RAW'] ); return true; } /* ===================== DOWNLOAD ===================== */ public function downloadExcel(string $spreadsheetId) { $response = $this->drive->files->export( $spreadsheetId, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', ['alt' => 'media'] ); return $response->getBody()->getContents(); } /* ================= COPY TEMPLATE ================= */ public function copyTemplate(string $templateId, string $name, string $folderId): string { $file = $this->drive->files->copy( $templateId, new \Google_Service_Drive_DriveFile([ 'name' => $name, 'parents' => [$folderId], ]), [ 'supportsAllDrives' => true, 'fields' => 'id, name, parents', ] ); return $file->id; } /* ================= PERMISSIONS ================= */ public function applyPermissions(string $fileId, array $permissions) { foreach ($permissions['editors'] as $email) { $this->createPermission($fileId, $email, 'writer'); } foreach ($permissions['viewers'] as $email) { $this->createPermission($fileId, $email, 'reader'); } } private function createPermission(string $fileId, string $email, string $role) { $type = str_starts_with($email, 'group:') ? 'group' : 'user'; $email = str_replace('group:', '', $email); $this->drive->permissions->create( $fileId, new \Google_Service_Drive_Permission([ 'type' => $type, 'role' => $role, 'emailAddress' => $email, ]), ['sendNotificationEmail' => false, 'supportsAllDrives' => true] ); } /* ================= PROTECTIONS ================= */ public function applyProtectionsold(string $spreadsheetId, array $ranges) { $spreadsheet = $this->sheets->spreadsheets->get($spreadsheetId); $sheetId = $spreadsheet->getSheets()[0]->getProperties()->getSheetId(); $requests = []; foreach ($ranges as $range) { [$sheetName, $a1] = explode('!', $range); $requests[] = [ 'addProtectedRange' => [ 'protectedRange' => [ 'range' => [ 'sheetId' => $sheetId, ], 'warningOnly' => false, ], ], ]; } $this->sheets->spreadsheets->batchUpdate( $spreadsheetId, new Google_Service_Sheets_BatchUpdateSpreadsheetRequest([ 'requests' => $requests, ]) ); } public function applyProtections(string $spreadsheetId, array $protections) { // Fetch spreadsheet metadata $spreadsheet = $this->sheets->spreadsheets->get( $spreadsheetId, ['fields' => 'sheets(properties(sheetId,title,gridProperties))'] ); // Map sheet names $sheetMap = []; foreach ($spreadsheet->getSheets() as $sheet) { $props = $sheet->getProperties(); $sheetMap[$props->getTitle()] = [ 'sheetId' => $props->getSheetId(), 'rowCount' => $props->getGridProperties()->getRowCount(), 'colCount' => $props->getGridProperties()->getColumnCount(), ]; } $requests = []; foreach ($protections as $protection) { $rangeStr = $protection['range']; if (! str_contains($rangeStr, '!')) { throw new \Exception("Invalid range format: {$rangeStr}"); } [$sheetName, $a1] = explode('!', $rangeStr, 2); if (! isset($sheetMap[$sheetName])) { throw new \Exception("Sheet not found: {$sheetName}"); } $sheetMeta = $sheetMap[$sheetName]; $gridRange = $this->convertA1ToGridRange( $a1, $sheetMeta['sheetId'], $sheetMeta['rowCount'], $sheetMeta['colCount'] ); $protectedRange = [ 'range' => $gridRange, 'description' => 'RFQ Protected Area', 'warningOnly' => false, 'editors' => [ 'users' => $protection['users'] ?? [], 'groups' => $protection['groups'] ?? [], ], ]; $requests[] = [ 'addProtectedRange' => [ 'protectedRange' => $protectedRange, ], ]; } if (! empty($requests)) { $batch = new \Google_Service_Sheets_BatchUpdateSpreadsheetRequest([ 'requests' => $requests, ]); $this->sheets->spreadsheets->batchUpdate($spreadsheetId, $batch); } return true; } /* ================= URL ================= */ public function sheetUrl(string $sheetId): string { return "https://docs.google.com/spreadsheets/d/{$sheetId}/edit"; } private function convertA1ToGridRange($a1, $sheetId, $maxRows, $maxCols) { if (preg_match('/^([A-Z]+)(\d+)(?::([A-Z]+)(\d+))?$/i', $a1, $m)) { $startCol = $this->colToIndex($m[1]); $startRow = intval($m[2]) - 1; if (! empty($m[3])) { $endCol = $this->colToIndex($m[3]) + 1; $endRow = intval($m[4]); } else { $endCol = $startCol + 1; $endRow = $startRow + 1; } return [ 'sheetId' => $sheetId, 'startRowIndex' => $startRow, 'endRowIndex' => $endRow, 'startColumnIndex' => $startCol, 'endColumnIndex' => $endCol, ]; } throw new \Exception("Unsupported A1 format: {$a1}"); } private function colToIndex($letters) { $letters = strtoupper($letters); $index = 0; for ($i = 0; $i < strlen($letters); $i++) { $index = $index * 26 + (ord($letters[$i]) - 64); } return $index - 1; } }