1<?php 2 3use League\Flysystem\Filesystem; 4use League\Flysystem\Adapter\Local; 5use League\Flysystem\Cached\CachedAdapter; 6use League\Flysystem\Cached\Storage\Adapter as ACache; 7use Hypweb\Flysystem\GoogleDrive\GoogleDriveAdapter; 8use Hypweb\Flysystem\Cached\Extra\Hasdir; 9use Hypweb\Flysystem\Cached\Extra\DisableEnsureParentDirectories; 10use Hypweb\elFinderFlysystemDriverExt\Driver as ExtDriver; 11 12elFinder::$netDrivers['googledrive'] = 'FlysystemGoogleDriveNetmount'; 13 14if (!class_exists('elFinderVolumeFlysystemGoogleDriveCache', false)) { 15 class elFinderVolumeFlysystemGoogleDriveCache extends ACache 16 { 17 use Hasdir; 18 use DisableEnsureParentDirectories; 19 } 20} 21 22class elFinderVolumeFlysystemGoogleDriveNetmount extends ExtDriver 23{ 24 25 public function __construct() 26 { 27 parent::__construct(); 28 29 $opts = array( 30 'acceptedName' => '#^[^/\\?*:|"<>]*[^./\\?*:|"<>]$#', 31 'rootCssClass' => 'elfinder-navbar-root-googledrive', 32 'gdAlias' => '%s@GDrive', 33 'gdCacheDir' => __DIR__ . '/.tmp', 34 'gdCachePrefix' => 'gd-', 35 'gdCacheExpire' => 600 36 ); 37 38 $this->options = array_merge($this->options, $opts); 39 } 40 41 /** 42 * Prepare driver before mount volume. 43 * Return true if volume is ready. 44 * 45 * @return bool 46 **/ 47 protected function init() 48 { 49 if (empty($this->options['icon'])) { 50 $this->options['icon'] = true; 51 } 52 if ($res = parent::init()) { 53 if ($this->options['icon'] === true) { 54 unset($this->options['icon']); 55 } 56 // enable command archive 57 $this->options['useRemoteArchive'] = true; 58 } 59 return $res; 60 } 61 62 /** 63 * Prepare 64 * Call from elFinder::netmout() before volume->mount() 65 * 66 * @param $options 67 * 68 * @return Array 69 * @author Naoki Sawada 70 */ 71 public function netmountPrepare($options) 72 { 73 if (empty($options['client_id']) && defined('ELFINDER_GOOGLEDRIVE_CLIENTID')) { 74 $options['client_id'] = ELFINDER_GOOGLEDRIVE_CLIENTID; 75 } 76 if (empty($options['client_secret']) && defined('ELFINDER_GOOGLEDRIVE_CLIENTSECRET')) { 77 $options['client_secret'] = ELFINDER_GOOGLEDRIVE_CLIENTSECRET; 78 } 79 80 if (!isset($options['pass'])) { 81 $options['pass'] = ''; 82 } 83 84 try { 85 $client = new \Google_Client(); 86 $client->setClientId($options['client_id']); 87 $client->setClientSecret($options['client_secret']); 88 89 if ($options['pass'] === 'reauth') { 90 $options['pass'] = ''; 91 $this->session->set('GoogleDriveAuthParams', [])->set('GoogleDriveTokens', []); 92 } else if ($options['pass'] === 'googledrive') { 93 $options['pass'] = ''; 94 } 95 96 $options = array_merge($this->session->get('GoogleDriveAuthParams', []), $options); 97 98 if (!isset($options['access_token'])) { 99 $options['access_token'] = $this->session->get('GoogleDriveTokens', []); 100 $this->session->remove('GoogleDriveTokens'); 101 } 102 $aToken = $options['access_token']; 103 104 $rootObj = $service = null; 105 if ($aToken) { 106 try { 107 $client->setAccessToken($aToken); 108 if ($client->isAccessTokenExpired()) { 109 $aToken = array_merge($aToken, $client->fetchAccessTokenWithRefreshToken()); 110 $client->setAccessToken($aToken); 111 } 112 $service = new \Google_Service_Drive($client); 113 $rootObj = $service->files->get('root'); 114 115 $options['access_token'] = $aToken; 116 $this->session->set('GoogleDriveAuthParams', $options); 117 118 } catch (Exception $e) { 119 $aToken = []; 120 $options['access_token'] = []; 121 if ($options['user'] !== 'init') { 122 $this->session->set('GoogleDriveAuthParams', $options); 123 return array('exit' => true, 'error' => elFinder::ERROR_REAUTH_REQUIRE); 124 } 125 } 126 127 } 128 129 $itpCare = isset($options['code']); 130 $code = $itpCare? $options['code'] : (isset($_GET['code'])? $_GET['code'] : ''); 131 if ($code || $options['user'] === 'init') { 132 if (empty($options['url'])) { 133 $options['url'] = elFinder::getConnectorUrl(); 134 } 135 136 if (isset($options['id'])) { 137 $callback = $options['url'] . (strpos($options['url'], '?') !== false? '&' : '?') . 'cmd=netmount&protocol=googledrive&host=' . ($options['id'] === 'elfinder'? '1' : $options['id']); 138 $client->setRedirectUri($callback); 139 } 140 141 if (!$aToken && empty($code)) { 142 $client->setScopes([Google_Service_Drive::DRIVE]); 143 if (!empty($options['offline'])) { 144 $client->setApprovalPrompt('force'); 145 $client->setAccessType('offline'); 146 } 147 $url = $client->createAuthUrl(); 148 149 $html = '<input id="elf-volumedriver-googledrive-host-btn" class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only" value="{msg:btnApprove}" type="button">'; 150 $html .= '<script> 151 $("#' . $options['id'] . '").elfinder("instance").trigger("netmount", {protocol: "googledrive", mode: "makebtn", url: "' . $url . '"}); 152 </script>'; 153 if (empty($options['pass']) && $options['host'] !== '1') { 154 $options['pass'] = 'return'; 155 $this->session->set('GoogleDriveAuthParams', $options); 156 return array('exit' => true, 'body' => $html); 157 } else { 158 $out = array( 159 'node' => $options['id'], 160 'json' => '{"protocol": "googledrive", "mode": "makebtn", "body" : "' . str_replace($html, '"', '\\"') . '", "error" : "' . elFinder::ERROR_ACCESS_DENIED . '"}', 161 'bind' => 'netmount' 162 ); 163 return array('exit' => 'callback', 'out' => $out); 164 } 165 } else { 166 if ($code) { 167 if (!empty($options['id'])) { 168 $aToken = $client->fetchAccessTokenWithAuthCode($code); 169 $options['access_token'] = $aToken; 170 unset($options['code']); 171 $this->session->set('GoogleDriveTokens', $aToken)->set('GoogleDriveAuthParams', $options); 172 $out = array( 173 'node' => $options['id'], 174 'json' => '{"protocol": "googledrive", "mode": "done", "reset": 1}', 175 'bind' => 'netmount' 176 ); 177 } else { 178 $nodeid = ($_GET['host'] === '1')? 'elfinder' : $_GET['host']; 179 $out = array( 180 'node' => $nodeid, 181 'json' => json_encode(array( 182 'protocol' => 'googledrive', 183 'host' => $nodeid, 184 'mode' => 'redirect', 185 'options' => array( 186 'id' => $nodeid, 187 'code'=> $code 188 ) 189 )), 190 'bind' => 'netmount' 191 ); 192 } 193 if (!$itpCare) { 194 return array('exit' => 'callback', 'out' => $out); 195 } else { 196 return array('exit' => true, 'body' => $out['json']); 197 } 198 } 199 $folders = []; 200 foreach ($service->files->listFiles([ 201 'pageSize' => 1000, 202 'q' => 'trashed = false and mimeType = "application/vnd.google-apps.folder"' 203 ]) as $f) { 204 $folders[$f->getId()] = $f->getName(); 205 } 206 natcasesort($folders); 207 $folders = ['root' => $rootObj->getName()] + $folders; 208 $folders = json_encode($folders); 209 $json = '{"protocol": "googledrive", "mode": "done", "folders": ' . $folders . '}'; 210 $options['pass'] = 'return'; 211 $html = 'Google.com'; 212 $html .= '<script> 213 $("#' . $options['id'] . '").elfinder("instance").trigger("netmount", ' . $json . '); 214 </script>'; 215 $this->session->set('GoogleDriveAuthParams', $options); 216 return array('exit' => true, 'body' => $html); 217 } 218 } 219 } catch (Exception $e) { 220 $this->session->remove('GoogleDriveAuthParams')->remove('GoogleDriveTokens'); 221 if (empty($options['pass'])) { 222 return array('exit' => true, 'body' => '{msg:' . elFinder::ERROR_ACCESS_DENIED . '}' . ' ' . $e->getMessage()); 223 } else { 224 return array('exit' => true, 'error' => [elFinder::ERROR_ACCESS_DENIED, $e->getMessage()]); 225 } 226 } 227 228 if (!$aToken) { 229 return array('exit' => true, 'error' => elFinder::ERROR_REAUTH_REQUIRE); 230 } 231 232 if ($options['path'] === '/') { 233 $options['path'] = 'root'; 234 } 235 236 try { 237 $file = $service->files->get($options['path']); 238 $options['alias'] = sprintf($this->options['gdAlias'], $file->getName()); 239 if (!empty($this->options['netkey'])) { 240 elFinder::$instance->updateNetVolumeOption($this->options['netkey'], 'alias', $this->options['alias']); 241 } 242 } catch (Google_Service_Exception $e) { 243 $err = json_decode($e->getMessage(), true); 244 if (isset($err['error']) && $err['error']['code'] == 404) { 245 return array('exit' => true, 'error' => [elFinder::ERROR_TRGDIR_NOT_FOUND, $options['path']]); 246 } else { 247 return array('exit' => true, 'error' => $e->getMessage()); 248 } 249 } catch (Exception $e) { 250 return array('exit' => true, 'error' => $e->getMessage()); 251 } 252 253 foreach (['host', 'user', 'pass', 'id', 'offline'] as $key) { 254 unset($options[$key]); 255 } 256 257 return $options; 258 } 259 260 /** 261 * process of on netunmount 262 * Drop table `dropbox` & rm thumbs 263 * 264 * @param $netVolumes 265 * @param $key 266 * 267 * @return bool 268 * @internal param array $options 269 */ 270 public function netunmount($netVolumes, $key) 271 { 272 $cache = $this->options['gdCacheDir'] . DIRECTORY_SEPARATOR . $this->options['gdCachePrefix'] . $this->netMountKey; 273 if (file_exists($cache) && is_writeable($cache)) { 274 unlink($cache); 275 } 276 if ($tmbs = glob($this->tmbPath . DIRECTORY_SEPARATOR . $this->netMountKey . '*')) { 277 foreach ($tmbs as $file) { 278 unlink($file); 279 } 280 } 281 return true; 282 } 283 284 /** 285 * "Mount" volume. 286 * Return true if volume available for read or write, 287 * false - otherwise 288 * 289 * @param array $opts 290 * 291 * @return bool 292 * @author Naoki Sawada 293 */ 294 public function mount(array $opts) 295 { 296 $creds = null; 297 if (isset($opts['access_token'])) { 298 $this->netMountKey = md5(join('-', array('googledrive', $opts['path'], (isset($opts['access_token']['refresh_token']) ? $opts['access_token']['refresh_token'] : $opts['access_token']['access_token'])))); 299 } 300 301 $client = new \Google_Client(); 302 $client->setClientId($opts['client_id']); 303 $client->setClientSecret($opts['client_secret']); 304 305 if (!empty($opts['access_token'])) { 306 $client->setAccessToken($opts['access_token']); 307 } 308 if ($this->needOnline && $client->isAccessTokenExpired()) { 309 try { 310 $creds = $client->fetchAccessTokenWithRefreshToken(); 311 } catch (LogicException $e) { 312 $this->session->remove('GoogleDriveAuthParams'); 313 throw $e; 314 } 315 } 316 317 $service = new \Google_Service_Drive($client); 318 319 // If path is not set, use the root 320 if (!isset($opts['path']) || $opts['path'] === '') { 321 $opts['path'] = 'root'; 322 } 323 324 $googleDrive = new GoogleDriveAdapter($service, $opts['path'], ['useHasDir' => true]); 325 326 $opts['fscache'] = null; 327 if ($this->options['gdCacheDir'] && is_writeable($this->options['gdCacheDir'])) { 328 if ($this->options['gdCacheExpire']) { 329 $opts['fscache'] = new elFinderVolumeFlysystemGoogleDriveCache(new Local($this->options['gdCacheDir']), $this->options['gdCachePrefix'] . $this->netMountKey, $this->options['gdCacheExpire']); 330 } 331 } 332 if ($opts['fscache']) { 333 $filesystem = new Filesystem(new CachedAdapter($googleDrive, $opts['fscache'])); 334 } else { 335 $filesystem = new Filesystem($googleDrive); 336 } 337 338 $opts['driver'] = 'FlysystemExt'; 339 $opts['filesystem'] = $filesystem; 340 $opts['separator'] = '/'; 341 $opts['checkSubfolders'] = true; 342 if (!isset($opts['alias'])) { 343 $opts['alias'] = 'GoogleDrive'; 344 } 345 346 if ($res = parent::mount($opts)) { 347 // update access_token of session data 348 if ($creds) { 349 $netVolumes = $this->session->get('netvolume'); 350 $netVolumes[$this->netMountKey]['access_token'] = array_merge($netVolumes[$this->netMountKey]['access_token'], $creds); 351 $this->session->set('netvolume', $netVolumes); 352 } 353 } 354 355 return $res; 356 } 357 358 /** 359 * @inheritdoc 360 */ 361 protected function tmbname($stat) 362 { 363 return $this->netMountKey . substr(substr($stat['hash'], strlen($this->id)), -38) . $stat['ts'] . '.png'; 364 } 365 366 /** 367 * Return debug info for client. 368 * 369 * @return array 370 **/ 371 public function debug() 372 { 373 $res = parent::debug(); 374 if (!empty($this->options['netkey']) && empty($this->options['refresh_token']) && $this->options['access_token'] && isset($this->options['access_token']['refresh_token'])) { 375 $res['refresh_token'] = $this->options['access_token']['refresh_token']; 376 } 377 378 return $res; 379 } 380} 381