Edit File by line
/home/barbar84/www/wp-conte.../plugins/updraftp.../includes/Dropbox2
File: API.php
<?php
[0] Fix | Delete
[1] Fix | Delete
/**
[2] Fix | Delete
* Dropbox API base class
[3] Fix | Delete
* @author Ben Tadiar <ben@handcraftedbyben.co.uk>
[4] Fix | Delete
* @link https://github.com/benthedesigner/dropbox
[5] Fix | Delete
* @link https://www.dropbox.com/developers
[6] Fix | Delete
* @link https://status.dropbox.com Dropbox status
[7] Fix | Delete
* @package Dropbox
[8] Fix | Delete
*/
[9] Fix | Delete
class UpdraftPlus_Dropbox_API {
[10] Fix | Delete
// API Endpoints
[11] Fix | Delete
const API_URL_V2 = 'https://api.dropboxapi.com/';
[12] Fix | Delete
const CONTENT_URL_V2 = 'https://content.dropboxapi.com/2/';
[13] Fix | Delete
[14] Fix | Delete
/**
[15] Fix | Delete
* OAuth consumer object
[16] Fix | Delete
* @var null|OAuth\Consumer
[17] Fix | Delete
*/
[18] Fix | Delete
private $OAuth;
[19] Fix | Delete
[20] Fix | Delete
/**
[21] Fix | Delete
* The root level for file paths
[22] Fix | Delete
* Either `dropbox` or `sandbox` (preferred)
[23] Fix | Delete
* @var null|string
[24] Fix | Delete
*/
[25] Fix | Delete
private $root;
[26] Fix | Delete
[27] Fix | Delete
/**
[28] Fix | Delete
* Format of the API response
[29] Fix | Delete
* @var string
[30] Fix | Delete
*/
[31] Fix | Delete
private $responseFormat = 'php';
[32] Fix | Delete
[33] Fix | Delete
/**
[34] Fix | Delete
* JSONP callback
[35] Fix | Delete
* @var string
[36] Fix | Delete
*/
[37] Fix | Delete
private $callback = 'dropboxCallback';
[38] Fix | Delete
[39] Fix | Delete
/**
[40] Fix | Delete
* Chunk size used for chunked uploads
[41] Fix | Delete
* @see \Dropbox\API::chunkedUpload()
[42] Fix | Delete
*/
[43] Fix | Delete
private $chunkSize = 4194304;
[44] Fix | Delete
[45] Fix | Delete
/**
[46] Fix | Delete
* Set the OAuth consumer object
[47] Fix | Delete
* See 'General Notes' at the link below for information on access type
[48] Fix | Delete
* @link https://www.dropbox.com/developers/reference/api
[49] Fix | Delete
* @param OAuth\Consumer\ConsumerAbstract $OAuth
[50] Fix | Delete
* @param string $root Dropbox app access type
[51] Fix | Delete
*/
[52] Fix | Delete
public function __construct(Dropbox_ConsumerAbstract $OAuth, $root = 'sandbox') {
[53] Fix | Delete
$this->OAuth = $OAuth;
[54] Fix | Delete
$this->setRoot($root);
[55] Fix | Delete
}
[56] Fix | Delete
[57] Fix | Delete
/**
[58] Fix | Delete
* Set the root level
[59] Fix | Delete
* @param mixed $root
[60] Fix | Delete
* @throws Exception
[61] Fix | Delete
* @return void
[62] Fix | Delete
*/
[63] Fix | Delete
public function setRoot($root) {
[64] Fix | Delete
if ($root !== 'sandbox' && $root !== 'dropbox') {
[65] Fix | Delete
throw new Exception("Expected a root of either 'dropbox' or 'sandbox', got '$root'");
[66] Fix | Delete
} else {
[67] Fix | Delete
$this->root = $root;
[68] Fix | Delete
}
[69] Fix | Delete
}
[70] Fix | Delete
[71] Fix | Delete
/**
[72] Fix | Delete
* This function will make a request to refresh the access token
[73] Fix | Delete
*
[74] Fix | Delete
* @return void
[75] Fix | Delete
*/
[76] Fix | Delete
public function refreshAccessToken() {
[77] Fix | Delete
$this->OAuth->refreshAccessToken();
[78] Fix | Delete
}
[79] Fix | Delete
[80] Fix | Delete
/**
[81] Fix | Delete
* Retrieves information about the user's account
[82] Fix | Delete
* @return object stdClass
[83] Fix | Delete
*/
[84] Fix | Delete
public function accountInfo() {
[85] Fix | Delete
$call = '2/users/get_current_account';
[86] Fix | Delete
$params = array('api_v2' => true);
[87] Fix | Delete
$response = $this->fetch('POST', self::API_URL_V2, $call, $params);
[88] Fix | Delete
return $response;
[89] Fix | Delete
}
[90] Fix | Delete
[91] Fix | Delete
/**
[92] Fix | Delete
* Retrieves information about the user's quota
[93] Fix | Delete
* @param array $options - valid keys are 'timeout'
[94] Fix | Delete
* @return object stdClass
[95] Fix | Delete
*/
[96] Fix | Delete
public function quotaInfo($options = array()) {
[97] Fix | Delete
$call = '2/users/get_space_usage';
[98] Fix | Delete
// Cases have been seen (Apr 2019) where a response came back (HTTP/2.0 response header - suspected outgoing web hosting proxy, as everyone else seems to get HTTP/1.0 and I'm not aware that current Curl versions would do HTTP/2.0 without specifically being told to) after 180 seconds; a valid response, but took a long time.
[99] Fix | Delete
$params = array(
[100] Fix | Delete
'api_v2' => true,
[101] Fix | Delete
'timeout' => isset($options['timeout']) ? $options['timeout'] : 20
[102] Fix | Delete
);
[103] Fix | Delete
$response = $this->fetch('POST', self::API_URL_V2, $call, $params);
[104] Fix | Delete
return $response;
[105] Fix | Delete
}
[106] Fix | Delete
[107] Fix | Delete
/**
[108] Fix | Delete
* Uploads large files to Dropbox in mulitple chunks
[109] Fix | Delete
* @param string $file Absolute path to the file to be uploaded
[110] Fix | Delete
* @param string|bool $filename The destination filename of the uploaded file
[111] Fix | Delete
* @param string $path Path to upload the file to, relative to root
[112] Fix | Delete
* @param boolean $overwrite Should the file be overwritten? (Default: true)
[113] Fix | Delete
* @param integer $offset position to seek to when opening the file
[114] Fix | Delete
* @param string $uploadID existing upload_id to resume an upload
[115] Fix | Delete
* @param string|array function to call back to upon each chunk
[116] Fix | Delete
* @return stdClass
[117] Fix | Delete
*/
[118] Fix | Delete
public function chunkedUpload($file, $filename = false, $path = '', $overwrite = true, $offset = 0, $uploadID = null, $callback = null) {
[119] Fix | Delete
[120] Fix | Delete
if (file_exists($file)) {
[121] Fix | Delete
if ($handle = @fopen($file, 'r')) {
[122] Fix | Delete
// Set initial upload ID and offset
[123] Fix | Delete
if ($offset > 0) {
[124] Fix | Delete
fseek($handle, $offset);
[125] Fix | Delete
}
[126] Fix | Delete
[127] Fix | Delete
/*
[128] Fix | Delete
Set firstCommit to true so that the upload session start endpoint is called.
[129] Fix | Delete
*/
[130] Fix | Delete
$firstCommit = (0 == $offset);
[131] Fix | Delete
[132] Fix | Delete
// Read from the file handle until EOF, uploading each chunk
[133] Fix | Delete
while ($data = fread($handle, $this->chunkSize)) {
[134] Fix | Delete
[135] Fix | Delete
// Set the file, request parameters and send the request
[136] Fix | Delete
$this->OAuth->setInFile($data);
[137] Fix | Delete
[138] Fix | Delete
if ($firstCommit) {
[139] Fix | Delete
$params = array(
[140] Fix | Delete
'close' => false,
[141] Fix | Delete
'api_v2' => true,
[142] Fix | Delete
'content_upload' => true
[143] Fix | Delete
);
[144] Fix | Delete
$response = $this->fetch('POST', self::CONTENT_URL_V2, 'files/upload_session/start', $params);
[145] Fix | Delete
$firstCommit = false;
[146] Fix | Delete
} else {
[147] Fix | Delete
$params = array(
[148] Fix | Delete
'cursor' => array(
[149] Fix | Delete
'session_id' => $uploadID,
[150] Fix | Delete
// If you send it as a string, Dropbox will be unhappy
[151] Fix | Delete
'offset' => (int)$offset
[152] Fix | Delete
),
[153] Fix | Delete
'api_v2' => true,
[154] Fix | Delete
'content_upload' => true
[155] Fix | Delete
);
[156] Fix | Delete
$response = $this->append_upload($params, false);
[157] Fix | Delete
}
[158] Fix | Delete
[159] Fix | Delete
// On subsequent chunks, use the upload ID returned by the previous request
[160] Fix | Delete
if (isset($response['body']->session_id)) {
[161] Fix | Delete
$uploadID = $response['body']->session_id;
[162] Fix | Delete
}
[163] Fix | Delete
[164] Fix | Delete
/*
[165] Fix | Delete
API v2 no longer returns the offset, we need to manually work this out. So check that there are no errors and update the offset as well as calling the callback method.
[166] Fix | Delete
*/
[167] Fix | Delete
if (!isset($response['body']->error)) {
[168] Fix | Delete
$offset = ftell($handle);
[169] Fix | Delete
if ($callback) {
[170] Fix | Delete
call_user_func($callback, $offset, $uploadID, $file);
[171] Fix | Delete
}
[172] Fix | Delete
$this->OAuth->setInFile(null);
[173] Fix | Delete
}
[174] Fix | Delete
}
[175] Fix | Delete
[176] Fix | Delete
// Complete the chunked upload
[177] Fix | Delete
$filename = (is_string($filename)) ? $filename : basename($file);
[178] Fix | Delete
$params = array(
[179] Fix | Delete
'cursor' => array(
[180] Fix | Delete
'session_id' => $uploadID,
[181] Fix | Delete
'offset' => $offset
[182] Fix | Delete
),
[183] Fix | Delete
'commit' => array(
[184] Fix | Delete
'path' => '/' . $this->encodePath($path . $filename),
[185] Fix | Delete
'mode' => 'add'
[186] Fix | Delete
),
[187] Fix | Delete
'api_v2' => true,
[188] Fix | Delete
'content_upload' => true
[189] Fix | Delete
);
[190] Fix | Delete
$response = $this->append_upload($params, true);
[191] Fix | Delete
return $response;
[192] Fix | Delete
} else {
[193] Fix | Delete
throw new Exception('Could not open ' . $file . ' for reading');
[194] Fix | Delete
}
[195] Fix | Delete
}
[196] Fix | Delete
[197] Fix | Delete
// Throw an Exception if the file does not exist
[198] Fix | Delete
throw new Exception('Local file ' . $file . ' does not exist');
[199] Fix | Delete
}
[200] Fix | Delete
[201] Fix | Delete
private function append_upload($params, $last_call) {
[202] Fix | Delete
try {
[203] Fix | Delete
if ($last_call){
[204] Fix | Delete
$response = $this->fetch('POST', self::CONTENT_URL_V2, 'files/upload_session/finish', $params);
[205] Fix | Delete
} else {
[206] Fix | Delete
$response = $this->fetch('POST', self::CONTENT_URL_V2, 'files/upload_session/append_v2', $params);
[207] Fix | Delete
}
[208] Fix | Delete
} catch (Exception $e) {
[209] Fix | Delete
$responseCheck = json_decode($e->getMessage());
[210] Fix | Delete
if (isset($responseCheck) && strpos($responseCheck[0] , 'incorrect_offset') !== false) {
[211] Fix | Delete
$expected_offset = $responseCheck[1];
[212] Fix | Delete
throw new Exception('Submitted input out of alignment: got ['.$params['cursor']['offset'].'] expected ['.$expected_offset.']');
[213] Fix | Delete
[214] Fix | Delete
// $params['cursor']['offset'] = $responseCheck[1];
[215] Fix | Delete
// $response = $this->append_upload($params, $last_call);
[216] Fix | Delete
} elseif (isset($responseCheck) && strpos($responseCheck[0], 'closed') !== false) {
[217] Fix | Delete
throw new Exception("Upload with upload_id {$params['cursor']['session_id']} already completed");
[218] Fix | Delete
} else {
[219] Fix | Delete
throw $e;
[220] Fix | Delete
}
[221] Fix | Delete
}
[222] Fix | Delete
return $response;
[223] Fix | Delete
}
[224] Fix | Delete
[225] Fix | Delete
/**
[226] Fix | Delete
* Chunked downloads a file from Dropbox, it will return false if a file handle is not passed and will return true if the call was successful.
[227] Fix | Delete
*
[228] Fix | Delete
* @param string $file Path - to file, relative to root, including path
[229] Fix | Delete
* @param resource $outFile - the local file handle
[230] Fix | Delete
* @param array $options - any extra options to be passed e.g headers
[231] Fix | Delete
* @return boolean - a boolean to indicate success or failure
[232] Fix | Delete
*/
[233] Fix | Delete
public function download($file, $outFile = null, $options = array()) {
[234] Fix | Delete
// Only allow php response format for this call
[235] Fix | Delete
if ($this->responseFormat !== 'php') {
[236] Fix | Delete
throw new Exception('This method only supports the `php` response format');
[237] Fix | Delete
}
[238] Fix | Delete
[239] Fix | Delete
if ($outFile) {
[240] Fix | Delete
$this->OAuth->setOutFile($outFile);
[241] Fix | Delete
[242] Fix | Delete
$params = array('path' => '/' . $file, 'api_v2' => true, 'content_download' => true);
[243] Fix | Delete
[244] Fix | Delete
if (isset($options['headers'])) {
[245] Fix | Delete
foreach ($options['headers'] as $key => $header) {
[246] Fix | Delete
$headers[] = $key . ': ' . $header;
[247] Fix | Delete
}
[248] Fix | Delete
$params['headers'] = $headers;
[249] Fix | Delete
}
[250] Fix | Delete
[251] Fix | Delete
$file = $this->encodePath($file);
[252] Fix | Delete
$call = 'files/download';
[253] Fix | Delete
[254] Fix | Delete
$response = $this->fetch('GET', self::CONTENT_URL_V2, $call, $params);
[255] Fix | Delete
[256] Fix | Delete
fclose($outFile);
[257] Fix | Delete
[258] Fix | Delete
return true;
[259] Fix | Delete
} else {
[260] Fix | Delete
return false;
[261] Fix | Delete
}
[262] Fix | Delete
}
[263] Fix | Delete
[264] Fix | Delete
/**
[265] Fix | Delete
* Calls the relevant method to return metadata for all files and folders that match the search query
[266] Fix | Delete
* @param mixed $query The search string. Must be at least 3 characters long
[267] Fix | Delete
* @param string [$path=''] The path to the folder you want to search in
[268] Fix | Delete
* @param integer [$limit=1000] Maximum number of results to return (1-1000)
[269] Fix | Delete
* @param integer [$cursor=''] A Dropbox ID to start the search from
[270] Fix | Delete
* @return array
[271] Fix | Delete
*/
[272] Fix | Delete
public function search($query, $path = '', $limit = 1000, $cursor = '') {
[273] Fix | Delete
if (empty($cursor)) {
[274] Fix | Delete
return $this->start_search($query, $path, $limit);
[275] Fix | Delete
} else {
[276] Fix | Delete
return $this->continue_search($cursor);
[277] Fix | Delete
}
[278] Fix | Delete
}
[279] Fix | Delete
[280] Fix | Delete
/**
[281] Fix | Delete
* This method will start a search for all files and folders that match the search query
[282] Fix | Delete
*
[283] Fix | Delete
* @param mixed $query - the search string, must be at least 3 characters long
[284] Fix | Delete
* @param string $path - the path to the folder you want to search in
[285] Fix | Delete
* @param integer $limit - maximum number of results to return (1-1000)
[286] Fix | Delete
*
[287] Fix | Delete
* @return array - an array of search results
[288] Fix | Delete
*/
[289] Fix | Delete
private function start_search($query, $path, $limit) {
[290] Fix | Delete
$call = '2/files/search_v2';
[291] Fix | Delete
$path = $this->encodePath($path);
[292] Fix | Delete
// APIv2 requires that the path match this regex: String(pattern="(/(.|[\r\n])*)?|(ns:[0-9]+(/.*)?)")
[293] Fix | Delete
if ($path && '/' != substr($path, 0, 1)) $path = "/$path";
[294] Fix | Delete
$params = array(
[295] Fix | Delete
'query' => $query,
[296] Fix | Delete
'options' => array(
[297] Fix | Delete
'path' => $path,
[298] Fix | Delete
'max_results' => ($limit < 1) ? 1 : (($limit > 1000) ? 1000 : (int) $limit),
[299] Fix | Delete
),
[300] Fix | Delete
'api_v2' => true,
[301] Fix | Delete
);
[302] Fix | Delete
$response = $this->fetch('POST', self::API_URL_V2, $call, $params);
[303] Fix | Delete
return $response;
[304] Fix | Delete
}
[305] Fix | Delete
[306] Fix | Delete
/**
[307] Fix | Delete
* This method will continue a previous search for all files and folders that match the previous search query
[308] Fix | Delete
*
[309] Fix | Delete
* @param string $cursor - a Dropbox ID to continue the search
[310] Fix | Delete
*
[311] Fix | Delete
* @return array - an array of search results
[312] Fix | Delete
*/
[313] Fix | Delete
private function continue_search($cursor) {
[314] Fix | Delete
$call = '2/files/search/continue_v2';
[315] Fix | Delete
$params = array(
[316] Fix | Delete
'cursor' => $cursor,
[317] Fix | Delete
'api_v2' => true,
[318] Fix | Delete
);
[319] Fix | Delete
$response = $this->fetch('POST', self::API_URL_V2, $call, $params);
[320] Fix | Delete
return $response;
[321] Fix | Delete
}
[322] Fix | Delete
[323] Fix | Delete
/**
[324] Fix | Delete
* Deletes a file or folder
[325] Fix | Delete
* @param string $path The path to the file or folder to be deleted
[326] Fix | Delete
* @return object stdClass
[327] Fix | Delete
*/
[328] Fix | Delete
public function delete($path) {
[329] Fix | Delete
$call = '2/files/delete_v2';
[330] Fix | Delete
$params = array('path' => '/' . $this->normalisePath($path), 'api_v2' => true);
[331] Fix | Delete
$response = $this->fetch('POST', self::API_URL_V2, $call, $params);
[332] Fix | Delete
return $response;
[333] Fix | Delete
}
[334] Fix | Delete
[335] Fix | Delete
/**
[336] Fix | Delete
* Intermediate fetch function
[337] Fix | Delete
* @param string $method The HTTP method
[338] Fix | Delete
* @param string $url The API endpoint
[339] Fix | Delete
* @param string $call The API method to call
[340] Fix | Delete
* @param array $params Additional parameters
[341] Fix | Delete
* @return mixed
[342] Fix | Delete
*/
[343] Fix | Delete
private function fetch($method, $url, $call, array $params = array()) {
[344] Fix | Delete
// Make the API call via the consumer
[345] Fix | Delete
$response = $this->OAuth->fetch($method, $url, $call, $params);
[346] Fix | Delete
[347] Fix | Delete
// Format the response and return
[348] Fix | Delete
switch ($this->responseFormat) {
[349] Fix | Delete
case 'json':
[350] Fix | Delete
return json_encode($response);
[351] Fix | Delete
case 'jsonp':
[352] Fix | Delete
$response = json_encode($response);
[353] Fix | Delete
return $this->callback . '(' . $response . ')';
[354] Fix | Delete
default:
[355] Fix | Delete
return $response;
[356] Fix | Delete
}
[357] Fix | Delete
}
[358] Fix | Delete
[359] Fix | Delete
/**
[360] Fix | Delete
* Set the API response format
[361] Fix | Delete
* @param string $format One of php, json or jsonp
[362] Fix | Delete
* @return void
[363] Fix | Delete
*/
[364] Fix | Delete
public function setResponseFormat($format) {
[365] Fix | Delete
$format = strtolower($format);
[366] Fix | Delete
if (!in_array($format, array('php', 'json', 'jsonp'))) {
[367] Fix | Delete
throw new Exception("Expected a format of php, json or jsonp, got '$format'");
[368] Fix | Delete
} else {
[369] Fix | Delete
$this->responseFormat = $format;
[370] Fix | Delete
}
[371] Fix | Delete
}
[372] Fix | Delete
[373] Fix | Delete
/**
[374] Fix | Delete
* Set the chunk size for chunked uploads
[375] Fix | Delete
* If $chunkSize is empty, set to 4194304 bytes (4 MB)
[376] Fix | Delete
* @see \Dropbox\API\chunkedUpload()
[377] Fix | Delete
*/
[378] Fix | Delete
public function setChunkSize($chunkSize = 4194304) {
[379] Fix | Delete
if (!is_int($chunkSize)) {
[380] Fix | Delete
throw new Exception('Expecting chunk size to be an integer, got ' . gettype($chunkSize));
[381] Fix | Delete
} elseif ($chunkSize > 157286400) {
[382] Fix | Delete
throw new Exception('Chunk size must not exceed 157286400 bytes, got ' . $chunkSize);
[383] Fix | Delete
} else {
[384] Fix | Delete
$this->chunkSize = $chunkSize;
[385] Fix | Delete
}
[386] Fix | Delete
}
[387] Fix | Delete
[388] Fix | Delete
/**
[389] Fix | Delete
* Set the JSONP callback function
[390] Fix | Delete
* @param string $function
[391] Fix | Delete
* @return void
[392] Fix | Delete
*/
[393] Fix | Delete
public function setCallback($function) {
[394] Fix | Delete
$this->callback = $function;
[395] Fix | Delete
}
[396] Fix | Delete
[397] Fix | Delete
/**
[398] Fix | Delete
* Get the mime type of downloaded file
[399] Fix | Delete
* If the Fileinfo extension is not loaded, return false
[400] Fix | Delete
* @param string $data File contents as a string or filename
[401] Fix | Delete
* @param string $isFilename Is $data a filename?
[402] Fix | Delete
* @return boolean|string Mime type and encoding of the file
[403] Fix | Delete
*/
[404] Fix | Delete
private function getMimeType($data, $isFilename = false) {
[405] Fix | Delete
if (extension_loaded('fileinfo')) {
[406] Fix | Delete
$finfo = new finfo(FILEINFO_MIME);
[407] Fix | Delete
if ($isFilename !== false) {
[408] Fix | Delete
return $finfo->file($data);
[409] Fix | Delete
}
[410] Fix | Delete
return $finfo->buffer($data);
[411] Fix | Delete
}
[412] Fix | Delete
return false;
[413] Fix | Delete
}
[414] Fix | Delete
[415] Fix | Delete
/**
[416] Fix | Delete
* Trim the path of forward slashes and replace
[417] Fix | Delete
* consecutive forward slashes with a single slash
[418] Fix | Delete
* @param string $path The path to normalise
[419] Fix | Delete
* @return string
[420] Fix | Delete
*/
[421] Fix | Delete
private function normalisePath($path) {
[422] Fix | Delete
$path = preg_replace('#/+#', '/', trim($path, '/'));
[423] Fix | Delete
return $path;
[424] Fix | Delete
}
[425] Fix | Delete
[426] Fix | Delete
/**
[427] Fix | Delete
* Encode the path, then replace encoded slashes
[428] Fix | Delete
* with literal forward slash characters
[429] Fix | Delete
* @param string $path The path to encode
[430] Fix | Delete
* @return string
[431] Fix | Delete
*/
[432] Fix | Delete
private function encodePath($path) {
[433] Fix | Delete
// in APIv1, encoding was needed because parameters were passed as part of the URL; this is no longer done in our APIv2 SDK; hence, all that we now do here is normalise.
[434] Fix | Delete
return $this->normalisePath($path);
[435] Fix | Delete
}
[436] Fix | Delete
}
[437] Fix | Delete
[438] Fix | Delete
It is recommended that you Edit text format, this type of Fix handles quite a lot in one request
Function