php/googleClientLib/googledrive.php

<?php

/* Google Drive Interface
    docs + links
        api:            https://developers.google.com/drive/api/guides/about-sdk
        file fields:    https://developers.google.com/drive/api/reference/rest/v3/files
        guide           https://developers.google.com/drive/api/guides/manage-uploads  examples in php http etc.
    ----------- installation
    download latest release, select zip with appropriate release and expand
        https://github.com/googleapis/google-api-php-client/releases
            expand and remove in vendor/google/apiclient-services/src everything except Drive and Drive.php
        https://github.com/googleapis/google-api-php-client-services/releases
*/
require_once 'env.php';
require_once 'google-api-small/vendor/autoload.php';

class GoogleDrive {
    /*
        docs: https://docs.nextcloud.com/server/latest/developer_manual/client_apis/WebDAV/index.html 
    */
    public $cloudId, $user, $meta;
    private $srv, $fiFi = 'name, id, mimeType, modifiedTime, owners(emailAddress), size, parents';
    public function __construct($gc, $id, $ty, $cliN, $us) {
        $this->gc = $gc;
        $this->srv = new Google\Service\Drive($gc);
        $this->cloudId = $id;
        $this->user = $us;
        $this->accessTk = $gc->getAccessToken();
        $this->hAuth = "Authorization: Bearer " . $this->accessTk['access_token'];
        dbg1("auth $this->hAuth, accessToken", $this->accessTk);
        $r = $this->get('root');
        if ($r[5] !== $this->user)
            err("mismatch GoogleDrive(user=$this->user) but root user $r[5]:", $r);
    }

    function meta($fun, $dir) {
        $now = new DateTime();
        return $this->meta =[
              ['timestamp'      , 'cloudId'    , 'fun', 'dir', 'user'     , 'timeZone']
            , [toLocalTst($now), $this->cloudId, $fun , $dir , $this->user , $now->format('e P')]
            , ['filename', 'id', 'type', 'path', 'lastmod', 'owner', 'size', 'parent', 'level']
        ];
    }

    public function getHTTP($gid, $gp=false) {
         #   "Authorization: Bearer " . $this->accessTk['access_token']ACCESS_TOKEN"
        $ctx = stream_context_create(['http' => ['header' => $this->hAuth
              #. "\nAccept: application/json" # . ' realm="https://www.googleapis.com/auth/documents.readonly"' # . $this->accessTk['scope'] . '"' 
              #  , 'method' => 'PUT'
            ]]);
        $cnt = @file_get_contents("https://www.googleapis.com/drive/v3/files/$gid?fields=" . rawurlencode($this->fiFi) , false, $ctx);
        if(is_null($cnt) or $http_response_header[0] !== 'HTTP/1.1 200 OK') {
            if ($http_response_header[0] === 'HTTP/1.1 404 Not Found')
                return null;
            err(__METHOD__ . "get($gid)", error_get_last(), ", response", $http_response_header);
        };
        return json_decode($cnt);
         $g = $f = $this->srv->files->get($gid, ['fields' => $this->fiFi]);
        if (! ($id = $g->getParents()[0] ?? false)) {
            $p = './';
        } elseif ($gp) {
            $p = $g->getName() . substr('/', 0, 'application/vnd.google-apps.folder' === $f->getMimeType());
            while(true) {
                $g = $this->srv->files->get($id, ['fields' => 'name, parents']);
                if ( ! ($id = $g->getParents()[0] ?? false))
                    break;
                $p = $g->getName() . "/$p";
            } 
        }
       return [$f->getName(), $f->getiD(), $f->getMimeType(), $p ?? ''
                       , toLocalTst($f->getModifiedTime()), $f->getOwners()[0]->getEmailAddress(), $f->getSize()
                       , $f->getParents()[0] ?? null, 0];
    }


    public function get($gid, $gp=false) {
        $g = $f = $this->srv->files->get($gid, ['fields' => $this->fiFi]);
        if (! ($id = $g->getParents()[0] ?? false)) {
            $p = './';
        } elseif ($gp) {
            $p = $g->getName() . substr('/', 0, 'application/vnd.google-apps.folder' === $f->getMimeType());
            while(true) {
                $g = $this->srv->files->get($id, ['fields' => 'name, parents']);
                if ( ! ($id = $g->getParents()[0] ?? false))
                    break;
                $p = $g->getName() . "/$p";
            } 
        }
       dbg1("drive client token", $this->gc->getAccessToken());
       return [$f->getName(), $f->getiD(), $f->getMimeType(), $p ?? ''
                       , toLocalTst($f->getModifiedTime()), $f->getOwners()[0]->getEmailAddress(), $f->getSize()
                       , $f->getParents()[0] ?? null, 0];
    }

