You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
595 lines
22 KiB
595 lines
22 KiB
<?php
|
|
|
|
/**
|
|
* Dropbox API base class
|
|
* @author Ben Tadiar <ben@handcraftedbyben.co.uk>
|
|
* @link https://github.com/benthedesigner/dropbox
|
|
* @link https://www.dropbox.com/developers
|
|
* @link https://status.dropbox.com Dropbox status
|
|
* @package Dropbox
|
|
*/
|
|
class UpdraftPlus_Dropbox_API
|
|
{
|
|
// API Endpoints
|
|
const API_URL = 'https://api.dropbox.com/1/';
|
|
const API_URL_V2 = 'https://api.dropboxapi.com/2/';
|
|
const CONTENT_URL = 'https://api-content.dropbox.com/1/';
|
|
|
|
/**
|
|
* OAuth consumer object
|
|
* @var null|OAuth\Consumer
|
|
*/
|
|
private $OAuth;
|
|
|
|
/**
|
|
* The root level for file paths
|
|
* Either `dropbox` or `sandbox` (preferred)
|
|
* @var null|string
|
|
*/
|
|
private $root;
|
|
|
|
/**
|
|
* Format of the API response
|
|
* @var string
|
|
*/
|
|
private $responseFormat = 'php';
|
|
|
|
/**
|
|
* JSONP callback
|
|
* @var string
|
|
*/
|
|
private $callback = 'dropboxCallback';
|
|
|
|
/**
|
|
* Chunk size used for chunked uploads
|
|
* @see \Dropbox\API::chunkedUpload()
|
|
*/
|
|
private $chunkSize = 4194304;
|
|
|
|
/**
|
|
* Set the OAuth consumer object
|
|
* See 'General Notes' at the link below for information on access type
|
|
* @link https://www.dropbox.com/developers/reference/api
|
|
* @param OAuth\Consumer\ConsumerAbstract $OAuth
|
|
* @param string $root Dropbox app access type
|
|
*/
|
|
public function __construct(Dropbox_ConsumerAbstract $OAuth, $root = 'sandbox')
|
|
{
|
|
$this->OAuth = $OAuth;
|
|
$this->setRoot($root);
|
|
}
|
|
|
|
/**
|
|
* Set the root level
|
|
* @param mixed $root
|
|
* @throws Exception
|
|
* @return void
|
|
*/
|
|
public function setRoot($root)
|
|
{
|
|
if ($root !== 'sandbox' && $root !== 'dropbox') {
|
|
throw new Exception("Expected a root of either 'dropbox' or 'sandbox', got '$root'");
|
|
} else {
|
|
$this->root = $root;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves information about the user's account
|
|
* @return object stdClass
|
|
*/
|
|
public function accountInfo()
|
|
{
|
|
$response = $this->fetch('POST', self::API_URL, 'account/info');
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Uploads a physical file from disk
|
|
* Dropbox impose a 150MB limit to files uploaded via the API. If the file
|
|
* exceeds this limit or does not exist, an Exception will be thrown
|
|
* @param string $file Absolute path to the file to be uploaded
|
|
* @param string|bool $filename The destination filename of the uploaded file
|
|
* @param string $path Path to upload the file to, relative to root
|
|
* @param boolean $overwrite Should the file be overwritten? (Default: true)
|
|
* @return object stdClass
|
|
*/
|
|
public function putFile($file, $filename = false, $path = '', $overwrite = true)
|
|
{
|
|
if (file_exists($file)) {
|
|
if (filesize($file) <= 157286400) {
|
|
$call = 'files/' . $this->root . '/' . $this->encodePath($path);
|
|
// If no filename is provided we'll use the original filename
|
|
$filename = (is_string($filename)) ? $filename : basename($file);
|
|
$params = array(
|
|
'filename' => $filename,
|
|
'file' => '@' . str_replace('\\', '/', $file) . ';filename=' . $filename,
|
|
'overwrite' => (int) $overwrite,
|
|
);
|
|
$response = $this->fetch('POST', self::CONTENT_URL, $call, $params);
|
|
return $response;
|
|
}
|
|
throw new Exception('File exceeds 150MB upload limit');
|
|
}
|
|
|
|
// Throw an Exception if the file does not exist
|
|
throw new Exception('Local file ' . $file . ' does not exist');
|
|
}
|
|
|
|
/**
|
|
* Uploads file data from a stream
|
|
* Note: This function is experimental and requires further testing
|
|
* @todo Add filesize check
|
|
* @param resource $stream A readable stream created using fopen()
|
|
* @param string $filename The destination filename, including path
|
|
* @param boolean $overwrite Should the file be overwritten? (Default: true)
|
|
* @return array
|
|
*/
|
|
public function putStream($stream, $filename, $overwrite = true)
|
|
{
|
|
$this->OAuth->setInFile($stream);
|
|
$path = $this->encodePath($filename);
|
|
$call = 'files_put/' . $this->root . '/' . $path;
|
|
$params = array('overwrite' => (int) $overwrite);
|
|
$response = $this->fetch('PUT', self::CONTENT_URL, $call, $params);
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Uploads large files to Dropbox in mulitple chunks
|
|
* @param string $file Absolute path to the file to be uploaded
|
|
* @param string|bool $filename The destination filename of the uploaded file
|
|
* @param string $path Path to upload the file to, relative to root
|
|
* @param boolean $overwrite Should the file be overwritten? (Default: true)
|
|
* @param integer $offset position to seek to when opening the file
|
|
* @param string $uploadID existing upload_id to resume an upload
|
|
* @param string|array function to call back to upon each chunk
|
|
* @return stdClass
|
|
*/
|
|
public function chunkedUpload($file, $filename = false, $path = '', $overwrite = true, $offset = 0, $uploadID = null, $callback = null)
|
|
{
|
|
if (file_exists($file)) {
|
|
if ($handle = @fopen($file, 'r')) {
|
|
// Set initial upload ID and offset
|
|
if ($offset > 0) {
|
|
fseek($handle, $offset);
|
|
}
|
|
|
|
// Read from the file handle until EOF, uploading each chunk
|
|
while ($data = fread($handle, $this->chunkSize)) {
|
|
// Open a temporary file handle and write a chunk of data to it
|
|
$chunkHandle = fopen('php://temp', 'rw');
|
|
fwrite($chunkHandle, $data);
|
|
|
|
// Set the file, request parameters and send the request
|
|
$this->OAuth->setInFile($chunkHandle);
|
|
$params = array('upload_id' => $uploadID, 'offset' => $offset);
|
|
$response = $this->fetch('PUT', self::CONTENT_URL, 'chunked_upload', $params);
|
|
|
|
// On subsequent chunks, use the upload ID returned by the previous request
|
|
if (isset($response['body']->upload_id)) {
|
|
$uploadID = $response['body']->upload_id;
|
|
}
|
|
|
|
if (isset($response['body']->offset)) {
|
|
$offset = $response['body']->offset;
|
|
if ($callback) {
|
|
call_user_func($callback, $offset, $uploadID, $file);
|
|
}
|
|
}
|
|
|
|
// Close the file handle for this chunk
|
|
fclose($chunkHandle);
|
|
}
|
|
|
|
// Complete the chunked upload
|
|
$filename = (is_string($filename)) ? $filename : basename($file);
|
|
$call = 'commit_chunked_upload/' . $this->root . '/' . $this->encodePath($path . $filename);
|
|
$params = array('overwrite' => (int) $overwrite, 'upload_id' => $uploadID);
|
|
$response = $this->fetch('POST', self::CONTENT_URL, $call, $params);
|
|
return $response;
|
|
} else {
|
|
throw new Exception('Could not open ' . $file . ' for reading');
|
|
}
|
|
}
|
|
|
|
// Throw an Exception if the file does not exist
|
|
throw new Exception('Local file ' . $file . ' does not exist');
|
|
}
|
|
|
|
/**
|
|
* Downloads a file
|
|
* Returns the base filename, raw file data and mime type returned by Fileinfo
|
|
* @param string $file Path to file, relative to root, including path
|
|
* @param string $outFile Filename to write the downloaded file to
|
|
* @param string $revision The revision of the file to retrieve
|
|
* @param boolean $allow_resume - append to the file if it already exists
|
|
* @return array
|
|
*/
|
|
public function getFile($file, $outFile = false, $revision = null, $allow_resume = false)
|
|
{
|
|
// Only allow php response format for this call
|
|
if ($this->responseFormat !== 'php') {
|
|
throw new Exception('This method only supports the `php` response format');
|
|
}
|
|
|
|
$params = array('rev' => $revision);
|
|
|
|
$handle = null;
|
|
if ($outFile !== false) {
|
|
// Create a file handle if $outFile is specified
|
|
if ($allow_resume && file_exists($outFile)) {
|
|
if (!$handle = fopen($outFile, 'a')) {
|
|
throw new Exception("Unable to open file handle for $outFile");
|
|
} else {
|
|
$this->OAuth->setOutFile($handle);
|
|
$params['headers'] = array('Range: bytes='.filesize($outFile).'-');
|
|
}
|
|
}
|
|
elseif (!$handle = fopen($outFile, 'w')) {
|
|
throw new Exception("Unable to open file handle for $outFile");
|
|
} else {
|
|
$this->OAuth->setOutFile($handle);
|
|
}
|
|
}
|
|
|
|
$file = $this->encodePath($file);
|
|
$call = 'files/' . $this->root . '/' . $file;
|
|
$response = $this->fetch('GET', self::CONTENT_URL, $call, $params);
|
|
|
|
// Close the file handle if one was opened
|
|
if ($handle) fclose($handle);
|
|
|
|
return array(
|
|
'name' => ($outFile) ? $outFile : basename($file),
|
|
'mime' => $this->getMimeType(($outFile) ? $outFile : $response['body'], $outFile),
|
|
'meta' => json_decode($response['headers']['x-dropbox-metadata']),
|
|
'data' => $response['body'],
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Retrieves file and folder metadata
|
|
* @param string $path The path to the file/folder, relative to root
|
|
* @param string $rev Return metadata for a specific revision (Default: latest rev)
|
|
* @param int $limit Maximum number of listings to return
|
|
* @param string $hash Metadata hash to compare against
|
|
* @param bool $list Return contents field with response
|
|
* @param bool $deleted Include files/folders that have been deleted
|
|
* @return object stdClass
|
|
*/
|
|
public function metaData($path = null, $rev = null, $limit = 10000, $hash = false, $list = true, $deleted = false)
|
|
{
|
|
$call = 'metadata/' . $this->root . '/' . $this->encodePath($path);
|
|
$params = array(
|
|
'file_limit' => ($limit < 1) ? 1 : (($limit > 10000) ? 10000 : (int) $limit),
|
|
'hash' => (is_string($hash)) ? $hash : 0,
|
|
'list' => (int) $list,
|
|
'include_deleted' => (int) $deleted,
|
|
'rev' => (is_string($rev)) ? $rev : null,
|
|
);
|
|
$response = $this->fetch('POST', self::API_URL, $call, $params);
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Return "delta entries", intructing you how to update
|
|
* your application state to match the server's state
|
|
* Important: This method does not make changes to the application state
|
|
* @param null|string $cursor Used to keep track of your current state
|
|
* @return array Array of delta entries
|
|
*/
|
|
public function delta($cursor = null)
|
|
{
|
|
$call = 'delta';
|
|
$params = array('cursor' => $cursor);
|
|
$response = $this->fetch('POST', self::API_URL, $call, $params);
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Obtains metadata for the previous revisions of a file
|
|
* @param string Path to the file, relative to root
|
|
* @param integer Number of revisions to return (1-1000)
|
|
* @return array
|
|
*/
|
|
public function revisions($file, $limit = 10)
|
|
{
|
|
$call = 'revisions/' . $this->root . '/' . $this->encodePath($file);
|
|
$params = array(
|
|
'rev_limit' => ($limit < 1) ? 1 : (($limit > 1000) ? 1000 : (int) $limit),
|
|
);
|
|
$response = $this->fetch('GET', self::API_URL, $call, $params);
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Restores a file path to a previous revision
|
|
* @param string $file Path to the file, relative to root
|
|
* @param string $revision The revision of the file to restore
|
|
* @return object stdClass
|
|
*/
|
|
public function restore($file, $revision)
|
|
{
|
|
$call = 'restore/' . $this->root . '/' . $this->encodePath($file);
|
|
$params = array('rev' => $revision);
|
|
$response = $this->fetch('POST', self::API_URL, $call, $params);
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Returns metadata for all files and folders that match the search query
|
|
* @param mixed $query The search string. Must be at least 3 characters long
|
|
* @param string $path The path to the folder you want to search in
|
|
* @param integer $limit Maximum number of results to return (1-1000)
|
|
* @param boolean $deleted Include deleted files/folders in the search
|
|
* @return array
|
|
*/
|
|
public function search($query, $path = '', $limit = 1000, $deleted = false)
|
|
{
|
|
$call = 'search/' . $this->root . '/' . $this->encodePath($path);
|
|
$params = array(
|
|
'query' => $query,
|
|
'file_limit' => ($limit < 1) ? 1 : (($limit > 1000) ? 1000 : (int) $limit),
|
|
'include_deleted' => (int) $deleted,
|
|
);
|
|
$response = $this->fetch('GET', self::API_URL, $call, $params);
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Creates and returns a shareable link to files or folders
|
|
* The link returned is for a preview page from which the user an choose to
|
|
* download the file if they wish. For direct download links, see media().
|
|
* @param string $path The path to the file/folder you want a sharable link to
|
|
* @return object stdClass
|
|
*/
|
|
public function shares($path, $shortUrl = true)
|
|
{
|
|
$call = 'shares/' . $this->root . '/' .$this->encodePath($path);
|
|
$params = array('short_url' => ($shortUrl) ? 1 : 0);
|
|
$response = $this->fetch('POST', self::API_URL, $call, $params);
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Returns a link directly to a file
|
|
* @param string $path The path to the media file you want a direct link to
|
|
* @return object stdClass
|
|
*/
|
|
public function media($path)
|
|
{
|
|
$call = 'media/' . $this->root . '/' . $this->encodePath($path);
|
|
$response = $this->fetch('POST', self::API_URL, $call);
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Gets a thumbnail for an image
|
|
* @param string $file The path to the image you wish to thumbnail
|
|
* @param string $format The thumbnail format, either JPEG or PNG
|
|
* @param string $size The size of the thumbnail
|
|
* @return array
|
|
*/
|
|
public function thumbnails($file, $format = 'JPEG', $size = 'small')
|
|
{
|
|
// Only allow php response format for this call
|
|
if ($this->responseFormat !== 'php') {
|
|
throw new Exception('This method only supports the `php` response format');
|
|
}
|
|
|
|
$format = strtoupper($format);
|
|
// If $format is not 'PNG', default to 'JPEG'
|
|
if ($format != 'PNG') $format = 'JPEG';
|
|
|
|
$size = strtolower($size);
|
|
$sizes = array('s', 'm', 'l', 'xl', 'small', 'medium', 'large');
|
|
// If $size is not valid, default to 'small'
|
|
if (!in_array($size, $sizes)) $size = 'small';
|
|
|
|
$call = 'thumbnails/' . $this->root . '/' . $this->encodePath($file);
|
|
$params = array('format' => $format, 'size' => $size);
|
|
$response = $this->fetch('GET', self::CONTENT_URL, $call, $params);
|
|
|
|
return array(
|
|
'name' => basename($file),
|
|
'mime' => $this->getMimeType($response['body']),
|
|
'meta' => json_decode($response['headers']['x-dropbox-metadata']),
|
|
'data' => $response['body'],
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Creates and returns a copy_ref to a file
|
|
* This reference string can be used to copy that file to another user's
|
|
* Dropbox by passing it in as the from_copy_ref parameter on /fileops/copy
|
|
* @param $path File for which ref should be created, relative to root
|
|
* @return array
|
|
*/
|
|
public function copyRef($path)
|
|
{
|
|
$call = 'copy_ref/' . $this->root . '/' . $this->encodePath($path);
|
|
$response = $this->fetch('GET', self::API_URL, $call);
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Copies a file or folder to a new location
|
|
* @param string $from File or folder to be copied, relative to root
|
|
* @param string $to Destination path, relative to root
|
|
* @param null|string $fromCopyRef Must be used instead of the from_path
|
|
* @return object stdClass
|
|
*/
|
|
public function copy($from, $to, $fromCopyRef = null)
|
|
{
|
|
$call = 'fileops/copy';
|
|
$params = array(
|
|
'root' => $this->root,
|
|
'from_path' => $this->normalisePath($from),
|
|
'to_path' => $this->normalisePath($to),
|
|
);
|
|
|
|
if ($fromCopyRef) {
|
|
$params['from_path'] = null;
|
|
$params['from_copy_ref'] = $fromCopyRef;
|
|
}
|
|
|
|
$response = $this->fetch('POST', self::API_URL, $call, $params);
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Creates a folder
|
|
* @param string New folder to create relative to root
|
|
* @return object stdClass
|
|
*/
|
|
public function create($path)
|
|
{
|
|
$call = 'fileops/create_folder';
|
|
$params = array('root' => $this->root, 'path' => $this->normalisePath($path));
|
|
$response = $this->fetch('POST', self::API_URL, $call, $params);
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Deletes a file or folder
|
|
* @param string $path The path to the file or folder to be deleted
|
|
* @return object stdClass
|
|
*/
|
|
public function delete($path)
|
|
{
|
|
$call = 'fileops/delete';
|
|
$params = array('root' => $this->root, 'path' => $this->normalisePath($path));
|
|
$response = $this->fetch('POST', self::API_URL, $call, $params);
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Moves a file or folder to a new location
|
|
* @param string $from File or folder to be moved, relative to root
|
|
* @param string $to Destination path, relative to root
|
|
* @return object stdClass
|
|
*/
|
|
public function move($from, $to)
|
|
{
|
|
$call = 'fileops/move';
|
|
$params = array(
|
|
'root' => $this->root,
|
|
'from_path' => $this->normalisePath($from),
|
|
'to_path' => $this->normalisePath($to),
|
|
);
|
|
$response = $this->fetch('POST', self::API_URL, $call, $params);
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Intermediate fetch function
|
|
* @param string $method The HTTP method
|
|
* @param string $url The API endpoint
|
|
* @param string $call The API method to call
|
|
* @param array $params Additional parameters
|
|
* @return mixed
|
|
*/
|
|
private function fetch($method, $url, $call, array $params = array())
|
|
{
|
|
// Make the API call via the consumer
|
|
$response = $this->OAuth->fetch($method, $url, $call, $params);
|
|
|
|
// Format the response and return
|
|
switch ($this->responseFormat) {
|
|
case 'json':
|
|
return json_encode($response);
|
|
case 'jsonp':
|
|
$response = json_encode($response);
|
|
return $this->callback . '(' . $response . ')';
|
|
default:
|
|
return $response;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the API response format
|
|
* @param string $format One of php, json or jsonp
|
|
* @return void
|
|
*/
|
|
public function setResponseFormat($format)
|
|
{
|
|
$format = strtolower($format);
|
|
if (!in_array($format, array('php', 'json', 'jsonp'))) {
|
|
throw new Exception("Expected a format of php, json or jsonp, got '$format'");
|
|
} else {
|
|
$this->responseFormat = $format;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the chunk size for chunked uploads
|
|
* If $chunkSize is empty, set to 4194304 bytes (4 MB)
|
|
* @see \Dropbox\API\chunkedUpload()
|
|
*/
|
|
public function setChunkSize($chunkSize = 4194304)
|
|
{
|
|
if (!is_int($chunkSize)) {
|
|
throw new Exception('Expecting chunk size to be an integer, got ' . gettype($chunkSize));
|
|
} elseif ($chunkSize > 157286400) {
|
|
throw new Exception('Chunk size must not exceed 157286400 bytes, got ' . $chunkSize);
|
|
} else {
|
|
$this->chunkSize = $chunkSize;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the JSONP callback function
|
|
* @param string $function
|
|
* @return void
|
|
*/
|
|
public function setCallback($function)
|
|
{
|
|
$this->callback = $function;
|
|
}
|
|
|
|
/**
|
|
* Get the mime type of downloaded file
|
|
* If the Fileinfo extension is not loaded, return false
|
|
* @param string $data File contents as a string or filename
|
|
* @param string $isFilename Is $data a filename?
|
|
* @return boolean|string Mime type and encoding of the file
|
|
*/
|
|
private function getMimeType($data, $isFilename = false)
|
|
{
|
|
if (extension_loaded('fileinfo')) {
|
|
$finfo = new finfo(FILEINFO_MIME);
|
|
if ($isFilename !== false) {
|
|
return $finfo->file($data);
|
|
}
|
|
return $finfo->buffer($data);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Trim the path of forward slashes and replace
|
|
* consecutive forward slashes with a single slash
|
|
* @param string $path The path to normalise
|
|
* @return string
|
|
*/
|
|
private function normalisePath($path)
|
|
{
|
|
$path = preg_replace('#/+#', '/', trim($path, '/'));
|
|
return $path;
|
|
}
|
|
|
|
/**
|
|
* Encode the path, then replace encoded slashes
|
|
* with literal forward slash characters
|
|
* @param string $path The path to encode
|
|
* @return string
|
|
*/
|
|
private function encodePath($path)
|
|
{
|
|
$path = $this->normalisePath($path);
|
|
$path = str_replace('%2F', '/', rawurlencode($path));
|
|
return $path;
|
|
}
|
|
}
|