     public function ffSim($q, $hn=null) {
       $opt =   [ 'pageSize' => 100
                , 'fields' => "nextPageToken, files($this->fiFi)"
                , 'q' => 'not trashed'. ($q ? " and ($q)" :  '')
                , 'orderBy' => 'name'
                ];
        $this->ffFi = $this->ffFo = $this->ffSz = 0;
        if ($hn) 
            return ffOpt($opt, $hn);
        $res = [];
        $this->ffOpt($opt, function ($n) use(&$res) {$res[] = $n; });
        return $res;
        }

        
     public function ffOpt($opt, $hn, $pPa = '') {
        out("ffOpt", $opt);
        do {
            $results = $this->srv->files->listFiles($opt);
            foreach ($files=$results->getFiles() as $f) {
                $fo = 'application/vnd.google-apps.folder' === $ty = $f->getMimeType();
                if ($fo) {
                    $this->ffFo++;
                } else {
                    $this->ffFi++;
                    $this->ffSz += $f->getSize();
                }
                $p = "$pPa{$f->getName()}" . substr('/', 0, $fo);
                $hn([$f->getName(), $f->getiD(), $f->getMimeType(), $p
                       , toLocalTst($f->getModifiedTime()), $f->getOwners()[0]->getEmailAddress(), $f->getSize()
                       ,  $f->getParents()[0]]);
            }
        } while ($opt['pageToken'] = $results->getNextPageToken());
    }

    public function ff($dirId, $hn, $hMeta=null, $whr=null) {
        $dirId or $dirId='root';
        $dirNd = $this->get($dirId, true); 
        $this->meta('ff', $dirNd[3]);    
        if ($hMeta)
            $hMeta($this->meta);
        $ix = 0;
        $this->ffHn = $hn;
        $this->ffOpt = [ 'pageSize' => 100
                , 'fields' => "nextPageToken, files($this->fiFi)"
                , 'q' => ' in parents and not trashed' . ($whr ? " and ($whr)" :  '')
                , 'orderBy' => 'name'
                ];
        $this->ffFi = $this->ffFo = $this->ffSz = 0;
        $dirNd[3] = '';
        ($this->ffHn)($dirNd);
        $this->ff1(1, $dirId, '');
    }

    function ff1($lv, $pId, $pPa) {
        $opt = $this->ffOpt; 
        $opt['q'] = "\"$pId\" $opt[q]";
        do {
            $results = $this->srv->files->listFiles($opt);
            foreach ($files=$results->getFiles() as $f) {
                $fo = 'application/vnd.google-apps.folder' === $ty = $f->getMimeType();
                if ($fo) {
                    $this->ffFo++;
                } else {
                    $this->ffFi++;
                    $this->ffSz += $f->getSize();
                }
                $p = "$pPa{$f->getName()}" . substr('/', 0, $fo);
                ($this->ffHn)([$f->getName(), $f->getiD(), $ty, $p
                       , toLocalTst($f->getModifiedTime()), $f->getOwners()[0]->getEmailAddress(), $f->getSize()
                       ,  $f->getParents()[0], $lv]);
                if ($fo)
                    $this->ff1($lv+1, $f->getiD(), $p);
            }
        } while ($opt['pageToken'] = $results->getNextPageToken());
    }

    public function create($pa, $fn, $ty, $cnt) {
         $fileMetadata = new Google\Service\Drive\DriveFile(['name' => $fn, 'parents' => [$pa]]);
         $file = $this->srv->files->create($fileMetadata
          , [ 'data' => $cnt . "\n" . toLocalTst('now') . "\n und so weiter"
            , 'mimeType' => $ty
            , 'uploadType' => 'multipart'
            , 'fields' => 'id'
            , 'keepRevisionForever' => true
           ]);
        out("File ID: $file->id");
        return $file->id;
    }

    public function uploadversion($gid, $cnt) {
        $fileMetadata = new Google\Service\Drive\DriveFile();        
        $f = $this->srv->files->update($gid, $fileMetadata
          , [ 'data' => $cnt . "\n" . toLocalTst('now') . "\n und so weiter immer spö"
         #   , 'mimeType' => $ty
            , 'uploadType' => 'multipart'
            , 'fields' => $this->fiFi
            , 'keepRevisionForever' => true
            ]);
        out('uploadVers file ID:', [$f->getName(), $f->getiD(), $f->getMimeType(), $p ?? ''
                       , toLocalTst($f->getModifiedTime()), $f->getOwners()[0]->getEmailAddress(), $f->getSize()
                       , $f->getParents()[0] ?? null]);
        return $f->id;
    }


    public function upload($fn, $ty, $cnt) {
        $ff = $this->ffSim("\"root\" in parents and name =\"$fn\"" );
        if (count($ff) === 0)
            $this->create('root', $fn, $ty, $cnt);
        elseif (count($ff) === 1)
            $this->uploadversion($ff[0][1], $cnt);
        else
            err("upload $fn has " . count($ff) . " nodes", $ff);
    }

    public function mv($fP, $tP) {
        /*  move file/directory $fr to new path/name $to
        */
        $fr =implode('/', array_map('rawurlencode', is_array($fP) ? $fP : explode('/', $fP)));
        $to =implode('/', array_map('rawurlencode', is_array($tP) ? $tP :explode('/', $tP)));
        $ctx = stream_context_create(['http' => [
                'header' => "$this->hAuth\nDestination: $this->fUri$to\nOverwrite: F"
                , 'method' => 'MOVE'
                ]]);
        $r = file_get_contents("$this->fUri$fr", 0, $ctx);
        if ($http_response_header[0] !== 'HTTP/1.1 201 Created')
            err("mv $fr to $to failed: http_response_header", $http_response_header);
    }

    public function putFi($fn, $cnt, $ty='text/csv') {
        // upload to host $hst a file named $fn and contents $cnt, if the file already existst, create new version of the same id
        $ctx = stream_context_create(['http' => [
                'header' => "Content-Type: $ty\n$this->hAuth"
                , 'method' => 'PUT'
                , 'content' => $cnt
                ]]);
        $r = file_get_contents("$this->fUri$fn", 0, $ctx);
        echo "putFi sent " .strlen($cnt) ." chars to $this->cloudId $this->host $fn, response $http_response_header[0]\n";
        if ($http_response_header[0] !== "HTTP/1.1 201 Created" and $http_response_header[0] !== "HTTP/1.1 204 No Content")
            err("putFi $fn failed, response:>n ", $http_response_header);
    }
    public function byId($id, $dir='') {
       $ctx = stream_context_create($c = ['http' => [
                'header' => "Content-Type: text/xml\n$this->hAuth" 
                , 'method' => 'SEARCH'
                , 'content' => <<<data
            <?xml version="1.0" encoding="UTF-8"?>
            <d:searchrequest xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
                <d:basicsearch>
                    <d:select>
                         <d:prop>
                             <oc:fileid/>
                             <d:displayname/>
                             <d:getcontenttype/>
                             <d:getetag/>
                             <oc:size/>
                         </d:prop>
                   </d:select>
                    <d:from>
                        <d:scope>
                            <d:href>/files/$this->rootDir/</d:href>
                            <d:depth>infinity</d:depth>
                        </d:scope>
                    </d:from>
                    <d:where>
                        <d:eq>
                            <d:prop>
                                <oc:fileid/>
                            </d:prop>
                            <d:literal>$id</d:literal>
                        </d:eq>
                    </d:where>
                </d:basicsearch>
            </d:searchrequest> 
            data ]]);
        $r = file_get_contents("$this->sUri", 0, $ctx);
        echo "search $this->sUri response $http_response_header[0]\n";
        var_dump($http_response_header);
        return $r;
    }
    public function rename($r, $f2v, $hi) { 
        /* check all resources in xmmResult $r, if they need to be renamed
            $f2v: callback: rename last level of path
            $hi($nx, $n, $fP, $fM, $tP): callback if last level should be rename:
                $nx=count, $n array for resource, $fP from path old, $fM from path for mv, $tp to path for mv
        */  
        $nx = -1;
        $fP = $tP = [];
        $hR = function ($n) use (&$nx, &$fP, &$tP, $f2v, $hi) {
            $nx++;
            $aS = $n[0];
            if (($fo = $n[2] === 'd:collection') !== str_ends_with($aS, '/'))
                err("path $aS mismatches type $n[2]");
            if ($aS === './')
                return; 
            $aP = explode('/', $fo ? substr($aS, 0, -1) : $aS);
            $aV = $f2v($aL = $aP[$aY = count($aP) - 1]);
            if ($aL === $aV)
                return;
            $y = min($aY, count($fP));
            for ($x=0; $x < $y and $aP[$x] === $fP[$x]; $x++) ; # index of first difference
            out("a) $x $aY fP", $fP, ", tP", $tP);
            for ( ; $x < $aY ; $x++)                            # copy new levels
                $fP[$x] = $tP[$x] = $aP[$x];
            out("b) $x $aY fP", $fP, ", tP", $tP);
            while (array_key_last($fP) > $aY)
                array_pop($fP);
            while (array_key_last($tP) > $aY)
                array_pop($tP);
            $fM = $tP;
            $fP[$aY] = $fM[$aY] = $aL;
            $tP[$aY] = $aV;
            $hi($nx, $n, $fP, $fM, $tP);
        };
        $this->ffRsrc($r, $hR);
        echo "$nx nodes in $this->cloudId\n";
    }
} # end class GoogleDrive