mirror of
https://github.com/Yetangitu/ampache
synced 2025-10-03 09:49:30 +02:00
Add Dropbox catalog
Fix Google Music streaming
This commit is contained in:
parent
305b9294ce
commit
0461f2218f
47 changed files with 4747 additions and 267 deletions
|
@ -47,7 +47,7 @@ switch ($_REQUEST['action']) {
|
||||||
if ($_REQUEST['catalogs'] ) {
|
if ($_REQUEST['catalogs'] ) {
|
||||||
foreach ($_REQUEST['catalogs'] as $catalog_id) {
|
foreach ($_REQUEST['catalogs'] as $catalog_id) {
|
||||||
$catalog = Catalog::create_from_id($catalog_id);
|
$catalog = Catalog::create_from_id($catalog_id);
|
||||||
$catalog->add_to_catalog();
|
$catalog->add_to_catalog($_POST);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$url = Config::get('web_path') . '/admin/catalog.php';
|
$url = Config::get('web_path') . '/admin/catalog.php';
|
||||||
|
@ -180,7 +180,7 @@ switch ($_REQUEST['action']) {
|
||||||
if ($_POST['add_path'] != '/' AND strlen($_POST['add_path'])) {
|
if ($_POST['add_path'] != '/' AND strlen($_POST['add_path'])) {
|
||||||
if ($catalog_id = Catalog_local::get_from_path($_POST['add_path'])) {
|
if ($catalog_id = Catalog_local::get_from_path($_POST['add_path'])) {
|
||||||
$catalog = Catalog::create_from_id($catalog_id);
|
$catalog = Catalog::create_from_id($catalog_id);
|
||||||
$catalog->run_add(array('subdirectory'=>$_POST['add_path']));
|
$catalog->add_to_catalog(array('subdirectory'=>$_POST['add_path']));
|
||||||
}
|
}
|
||||||
} // end if add
|
} // end if add
|
||||||
|
|
||||||
|
@ -226,7 +226,7 @@ switch ($_REQUEST['action']) {
|
||||||
$catalog = Catalog::create_from_id($catalog_id);
|
$catalog = Catalog::create_from_id($catalog_id);
|
||||||
|
|
||||||
// Run our initial add
|
// Run our initial add
|
||||||
$catalog->run_add($_POST);
|
$catalog->add_to_catalog($_POST);
|
||||||
|
|
||||||
UI::show_box_top(T_('Catalog Created'), 'box box_catalog_created');
|
UI::show_box_top(T_('Catalog Created'), 'box box_catalog_created');
|
||||||
echo "<h2>" . T_('Catalog Created') . "</h2>";
|
echo "<h2>" . T_('Catalog Created') . "</h2>";
|
||||||
|
|
|
@ -52,17 +52,32 @@ abstract class Catalog extends database_object {
|
||||||
abstract public function get_type();
|
abstract public function get_type();
|
||||||
abstract public function get_description();
|
abstract public function get_description();
|
||||||
abstract public function get_version();
|
abstract public function get_version();
|
||||||
|
abstract public function get_create_help();
|
||||||
abstract public function is_installed();
|
abstract public function is_installed();
|
||||||
abstract public function install();
|
abstract public function install();
|
||||||
abstract public function uninstall();
|
abstract public function add_to_catalog($options = null);
|
||||||
abstract public function run_add($options);
|
|
||||||
abstract public function add_to_catalog();
|
|
||||||
abstract public function verify_catalog_proc();
|
abstract public function verify_catalog_proc();
|
||||||
abstract public function clean_catalog_proc();
|
abstract public function clean_catalog_proc();
|
||||||
abstract public function catalog_fields();
|
abstract public function catalog_fields();
|
||||||
abstract public function get_rel_path($file_path);
|
abstract public function get_rel_path($file_path);
|
||||||
abstract public function prepare_media($media);
|
abstract public function prepare_media($media);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* uninstall
|
||||||
|
* This removes the remote catalog
|
||||||
|
*/
|
||||||
|
public function uninstall() {
|
||||||
|
|
||||||
|
$sql = "DELETE FROM `catalog` WHERE `catalog_type` = ?";
|
||||||
|
$db_results = Dba::query($sql, array($this->get_type()));
|
||||||
|
|
||||||
|
$sql = "DROP TABLE `catalog_" . $this->get_type() ."`";
|
||||||
|
$db_results = Dba::query($sql);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} // uninstall
|
||||||
|
|
||||||
public static function create_from_id($id) {
|
public static function create_from_id($id) {
|
||||||
|
|
||||||
$sql = 'SELECT `catalog_type` FROM `catalog` WHERE `id` = ?';
|
$sql = 'SELECT `catalog_type` FROM `catalog` WHERE `id` = ?';
|
||||||
|
@ -120,10 +135,25 @@ abstract class Catalog extends database_object {
|
||||||
$seltypes .= '<option value="' . $type . '">' . $type . '</option>';
|
$seltypes .= '<option value="' . $type . '">' . $type . '</option>';
|
||||||
echo "type_fields['" . $type . "'] = \"";
|
echo "type_fields['" . $type . "'] = \"";
|
||||||
$fields = $catalog->catalog_fields();
|
$fields = $catalog->catalog_fields();
|
||||||
|
$help = $catalog->get_create_help();
|
||||||
|
if (!empty($help)) {
|
||||||
|
echo "<tr><td></td><td>" . $help . "</td></tr>";
|
||||||
|
}
|
||||||
foreach ($fields as $key=>$field) {
|
foreach ($fields as $key=>$field) {
|
||||||
echo "<tr><td style='width: 25%;'>" . $field['description'] . ":</td><td><input type='";
|
echo "<tr><td style='width: 25%;'>" . $field['description'] . ":</td><td>";
|
||||||
echo ($field['type'] == 'password') ? 'password' : 'text';
|
|
||||||
echo "' size='60' name='" . $key . "' /></td></tr>";
|
switch ($field['type']) {
|
||||||
|
case 'checkbox':
|
||||||
|
echo "<input type='checkbox' size='10' name='" . $key . "' value='1' " . (($field['value']) ? 'checked' : '') . "/>";
|
||||||
|
break;
|
||||||
|
case 'password':
|
||||||
|
echo "<input type='password' size='60' name='" . $key . "' value='" . $field['value'] . "' />";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
echo "<input type='text' size='60' name='" . $key . "' value='" . $field['value'] . "' />";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
echo "</td></tr>";
|
||||||
}
|
}
|
||||||
echo "\";";
|
echo "\";";
|
||||||
}
|
}
|
||||||
|
@ -170,6 +200,22 @@ abstract class Catalog extends database_object {
|
||||||
|
|
||||||
} // get_catalog_types
|
} // get_catalog_types
|
||||||
|
|
||||||
|
public static function is_audio_file($file) {
|
||||||
|
$pattern = "/\.(" . Config::get('catalog_file_pattern');
|
||||||
|
if ($options['parse_m3u']) {
|
||||||
|
$pattern .= "|m3u)$/i";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$pattern .= ")$/i";
|
||||||
|
}
|
||||||
|
return preg_match($pattern, $file);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function is_video_file($file) {
|
||||||
|
$video_pattern = "/\.(" . Config::get('catalog_video_pattern') . ")$/i";
|
||||||
|
return preg_match($video_pattern, $file);
|
||||||
|
}
|
||||||
|
|
||||||
public function get_info($id, $table = 'catalog') {
|
public function get_info($id, $table = 'catalog') {
|
||||||
$info = parent::get_info($id, $table);
|
$info = parent::get_info($id, $table);
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,8 @@ class Core {
|
||||||
* more than we need.
|
* more than we need.
|
||||||
*/
|
*/
|
||||||
public static function autoload($class) {
|
public static function autoload($class) {
|
||||||
|
// Ignore class with namespace, not used by Ampache
|
||||||
|
if (strpos($class, '\\') === false) {
|
||||||
$file = Config::get('prefix') . '/lib/class/' .
|
$file = Config::get('prefix') . '/lib/class/' .
|
||||||
strtolower($class) . '.class.php';
|
strtolower($class) . '.class.php';
|
||||||
|
|
||||||
|
@ -63,6 +65,7 @@ class Core {
|
||||||
debug_event('autoload', "'$class' not found!", 1);
|
debug_event('autoload', "'$class' not found!", 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* form_register
|
* form_register
|
||||||
|
|
|
@ -38,6 +38,7 @@ class vainfo {
|
||||||
|
|
||||||
protected $_raw = array();
|
protected $_raw = array();
|
||||||
protected $_getID3 = '';
|
protected $_getID3 = '';
|
||||||
|
protected $_forcedSize = 0;
|
||||||
|
|
||||||
protected $_file_encoding = '';
|
protected $_file_encoding = '';
|
||||||
protected $_file_pattern = '';
|
protected $_file_pattern = '';
|
||||||
|
@ -52,8 +53,9 @@ class vainfo {
|
||||||
* This function just sets up the class, it doesn't pull the information.
|
* This function just sets up the class, it doesn't pull the information.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public function __construct($file, $encoding = null, $encoding_id3v1 = null, $encoding_id3v2 = null, $dir_pattern, $file_pattern) {
|
public function __construct($file, $encoding = null, $encoding_id3v1 = null, $encoding_id3v2 = null, $dir_pattern = '', $file_pattern ='', $islocal = true) {
|
||||||
|
|
||||||
|
$this->islocal = $islocal;
|
||||||
$this->filename = $file;
|
$this->filename = $file;
|
||||||
$this->encoding = $encoding ?: Config::get('site_charset');
|
$this->encoding = $encoding ?: Config::get('site_charset');
|
||||||
|
|
||||||
|
@ -71,6 +73,7 @@ class vainfo {
|
||||||
}
|
}
|
||||||
$this->_pathinfo['extension'] = strtolower($this->_pathinfo['extension']);
|
$this->_pathinfo['extension'] = strtolower($this->_pathinfo['extension']);
|
||||||
|
|
||||||
|
if ($this->islocal) {
|
||||||
// Initialize getID3 engine
|
// Initialize getID3 engine
|
||||||
$this->_getID3 = new getID3();
|
$this->_getID3 = new getID3();
|
||||||
|
|
||||||
|
@ -131,6 +134,11 @@ class vainfo {
|
||||||
|
|
||||||
$this->_getID3->encoding_id3v1 = $this->encoding_id3v1;
|
$this->_getID3->encoding_id3v1 = $this->encoding_id3v1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function forceSize($size) {
|
||||||
|
$this->_forcedSize = $size;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* _detect_encoding
|
* _detect_encoding
|
||||||
|
@ -180,12 +188,14 @@ class vainfo {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->islocal) {
|
||||||
try {
|
try {
|
||||||
$this->_raw = $this->_getID3->analyze($this->filename);
|
$this->_raw = $this->_getID3->analyze($this->filename);
|
||||||
}
|
}
|
||||||
catch (Exception $error) {
|
catch (Exception $error) {
|
||||||
debug_event('getID2', 'Unable to catalog file: ' . $error->message, 1);
|
debug_event('getID2', 'Unable to catalog file: ' . $error->message, 1);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Figure out what type of file we are dealing with */
|
/* Figure out what type of file we are dealing with */
|
||||||
$this->type = $this->_get_type();
|
$this->type = $this->_get_type();
|
||||||
|
@ -196,7 +206,7 @@ class vainfo {
|
||||||
$this->tags['filename'] = $this->_parse_filename($this->filename);
|
$this->tags['filename'] = $this->_parse_filename($this->filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (in_array('getID3', $enabled_sources)) {
|
if (in_array('getID3', $enabled_sources) && $this->islocal) {
|
||||||
$this->tags['getID3'] = $this->_get_tags();
|
$this->tags['getID3'] = $this->_get_tags();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -460,10 +470,10 @@ class vainfo {
|
||||||
$parsed['bitrate'] = $tags['audio']['bitrate'];
|
$parsed['bitrate'] = $tags['audio']['bitrate'];
|
||||||
$parsed['channels'] = intval($tags['audio']['channels']);
|
$parsed['channels'] = intval($tags['audio']['channels']);
|
||||||
$parsed['rate'] = intval($tags['audio']['sample_rate']);
|
$parsed['rate'] = intval($tags['audio']['sample_rate']);
|
||||||
$parsed['size'] = intval($tags['filesize']);
|
$parsed['size'] = $this->_forcedSize ?: intval($tags['filesize']);
|
||||||
$parsed['encoding'] = $tags['encoding'];
|
$parsed['encoding'] = $tags['encoding'];
|
||||||
$parsed['mime'] = $tags['mime_type'];
|
$parsed['mime'] = $tags['mime_type'];
|
||||||
$parsed['time'] = $tags['playtime_seconds'];
|
$parsed['time'] = ($this->_forcedSize ? ((($this->_forcedSize - $tags['avdataoffset']) * 8) / $tags['bitrate']) : $tags['playtime_seconds']);
|
||||||
$parsed['video_codec'] = $tags['video']['fourcc'];
|
$parsed['video_codec'] = $tags['video']['fourcc'];
|
||||||
$parsed['audio_codec'] = $tags['audio']['dataformat'];
|
$parsed['audio_codec'] = $tags['audio']['dataformat'];
|
||||||
$parsed['resolution_x'] = $tags['video']['resolution_x'];
|
$parsed['resolution_x'] = $tags['video']['resolution_x'];
|
||||||
|
@ -725,6 +735,7 @@ class vainfo {
|
||||||
*/
|
*/
|
||||||
private function _parse_filename($filename) {
|
private function _parse_filename($filename) {
|
||||||
|
|
||||||
|
$origin = $filename;
|
||||||
$results = array();
|
$results = array();
|
||||||
|
|
||||||
// Correctly detect the slash we need to use here
|
// Correctly detect the slash we need to use here
|
||||||
|
@ -738,6 +749,13 @@ class vainfo {
|
||||||
// Combine the patterns
|
// Combine the patterns
|
||||||
$pattern = preg_quote($this->_dir_pattern) . $slash_type . preg_quote($this->_file_pattern);
|
$pattern = preg_quote($this->_dir_pattern) . $slash_type . preg_quote($this->_file_pattern);
|
||||||
|
|
||||||
|
// Remove first left directories from filename to match pattern
|
||||||
|
$cntslash = substr_count($pattern, $slash_type) + 1;
|
||||||
|
$filepart = explode($slash_type, $filename);
|
||||||
|
if (count($filepart) > $cntslash) {
|
||||||
|
$filename = implode($slash_type, array_slice($filepart, count($filepart) - $cntslash));
|
||||||
|
}
|
||||||
|
|
||||||
// Pull out the pattern codes into an array
|
// Pull out the pattern codes into an array
|
||||||
preg_match_all('/\%\w/', $pattern, $elements);
|
preg_match_all('/\%\w/', $pattern, $elements);
|
||||||
|
|
||||||
|
@ -764,7 +782,9 @@ class vainfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
$results['title'] = $results['title'] ?: basename($filename);
|
$results['title'] = $results['title'] ?: basename($filename);
|
||||||
$results['size'] = filesize($filename);
|
if ($this->islocal) {
|
||||||
|
$results['size'] = filesize($origin);
|
||||||
|
}
|
||||||
|
|
||||||
return $results;
|
return $results;
|
||||||
}
|
}
|
||||||
|
|
238
modules/Dropbox/AppInfo.php
Normal file
238
modules/Dropbox/AppInfo.php
Normal file
|
@ -0,0 +1,238 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Your app's API key and secret.
|
||||||
|
*/
|
||||||
|
final class AppInfo
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Your Dropbox <em>app key</em> (OAuth calls this the <em>consumer key</em>). You can
|
||||||
|
* create an app key and secret on the <a href="http://dropbox.com/developers/apps">Dropbox developer website</a>.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function getKey() { return $this->key; }
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Your Dropbox <em>app secret</em> (OAuth calls this the <em>consumer secret</em>). You can
|
||||||
|
* create an app key and secret on the <a href="http://dropbox.com/developers/apps">Dropbox developer website</a>.
|
||||||
|
*
|
||||||
|
* Make sure that this is kept a secret. Someone with your app secret can impesonate your
|
||||||
|
* application. People sometimes ask for help on the Dropbox API forums and
|
||||||
|
* copy/paste code that includes their app secret. Do not do that.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function getSecret() { return $this->secret; }
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $secret;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The set of servers your app will use. This defaults to the standard Dropbox servers
|
||||||
|
* {@link Host::getDefault}.
|
||||||
|
*
|
||||||
|
* @return Host
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function getHost() { return $this->host; }
|
||||||
|
|
||||||
|
/** @var Host */
|
||||||
|
private $host;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* See {@link getKey()}
|
||||||
|
* @param string $secret
|
||||||
|
* See {@link getSecret()}
|
||||||
|
*/
|
||||||
|
function __construct($key, $secret)
|
||||||
|
{
|
||||||
|
self::checkKeyArg($key);
|
||||||
|
self::checkSecretArg($secret);
|
||||||
|
|
||||||
|
$this->key = $key;
|
||||||
|
$this->secret = $secret;
|
||||||
|
|
||||||
|
// The $host parameter is sort of internal. We don't include it in the param list because
|
||||||
|
// we don't want it to be included in the documentation. Use PHP arg list hacks to get at
|
||||||
|
// it.
|
||||||
|
$host = null;
|
||||||
|
if (\func_num_args() == 3) {
|
||||||
|
$host = \func_get_arg(2);
|
||||||
|
Host::checkArgOrNull("host", $host);
|
||||||
|
}
|
||||||
|
if ($host === null) {
|
||||||
|
$host = Host::getDefault();
|
||||||
|
}
|
||||||
|
$this->host = $host;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a JSON file containing information about your app. At a minimum, the file must include
|
||||||
|
* the "key" and "secret" fields. Run 'php authorize.php' in the examples directory
|
||||||
|
* for details about what this file should look like.
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* Path to a JSON file
|
||||||
|
*
|
||||||
|
* @return AppInfo
|
||||||
|
*
|
||||||
|
* @throws AppInfoLoadException
|
||||||
|
*/
|
||||||
|
static function loadFromJsonFile($path)
|
||||||
|
{
|
||||||
|
list($rawJson, $appInfo) = self::loadFromJsonFileWithRaw($path);
|
||||||
|
return $appInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a JSON file containing information about your app. At a minimum, the file must include
|
||||||
|
* the "key" and "secret" fields. Run 'php authorize.php' in the examples directory
|
||||||
|
* for details about what this file should look like.
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* Path to a JSON file
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* A list of two items. The first is a PHP array representation of the raw JSON, the second
|
||||||
|
* is an AppInfo object that is the parsed version of the JSON.
|
||||||
|
*
|
||||||
|
* @throws AppInfoLoadException
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
static function loadFromJsonFileWithRaw($path)
|
||||||
|
{
|
||||||
|
if (!file_exists($path)) {
|
||||||
|
throw new AppInfoLoadException("File doesn't exist: \"$path\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
$str = file_get_contents($path);
|
||||||
|
$jsonArr = json_decode($str, TRUE);
|
||||||
|
|
||||||
|
if (is_null($jsonArr)) {
|
||||||
|
throw new AppInfoLoadException("JSON parse error: \"$path\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
$appInfo = self::loadFromJson($jsonArr);
|
||||||
|
|
||||||
|
return array($jsonArr, $appInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a JSON object to build an AppInfo object. If you would like to load this from a file,
|
||||||
|
* use the loadFromJsonFile() method.
|
||||||
|
*
|
||||||
|
* @param array $jsonArr Output from json_decode($str, TRUE)
|
||||||
|
*
|
||||||
|
* @return AppInfo
|
||||||
|
*
|
||||||
|
* @throws AppInfoLoadException
|
||||||
|
*/
|
||||||
|
static function loadFromJson($jsonArr)
|
||||||
|
{
|
||||||
|
if (!is_array($jsonArr)) {
|
||||||
|
throw new AppInfoLoadException("Expecting JSON object, got something else");
|
||||||
|
}
|
||||||
|
|
||||||
|
$requiredKeys = array("key", "secret");
|
||||||
|
foreach ($requiredKeys as $key) {
|
||||||
|
if (!array_key_exists($key, $jsonArr)) {
|
||||||
|
throw new AppInfoLoadException("Missing field \"$key\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_string($jsonArr[$key])) {
|
||||||
|
throw new AppInfoLoadException("Expecting field \"$key\" to be a string");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check app_key and app_secret
|
||||||
|
$appKey = $jsonArr["key"];
|
||||||
|
$appSecret = $jsonArr["secret"];
|
||||||
|
|
||||||
|
$tokenErr = self::getTokenPartError($appKey);
|
||||||
|
if (!is_null($tokenErr)) {
|
||||||
|
throw new AppInfoLoadException("Field \"key\" doesn't look like a valid app key: $tokenErr");
|
||||||
|
}
|
||||||
|
|
||||||
|
$tokenErr = self::getTokenPartError($appSecret);
|
||||||
|
if (!is_null($tokenErr)) {
|
||||||
|
throw new AppInfoLoadException("Field \"secret\" doesn't look like a valid app secret: $tokenErr");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for the optional 'host' field
|
||||||
|
if (!array_key_exists('host', $jsonArr)) {
|
||||||
|
$host = null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$baseHost = $jsonArr["host"];
|
||||||
|
if (!is_string($baseHost)) {
|
||||||
|
throw new AppInfoLoadException("Optional field \"host\" must be a string");
|
||||||
|
}
|
||||||
|
|
||||||
|
$api = "api-$baseHost";
|
||||||
|
$content = "api-content-$baseHost";
|
||||||
|
$web = "meta-$baseHost";
|
||||||
|
|
||||||
|
$host = new Host($api, $content, $web);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AppInfo($appKey, $appSecret, $host);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this to check that a function argument is of type <code>AppInfo</code>
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
static function checkArg($argName, $argValue)
|
||||||
|
{
|
||||||
|
if (!($argValue instanceof self)) Checker::throwError($argName, $argValue, __CLASS__);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this to check that a function argument is either <code>null</code> or of type
|
||||||
|
* <code>AppInfo</code>.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
static function checkArgOrNull($argName, $argValue)
|
||||||
|
{
|
||||||
|
if ($argValue === null) return;
|
||||||
|
if (!($argValue instanceof self)) Checker::throwError($argName, $argValue, __CLASS__);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
static function getTokenPartError($s)
|
||||||
|
{
|
||||||
|
if ($s === null) return "can't be null";
|
||||||
|
if (strlen($s) === 0) return "can't be empty";
|
||||||
|
if (strstr($s, ' ')) return "can't contain a space";
|
||||||
|
return null; // 'null' means "no error"
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
static function checkKeyArg($key)
|
||||||
|
{
|
||||||
|
$error = self::getTokenPartError($key);
|
||||||
|
if ($error === null) return;
|
||||||
|
throw new \InvalidArgumentException("Bad 'key': \"$key\": $error.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
static function checkSecretArg($secret)
|
||||||
|
{
|
||||||
|
$error = self::getTokenPartError($secret);
|
||||||
|
if ($error === null) return;
|
||||||
|
throw new \InvalidArgumentException("Bad 'secret': \"$secret\": $error.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
18
modules/Dropbox/AppInfoLoadException.php
Normal file
18
modules/Dropbox/AppInfoLoadException.php
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown by the <code>AppInfo::loadXXX</code> methods if something goes wrong.
|
||||||
|
*/
|
||||||
|
final class AppInfoLoadException extends \Exception
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $message
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function __construct($message)
|
||||||
|
{
|
||||||
|
parent::__construct($message);
|
||||||
|
}
|
||||||
|
}
|
58
modules/Dropbox/ArrayEntryStore.php
Normal file
58
modules/Dropbox/ArrayEntryStore.php
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that gives get/put/clear access to a single entry in an array.
|
||||||
|
*/
|
||||||
|
class ArrayEntryStore implements ValueStore
|
||||||
|
{
|
||||||
|
private $array;
|
||||||
|
private $key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param array $array
|
||||||
|
* The array that we'll be accessing.
|
||||||
|
*
|
||||||
|
* @param object $key
|
||||||
|
* The key for the array element we'll be accessing.
|
||||||
|
*/
|
||||||
|
function __construct(&$array, $key)
|
||||||
|
{
|
||||||
|
$this->array = &$array;
|
||||||
|
$this->key = $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the entry's current value or <code>null</code> if nothing is set.
|
||||||
|
*
|
||||||
|
* @return object
|
||||||
|
*/
|
||||||
|
function get()
|
||||||
|
{
|
||||||
|
if (isset($this->array[$this->key])) {
|
||||||
|
return $this->array[$this->key];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the array entry to the given value.
|
||||||
|
*
|
||||||
|
* @param object $value
|
||||||
|
*/
|
||||||
|
function set($value)
|
||||||
|
{
|
||||||
|
$this->array[$this->key] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the entry.
|
||||||
|
*/
|
||||||
|
function clear()
|
||||||
|
{
|
||||||
|
unset($this->array[$this->key]);
|
||||||
|
}
|
||||||
|
}
|
85
modules/Dropbox/AuthInfo.php
Normal file
85
modules/Dropbox/AuthInfo.php
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class contains methods to load an AppInfo and AccessToken from a JSON file.
|
||||||
|
* This can help simplify simple scripts (such as the example programs that come with the
|
||||||
|
* SDK) but is probably not useful in typical Dropbox API apps.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
final class AuthInfo
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Loads a JSON file containing authorization information for your app. 'php authorize.php'
|
||||||
|
* in the examples directory for details about what this file should look like.
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* Path to a JSON file
|
||||||
|
* @return array
|
||||||
|
* A <code>list(string $accessToken, Host $host)</code>.
|
||||||
|
*
|
||||||
|
* @throws AuthInfoLoadException
|
||||||
|
*/
|
||||||
|
static function loadFromJsonFile($path)
|
||||||
|
{
|
||||||
|
if (!file_exists($path)) {
|
||||||
|
throw new AuthInfoLoadException("File doesn't exist: \"$path\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
$str = file_get_contents($path);
|
||||||
|
$jsonArr = json_decode($str, TRUE);
|
||||||
|
|
||||||
|
if (is_null($jsonArr)) {
|
||||||
|
throw new AuthInfoLoadException("JSON parse error: \"$path\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::loadFromJson($jsonArr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a JSON object to build an AuthInfo object. If you would like to load this from a file,
|
||||||
|
* please use the @see loadFromJsonFile method.
|
||||||
|
*
|
||||||
|
* @param array $jsonArr
|
||||||
|
* A parsed JSON object, typcally the result of json_decode(..., TRUE).
|
||||||
|
* @return array
|
||||||
|
* A <code>list(string $accessToken, Host $host)</code>.
|
||||||
|
*
|
||||||
|
* @throws AuthInfoLoadException
|
||||||
|
*/
|
||||||
|
private static function loadFromJson($jsonArr)
|
||||||
|
{
|
||||||
|
if (!is_array($jsonArr)) {
|
||||||
|
throw new AuthInfoLoadException("Expecting JSON object, found something else");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check access_token
|
||||||
|
if (!array_key_exists('access_token', $jsonArr)) {
|
||||||
|
throw new AuthInfoLoadException("Missing field \"access_token\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
$accessToken = $jsonArr['access_token'];
|
||||||
|
if (!is_string($accessToken)) {
|
||||||
|
throw new AuthInfoLoadException("Expecting field \"access_token\" to be a string");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for the optional 'host' field
|
||||||
|
if (!array_key_exists('host', $jsonArr)) {
|
||||||
|
$host = null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$baseHost = $jsonArr["host"];
|
||||||
|
if (!is_string($baseHost)) {
|
||||||
|
throw new AuthInfoLoadException("Optional field \"host\" must be a string");
|
||||||
|
}
|
||||||
|
|
||||||
|
$api = "api-$baseHost";
|
||||||
|
$content = "api-content-$baseHost";
|
||||||
|
$web = "meta-$baseHost";
|
||||||
|
|
||||||
|
$host = new Host($api, $content, $web);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array($accessToken, $host);
|
||||||
|
}
|
||||||
|
}
|
18
modules/Dropbox/AuthInfoLoadException.php
Normal file
18
modules/Dropbox/AuthInfoLoadException.php
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown by the <code>AuthInfo::loadXXX</code> methods if something goes wrong.
|
||||||
|
*/
|
||||||
|
final class AuthInfoLoadException extends \Exception
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $message
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function __construct($message)
|
||||||
|
{
|
||||||
|
parent::__construct($message);
|
||||||
|
}
|
||||||
|
}
|
94
modules/Dropbox/Checker.php
Normal file
94
modules/Dropbox/Checker.php
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper functions to validate arguments.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class Checker
|
||||||
|
{
|
||||||
|
static function throwError($argName, $argValue, $expectedTypeName)
|
||||||
|
{
|
||||||
|
if ($argValue === null) throw new \InvalidArgumentException("'$argName' must not be null");
|
||||||
|
|
||||||
|
if (is_object($argValue)) {
|
||||||
|
// Class type.
|
||||||
|
$argTypeName = get_class($argValue);
|
||||||
|
} else {
|
||||||
|
// Built-in type.
|
||||||
|
$argTypeName = gettype($argValue);
|
||||||
|
}
|
||||||
|
throw new \InvalidArgumentException("'$argName' has bad type; expecting $expectedTypeName, got $argTypeName");
|
||||||
|
}
|
||||||
|
|
||||||
|
static function argResource($argName, $argValue)
|
||||||
|
{
|
||||||
|
if (!is_resource($argValue)) self::throwError($argName, $argValue, "resource");
|
||||||
|
}
|
||||||
|
|
||||||
|
static function argCallable($argName, $argValue)
|
||||||
|
{
|
||||||
|
if (!is_callable($argValue)) self::throwError($argName, $argValue, "callable");
|
||||||
|
}
|
||||||
|
|
||||||
|
static function argBool($argName, $argValue)
|
||||||
|
{
|
||||||
|
if (!is_bool($argValue)) self::throwError($argName, $argValue, "boolean");
|
||||||
|
}
|
||||||
|
|
||||||
|
static function argArray($argName, $argValue)
|
||||||
|
{
|
||||||
|
if (!is_array($argValue)) self::throwError($argName, $argValue, "array");
|
||||||
|
}
|
||||||
|
|
||||||
|
static function argString($argName, $argValue)
|
||||||
|
{
|
||||||
|
if (!is_string($argValue)) self::throwError($argName, $argValue, "string");
|
||||||
|
}
|
||||||
|
|
||||||
|
static function argStringOrNull($argName, $argValue)
|
||||||
|
{
|
||||||
|
if ($argValue === null) return;
|
||||||
|
if (!is_string($argValue)) self::throwError($argName, $argValue, "string");
|
||||||
|
}
|
||||||
|
|
||||||
|
static function argStringNonEmpty($argName, $argValue)
|
||||||
|
{
|
||||||
|
if (!is_string($argValue)) self::throwError($argName, $argValue, "string");
|
||||||
|
if (strlen($argValue) === 0) throw new \InvalidArgumentException("'$argName' must be non-empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
static function argStringNonEmptyOrNull($argName, $argValue)
|
||||||
|
{
|
||||||
|
if ($argValue === null) return;
|
||||||
|
if (!is_string($argValue)) self::throwError($argName, $argValue, "string");
|
||||||
|
if (strlen($argValue) === 0) throw new \InvalidArgumentException("'$argName' must be non-empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
static function argNat($argName, $argValue)
|
||||||
|
{
|
||||||
|
if (!is_int($argValue)) self::throwError($argName, $argValue, "int");
|
||||||
|
if ($argValue < 0) throw new \InvalidArgumentException("'$argName' must be non-negative (you passed in $argValue)");
|
||||||
|
}
|
||||||
|
|
||||||
|
static function argNatOrNull($argName, $argValue)
|
||||||
|
{
|
||||||
|
if ($argValue === null) return;
|
||||||
|
if (!is_int($argValue)) self::throwError($argName, $argValue, "int");
|
||||||
|
if ($argValue < 0) throw new \InvalidArgumentException("'$argName' must be non-negative (you passed in $argValue)");
|
||||||
|
}
|
||||||
|
|
||||||
|
static function argIntPositive($argName, $argValue)
|
||||||
|
{
|
||||||
|
if (!is_int($argValue)) self::throwError($argName, $argValue, "int");
|
||||||
|
if ($argValue < 1) throw new \InvalidArgumentException("'$argName' must be positive (you passed in $argValue)");
|
||||||
|
}
|
||||||
|
|
||||||
|
static function argIntPositiveOrNull($argName, $argValue)
|
||||||
|
{
|
||||||
|
if ($argValue === null) return;
|
||||||
|
if (!is_int($argValue)) self::throwError($argName, $argValue, "int");
|
||||||
|
if ($argValue < 1) throw new \InvalidArgumentException("'$argName' must be positive (you passed in $argValue)");
|
||||||
|
}
|
||||||
|
}
|
1366
modules/Dropbox/Client.php
Normal file
1366
modules/Dropbox/Client.php
Normal file
File diff suppressed because it is too large
Load diff
92
modules/Dropbox/Curl.php
Normal file
92
modules/Dropbox/Curl.php
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A minimal wrapper around a cURL handle.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class Curl
|
||||||
|
{
|
||||||
|
/** @var resource */
|
||||||
|
public $handle;
|
||||||
|
|
||||||
|
public $maxsize;
|
||||||
|
|
||||||
|
/** @var string[] */
|
||||||
|
private $headers = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $url
|
||||||
|
*/
|
||||||
|
function __construct($url, $maxsize = 0)
|
||||||
|
{
|
||||||
|
$this->maxsize = $maxsize;
|
||||||
|
|
||||||
|
// Make sure there aren't any spaces in the URL (i.e. the caller forgot to URL-encode).
|
||||||
|
if (strpos($url, ' ') !== false) {
|
||||||
|
throw new \InvalidArgumentException("Found space in \$url; it should be encoded");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->handle = curl_init($url);
|
||||||
|
|
||||||
|
// Force SSL and use our own certificate list.
|
||||||
|
$this->set(CURLOPT_SSL_VERIFYPEER, true);
|
||||||
|
$this->set(CURLOPT_SSL_VERIFYHOST, 2);
|
||||||
|
$this->set(CURLOPT_SSLVERSION, 3); // Force SSL v3.
|
||||||
|
$this->set(CURLOPT_CAINFO, __DIR__."/trusted-certs.crt");
|
||||||
|
|
||||||
|
if ($this->maxsize > 0) {
|
||||||
|
// Check if we get excepted minimum length
|
||||||
|
$this->set(CURLOPT_BUFFERSIZE, 1024);
|
||||||
|
$this->set(CURLOPT_NOPROGRESS, false);
|
||||||
|
$this->set(CURLOPT_PROGRESSFUNCTION, array($this, 'progressChunk'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit vulnerability surface area. Supported in cURL 7.19.4+
|
||||||
|
if (defined('CURLOPT_PROTOCOLS')) $this->set(CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
|
||||||
|
if (defined('CURLOPT_REDIR_PROTOCOLS')) $this->set(CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);
|
||||||
|
}
|
||||||
|
|
||||||
|
function progressChunk($downloadSize, $downloaded, $uploadSize, $uploaded) {
|
||||||
|
return ($this->maxsize > 0 && $downloaded >= $this->maxsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $header
|
||||||
|
*/
|
||||||
|
function addHeader($header)
|
||||||
|
{
|
||||||
|
$this->headers[] = $header;
|
||||||
|
}
|
||||||
|
|
||||||
|
function exec()
|
||||||
|
{
|
||||||
|
$this->set(CURLOPT_HTTPHEADER, $this->headers);
|
||||||
|
|
||||||
|
$body = curl_exec($this->handle);
|
||||||
|
if ($body === false) {
|
||||||
|
if ($this->maxsize == 0 || curl_errno($this->handle) != CURLE_ABORTED_BY_CALLBACK) {
|
||||||
|
throw new Exception_NetworkIO("Error executing HTTP request: " . curl_error($this->handle));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$statusCode = curl_getinfo($this->handle, CURLINFO_HTTP_CODE);
|
||||||
|
|
||||||
|
return new HttpResponse($statusCode, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $option
|
||||||
|
* @param mixed $value
|
||||||
|
*/
|
||||||
|
function set($option, $value)
|
||||||
|
{
|
||||||
|
curl_setopt($this->handle, $option, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function __destruct()
|
||||||
|
{
|
||||||
|
curl_close($this->handle);
|
||||||
|
}
|
||||||
|
}
|
47
modules/Dropbox/CurlStreamRelay.php
Normal file
47
modules/Dropbox/CurlStreamRelay.php
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A CURLOPT_WRITEFUNCTION that will write HTTP response data to $outStream if
|
||||||
|
* it's an HTTP 200 response. For all other HTTP status codes, it'll save the
|
||||||
|
* output in a string, which you can retrieve it via {@link getErrorBody}.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class CurlStreamRelay
|
||||||
|
{
|
||||||
|
var $outStream;
|
||||||
|
var $errorData;
|
||||||
|
var $isError;
|
||||||
|
|
||||||
|
function __construct($ch, $outStream)
|
||||||
|
{
|
||||||
|
$this->outStream = $outStream;
|
||||||
|
$this->errorData = array();
|
||||||
|
$isError = null;
|
||||||
|
curl_setopt($ch, CURLOPT_WRITEFUNCTION, array($this, 'writeData'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeData($ch, $data)
|
||||||
|
{
|
||||||
|
if ($this->isError === null) {
|
||||||
|
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$this->isError = ($statusCode !== 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isError) {
|
||||||
|
$this->errorData[] = $data;
|
||||||
|
} else {
|
||||||
|
fwrite($this->outStream, $data);
|
||||||
|
ob_flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
return strlen($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getErrorBody()
|
||||||
|
{
|
||||||
|
return implode($this->errorData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
19
modules/Dropbox/DeserializeException.php
Normal file
19
modules/Dropbox/DeserializeException.php
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If, when loading a serialized {@link RequestToken} or {@link AccessToken}, the input string is
|
||||||
|
* malformed, this exception will be thrown.
|
||||||
|
*/
|
||||||
|
final class DeserializeException extends \Exception
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $message
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function __construct($message)
|
||||||
|
{
|
||||||
|
parent::__construct($message);
|
||||||
|
}
|
||||||
|
}
|
83
modules/Dropbox/DropboxMetadataHeaderCatcher.php
Normal file
83
modules/Dropbox/DropboxMetadataHeaderCatcher.php
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class DropboxMetadataHeaderCatcher
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var mixed
|
||||||
|
*/
|
||||||
|
var $metadata = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
var $error = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
var $skippedFirstLine = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param resource $ch
|
||||||
|
*/
|
||||||
|
function __construct($ch)
|
||||||
|
{
|
||||||
|
curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, 'headerFunction'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param resource $ch
|
||||||
|
* @param string $header
|
||||||
|
* @return int
|
||||||
|
* @throws Exception_BadResponse
|
||||||
|
*/
|
||||||
|
function headerFunction($ch, $header)
|
||||||
|
{
|
||||||
|
// The first line is the HTTP status line (Ex: "HTTP/1.1 200 OK").
|
||||||
|
if (!$this->skippedFirstLine) {
|
||||||
|
$this->skippedFirstLine = true;
|
||||||
|
return strlen($header);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we've encountered an error on a previous callback, then there's nothing left to do.
|
||||||
|
if ($this->error !== null) {
|
||||||
|
return strlen($header);
|
||||||
|
}
|
||||||
|
|
||||||
|
// case-insensitive starts-with check.
|
||||||
|
if (\substr_compare($header, "x-dropbox-metadata:", 0, 19, true) !== 0) {
|
||||||
|
return strlen($header);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->metadata !== null) {
|
||||||
|
$this->error = "Duplicate X-Dropbox-Metadata header";
|
||||||
|
return strlen($header);
|
||||||
|
}
|
||||||
|
|
||||||
|
$headerValue = substr($header, 19);
|
||||||
|
$parsed = json_decode($headerValue, true, 10);
|
||||||
|
|
||||||
|
if ($parsed === null) {
|
||||||
|
$this->error = "Bad JSON in X-Dropbox-Metadata header";
|
||||||
|
return strlen($header);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->metadata = $parsed;
|
||||||
|
return strlen($header);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMetadata()
|
||||||
|
{
|
||||||
|
if ($this->error !== null) {
|
||||||
|
throw new Exception_BadResponse($this->error);
|
||||||
|
}
|
||||||
|
if ($this->metadata === null) {
|
||||||
|
throw new Exception_BadResponse("Missing X-Dropbox-Metadata header");
|
||||||
|
}
|
||||||
|
return $this->metadata;
|
||||||
|
}
|
||||||
|
}
|
16
modules/Dropbox/Exception.php
Normal file
16
modules/Dropbox/Exception.php
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base class for all API call exceptions.
|
||||||
|
*/
|
||||||
|
class Exception extends \Exception
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function __construct($message, $cause = null)
|
||||||
|
{
|
||||||
|
parent::__construct($message, 0, $cause);
|
||||||
|
}
|
||||||
|
}
|
17
modules/Dropbox/Exception/BadRequest.php
Normal file
17
modules/Dropbox/Exception/BadRequest.php
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when the server tells us that our request was invalid. This is typically due to an
|
||||||
|
* HTTP 400 response from the server.
|
||||||
|
*/
|
||||||
|
final class Exception_BadRequest extends Exception_ProtocolError
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function __construct($message = "")
|
||||||
|
{
|
||||||
|
parent::__construct($message);
|
||||||
|
}
|
||||||
|
}
|
17
modules/Dropbox/Exception/BadResponse.php
Normal file
17
modules/Dropbox/Exception/BadResponse.php
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When this SDK can't understand the response from the server. This could be due to a bug in this
|
||||||
|
* SDK or a buggy response from the Dropbox server.
|
||||||
|
*/
|
||||||
|
class Exception_BadResponse extends Exception_ProtocolError
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function __construct($message)
|
||||||
|
{
|
||||||
|
parent::__construct($message);
|
||||||
|
}
|
||||||
|
}
|
33
modules/Dropbox/Exception/BadResponseCode.php
Normal file
33
modules/Dropbox/Exception/BadResponseCode.php
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when the the Dropbox server responds with an HTTP status code we didn't expect.
|
||||||
|
*/
|
||||||
|
final class Exception_BadResponseCode extends Exception_BadResponse
|
||||||
|
{
|
||||||
|
/** @var int */
|
||||||
|
private $statusCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $message
|
||||||
|
* @param int $statusCode
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function __construct($message, $statusCode)
|
||||||
|
{
|
||||||
|
parent::__construct($message);
|
||||||
|
$this->statusCode = $statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The HTTP status code returned by the Dropbox server.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getStatusCode()
|
||||||
|
{
|
||||||
|
return $this->statusCode;
|
||||||
|
}
|
||||||
|
}
|
18
modules/Dropbox/Exception/InvalidAccessToken.php
Normal file
18
modules/Dropbox/Exception/InvalidAccessToken.php
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Dropbox server said that the access token you used is invalid or expired. You should
|
||||||
|
* probably ask the user to go through the OAuth authorization flow again to get a new access
|
||||||
|
* token.
|
||||||
|
*/
|
||||||
|
final class Exception_InvalidAccessToken extends Exception
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function __construct($message = "")
|
||||||
|
{
|
||||||
|
parent::__construct($message);
|
||||||
|
}
|
||||||
|
}
|
16
modules/Dropbox/Exception/NetworkIO.php
Normal file
16
modules/Dropbox/Exception/NetworkIO.php
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* There was a network I/O error when making the request.
|
||||||
|
*/
|
||||||
|
final class Exception_NetworkIO extends Exception
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function __construct($message, $cause = null)
|
||||||
|
{
|
||||||
|
parent::__construct($message, $cause);
|
||||||
|
}
|
||||||
|
}
|
17
modules/Dropbox/Exception/ProtocolError.php
Normal file
17
modules/Dropbox/Exception/ProtocolError.php
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* There was an protocol misunderstanding between this SDK and the server. One of us didn't
|
||||||
|
* understand what the other one was saying.
|
||||||
|
*/
|
||||||
|
class Exception_ProtocolError extends Exception
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function __construct($message)
|
||||||
|
{
|
||||||
|
parent::__construct($message);
|
||||||
|
}
|
||||||
|
}
|
17
modules/Dropbox/Exception/RetryLater.php
Normal file
17
modules/Dropbox/Exception/RetryLater.php
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Dropbox server said it couldn't fulfil our request right now, but that we should try
|
||||||
|
* again later.
|
||||||
|
*/
|
||||||
|
final class Exception_RetryLater extends Exception
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function __construct($message)
|
||||||
|
{
|
||||||
|
parent::__construct($message);
|
||||||
|
}
|
||||||
|
}
|
15
modules/Dropbox/Exception/ServerError.php
Normal file
15
modules/Dropbox/Exception/ServerError.php
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Dropbox server said that there was an internal error when trying to fulfil our request.
|
||||||
|
* This usually corresponds to an HTTP 500 response.
|
||||||
|
*/
|
||||||
|
final class Exception_ServerError extends Exception
|
||||||
|
{
|
||||||
|
/** @internal */
|
||||||
|
function __construct($message = "")
|
||||||
|
{
|
||||||
|
parent::__construct($message);
|
||||||
|
}
|
||||||
|
}
|
99
modules/Dropbox/Host.php
Normal file
99
modules/Dropbox/Host.php
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Dropbox web API accesses three hosts; this structure holds the
|
||||||
|
* names of those three hosts. This is primarily for mocking things out
|
||||||
|
* during testing. Most of the time you won't have to deal with this class
|
||||||
|
* directly, and even when you do, you'll just use the default
|
||||||
|
* value: {@link Host::getDefault()}.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class Host
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns a Host object configured with the three standard Dropbox host: "api.dropbox.com",
|
||||||
|
* "api-content.dropbox.com", and "www.dropbox.com"
|
||||||
|
*
|
||||||
|
* @return Host
|
||||||
|
*/
|
||||||
|
static function getDefault()
|
||||||
|
{
|
||||||
|
if (!self::$defaultValue) {
|
||||||
|
self::$defaultValue = new Host("api.dropbox.com", "api-content.dropbox.com", "www.dropbox.com");
|
||||||
|
}
|
||||||
|
return self::$defaultValue;
|
||||||
|
}
|
||||||
|
private static $defaultValue;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $api;
|
||||||
|
/** @var string */
|
||||||
|
private $content;
|
||||||
|
/** @var string */
|
||||||
|
private $web;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param string $api
|
||||||
|
* See {@link getApi()}
|
||||||
|
* @param string $content
|
||||||
|
* See {@link getContent()}
|
||||||
|
* @param string $web
|
||||||
|
* See {@link getWeb()}
|
||||||
|
*/
|
||||||
|
function __construct($api, $content, $web)
|
||||||
|
{
|
||||||
|
$this->api = $api;
|
||||||
|
$this->content = $content;
|
||||||
|
$this->web = $web;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the host name of the main Dropbox API server.
|
||||||
|
* The default is "api.dropbox.com".
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function getApi() { return $this->api; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the host name of the Dropbox API content server.
|
||||||
|
* The default is "api-content.dropbox.com".
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function getContent() { return $this->content; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the host name of the Dropbox web server. Used during user authorization.
|
||||||
|
* The default is "www.dropbox.com".
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function getWeb() { return $this->web; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that a function argument is of type <code>Host</code>.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
static function checkArg($argName, $argValue)
|
||||||
|
{
|
||||||
|
if (!($argValue instanceof self)) Checker::throwError($argName, $argValue, __CLASS__);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that a function argument is either <code>null</code> or of type
|
||||||
|
* <code>Host</code>.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
static function checkArgOrNull($argName, $argValue)
|
||||||
|
{
|
||||||
|
if ($argValue === null) return;
|
||||||
|
if (!($argValue instanceof self)) Checker::throwError($argName, $argValue, __CLASS__);
|
||||||
|
}
|
||||||
|
}
|
17
modules/Dropbox/HttpResponse.php
Normal file
17
modules/Dropbox/HttpResponse.php
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class HttpResponse
|
||||||
|
{
|
||||||
|
public $statusCode;
|
||||||
|
public $body;
|
||||||
|
|
||||||
|
function __construct($statusCode, $body)
|
||||||
|
{
|
||||||
|
$this->statusCode = $statusCode;
|
||||||
|
$this->body = $body;
|
||||||
|
}
|
||||||
|
}
|
158
modules/Dropbox/Path.php
Normal file
158
modules/Dropbox/Path.php
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Path validation functions.
|
||||||
|
*/
|
||||||
|
final class Path
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Return whether the given path is a valid Dropbox path.
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* The path you want to check for validity.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
* Whether the path was valid or not.
|
||||||
|
*/
|
||||||
|
static function isValid($path)
|
||||||
|
{
|
||||||
|
$error = self::findError($path);
|
||||||
|
return ($error === null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether the given path is a valid non-root Dropbox path.
|
||||||
|
* This is the same as {@link isValid} except <code>"/"</code> is not allowed.
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* The path you want to check for validity.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
* Whether the path was valid or not.
|
||||||
|
*/
|
||||||
|
static function isValidNonRoot($path)
|
||||||
|
{
|
||||||
|
$error = self::findErrorNonRoot($path);
|
||||||
|
return ($error === null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the given path is a valid Dropbox path, return <code>null</code>,
|
||||||
|
* otherwise return an English string error message describing what is wrong with the path.
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* The path you want to check for validity.
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
* If the path was valid, return <code>null</code>. Otherwise, returns
|
||||||
|
* an English string describing the problem.
|
||||||
|
*/
|
||||||
|
static function findError($path)
|
||||||
|
{
|
||||||
|
Checker::argString("path", $path);
|
||||||
|
|
||||||
|
$matchResult = preg_match('%^(?:
|
||||||
|
[\x09\x0A\x0D\x20-\x7E] # ASCII
|
||||||
|
| [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
|
||||||
|
| \xE0[\xA0-\xBF][\x80-\xBD] # excluding overlongs, FFFE, and FFFF
|
||||||
|
| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
|
||||||
|
| \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
|
||||||
|
)*$%xs', $path);
|
||||||
|
|
||||||
|
if ($matchResult !== 1) {
|
||||||
|
return "must be valid UTF-8; BMP only, no surrogates, no U+FFFE or U+FFFF";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\substr_compare($path, "/", 0, 1) !== 0) return "must start with \"/\"";
|
||||||
|
$l = strlen($path);
|
||||||
|
if ($l === 1) return null; // Special case for "/"
|
||||||
|
|
||||||
|
if ($path[$l-1] === "/") return "must not end with \"/\"";
|
||||||
|
|
||||||
|
// TODO: More checks.
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the given path is a valid non-root Dropbox path, return <code>null</code>,
|
||||||
|
* otherwise return an English string error message describing what is wrong with the path.
|
||||||
|
* This is the same as {@link findError} except <code>"/"</code> will yield an error message.
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* The path you want to check for validity.
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
* If the path was valid, return <code>null</code>. Otherwise, returns
|
||||||
|
* an English string describing the problem.
|
||||||
|
*/
|
||||||
|
static function findErrorNonRoot($path)
|
||||||
|
{
|
||||||
|
if ($path == "/") return "root path not allowed";
|
||||||
|
return self::findError($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the last component of a path (the file or folder name).
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* Path::getName("/Misc/Notes.txt") // "Notes.txt"
|
||||||
|
* Path::getName("/Misc") // "Misc"
|
||||||
|
* Path::getName("/") // null
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* The full path you want to get the last component of.
|
||||||
|
*
|
||||||
|
* @return null|string
|
||||||
|
* The last component of <code>$path</code> or <code>null</code> if the given
|
||||||
|
* <code>$path</code> was <code>"/"<code>.
|
||||||
|
*/
|
||||||
|
static function getName($path)
|
||||||
|
{
|
||||||
|
Checker::argString("path", $path);
|
||||||
|
|
||||||
|
if (\substr_compare($path, "/", 0, 1) !== 0) {
|
||||||
|
throw new \InvalidArgumentException("'path' must start with \"/\"");
|
||||||
|
}
|
||||||
|
$l = strlen($path);
|
||||||
|
if ($l === 1) return null;
|
||||||
|
if ($path[$l-1] === "/") {
|
||||||
|
throw new \InvalidArgumentException("'path' must not end with \"/\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
$lastSlash = strrpos($path, "/");
|
||||||
|
return substr($path, $lastSlash+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @param string $argName
|
||||||
|
* @param mixed $value
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
static function checkArg($argName, $value)
|
||||||
|
{
|
||||||
|
if ($value === null) throw new \InvalidArgumentException("'$argName' must not be null");
|
||||||
|
if (!is_string($value)) throw new \InvalidArgumentException("'$argName' must be a string");
|
||||||
|
$error = self::findError($value);
|
||||||
|
if ($error !== null) throw new \InvalidArgumentException("'$argName'': bad path: $error: ".var_export($value, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @param string $argName
|
||||||
|
* @param mixed $value
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
static function checkArgNonRoot($argName, $value)
|
||||||
|
{
|
||||||
|
if ($value === null) throw new \InvalidArgumentException("'$argName' must not be null");
|
||||||
|
if (!is_string($value)) throw new \InvalidArgumentException("'$argName' must be a string");
|
||||||
|
$error = self::findErrorNonRoot($value);
|
||||||
|
if ($error !== null) throw new \InvalidArgumentException("'$argName'': bad path: $error: ".var_export($value, true));
|
||||||
|
}
|
||||||
|
}
|
252
modules/Dropbox/RequestUtil.php
Normal file
252
modules/Dropbox/RequestUtil.php
Normal file
|
@ -0,0 +1,252 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
if (!function_exists('curl_init')) {
|
||||||
|
throw new \Exception("The Dropbox SDK requires the cURL PHP extension, but it looks like you don't have it (couldn't find function \"curl_init\"). Library: \"" . __FILE__ . "\".");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('json_decode')) {
|
||||||
|
throw new \Exception("The Dropbox SDK requires the JSON PHP extension, but it looks like you don't have it (couldn't find function \"json_decode\"). Library: \"" . __FILE__ . "\".");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen((string) PHP_INT_MAX) < 19) {
|
||||||
|
// Looks like we're running on a 32-bit build of PHP. This could cause problems because some of the numbers
|
||||||
|
// we use (file sizes, quota, etc) can be larger than 32-bit ints can handle.
|
||||||
|
//throw new \Exception("The Dropbox SDK uses 64-bit integers, but it looks like we're running on a version of PHP that doesn't support 64-bit integers (PHP_INT_MAX=" . ((string) PHP_INT_MAX) . "). Library: \"" . __FILE__ . "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class RequestUtil
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $userLocale
|
||||||
|
* @param string $host
|
||||||
|
* @param string $path
|
||||||
|
* @param array $params
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
static function buildUrl($userLocale, $host, $path, $params = null)
|
||||||
|
{
|
||||||
|
$url = self::buildUri($host, $path);
|
||||||
|
$url .= "?locale=" . rawurlencode($userLocale);
|
||||||
|
|
||||||
|
if ($params !== null) {
|
||||||
|
foreach ($params as $key => $value) {
|
||||||
|
Checker::argStringNonEmpty("key in 'params'", $key);
|
||||||
|
if ($value !== null) {
|
||||||
|
if (is_bool($value)) {
|
||||||
|
$value = $value ? "true" : "false";
|
||||||
|
}
|
||||||
|
else if (is_int($value)) {
|
||||||
|
$value = (string) $value;
|
||||||
|
}
|
||||||
|
else if (!is_string($value)) {
|
||||||
|
throw new \InvalidArgumentException("params['$key'] is not a string, int, or bool");
|
||||||
|
}
|
||||||
|
$url .= "&" . rawurlencode($key) . "=" . rawurlencode($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $host
|
||||||
|
* @param string $path
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
static function buildUri($host, $path)
|
||||||
|
{
|
||||||
|
Checker::argStringNonEmpty("host", $host);
|
||||||
|
Checker::argStringNonEmpty("path", $path);
|
||||||
|
return "https://" . $host . "/" . $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $clientIdentifier
|
||||||
|
* @param string $url
|
||||||
|
* @return Curl
|
||||||
|
*/
|
||||||
|
static function mkCurlWithoutAuth($clientIdentifier, $url, $maxsize=0)
|
||||||
|
{
|
||||||
|
$curl = new Curl($url, $maxsize);
|
||||||
|
|
||||||
|
$curl->set(CURLOPT_CONNECTTIMEOUT, 10);
|
||||||
|
|
||||||
|
// If the transfer speed is below 1kB/sec for 10 sec, abort.
|
||||||
|
$curl->set(CURLOPT_LOW_SPEED_LIMIT, 1024);
|
||||||
|
$curl->set(CURLOPT_LOW_SPEED_TIME, 10);
|
||||||
|
|
||||||
|
//$curl->set(CURLOPT_VERBOSE, true); // For debugging.
|
||||||
|
// TODO: Figure out how to encode clientIdentifier (urlencode?)
|
||||||
|
$curl->addHeader("User-Agent: ".$clientIdentifier." Dropbox-PHP-SDK");
|
||||||
|
|
||||||
|
return $curl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $clientIdentifier
|
||||||
|
* @param string $url
|
||||||
|
* @param string $accessToken
|
||||||
|
* @return Curl
|
||||||
|
*/
|
||||||
|
static function mkCurl($clientIdentifier, $url, $accessToken, $maxsize=0)
|
||||||
|
{
|
||||||
|
$curl = self::mkCurlWithoutAuth($clientIdentifier, $url, $maxsize);
|
||||||
|
$curl->addHeader("Authorization: Bearer $accessToken");
|
||||||
|
return $curl;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function buildPostBody($params)
|
||||||
|
{
|
||||||
|
if ($params === null) return "";
|
||||||
|
|
||||||
|
$pairs = array();
|
||||||
|
foreach ($params as $key => $value) {
|
||||||
|
Checker::argStringNonEmpty("key in 'params'", $key);
|
||||||
|
if ($value !== null) {
|
||||||
|
if (is_bool($value)) {
|
||||||
|
$value = $value ? "true" : "false";
|
||||||
|
}
|
||||||
|
else if (is_int($value)) {
|
||||||
|
$value = (string) $value;
|
||||||
|
}
|
||||||
|
else if (!is_string($value)) {
|
||||||
|
throw new \InvalidArgumentException("params['$key'] is not a string, int, or bool");
|
||||||
|
}
|
||||||
|
$pairs[] = rawurlencode($key) . "=" . rawurlencode((string) $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return implode("&", $pairs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $accessToken
|
||||||
|
* @param string $userLocale
|
||||||
|
* @param string $host
|
||||||
|
* @param string $path
|
||||||
|
* @param array|null $params
|
||||||
|
*
|
||||||
|
* @return HttpResponse
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
static function doPost($clientIdentifier, $accessToken, $userLocale, $host, $path, $params = null)
|
||||||
|
{
|
||||||
|
Checker::argStringNonEmpty("accessToken", $accessToken);
|
||||||
|
|
||||||
|
$url = self::buildUri($host, $path);
|
||||||
|
|
||||||
|
if ($params === null) $params = array();
|
||||||
|
$params['locale'] = $userLocale;
|
||||||
|
|
||||||
|
$curl = self::mkCurl($clientIdentifier, $url, $accessToken);
|
||||||
|
$curl->set(CURLOPT_POST, true);
|
||||||
|
$curl->set(CURLOPT_POSTFIELDS, self::buildPostBody($params));
|
||||||
|
|
||||||
|
$curl->set(CURLOPT_RETURNTRANSFER, true);
|
||||||
|
return $curl->exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $accessToken
|
||||||
|
* @param string $userLocale
|
||||||
|
* @param string $host
|
||||||
|
* @param string $path
|
||||||
|
* @param array|null $params
|
||||||
|
*
|
||||||
|
* @return HttpResponse
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
static function doGet($clientIdentifier, $accessToken, $userLocale, $host, $path, $params = null)
|
||||||
|
{
|
||||||
|
Checker::argStringNonEmpty("accessToken", $accessToken);
|
||||||
|
|
||||||
|
$url = self::buildUrl($userLocale, $host, $path, $params);
|
||||||
|
|
||||||
|
$curl = self::mkCurl($clientIdentifier, $url, $accessToken);
|
||||||
|
$curl->set(CURLOPT_HTTPGET, true);
|
||||||
|
$curl->set(CURLOPT_RETURNTRANSFER, true);
|
||||||
|
|
||||||
|
return $curl->exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $responseBody
|
||||||
|
* @return mixed
|
||||||
|
* @throws Exception_BadResponse
|
||||||
|
*/
|
||||||
|
static function parseResponseJson($responseBody)
|
||||||
|
{
|
||||||
|
$obj = json_decode($responseBody, TRUE, 10);
|
||||||
|
if ($obj === null) {
|
||||||
|
throw new Exception_BadResponse("Got bad JSON from server: $responseBody");
|
||||||
|
}
|
||||||
|
return $obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function unexpectedStatus($httpResponse)
|
||||||
|
{
|
||||||
|
$sc = $httpResponse->statusCode;
|
||||||
|
|
||||||
|
$message = "HTTP status $sc";
|
||||||
|
if (is_string($httpResponse->body)) {
|
||||||
|
// TODO: Maybe only include the first ~200 chars of the body?
|
||||||
|
$message .= "\n".$httpResponse->body;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($sc === 400) return new Exception_BadRequest($message);
|
||||||
|
if ($sc === 401) return new Exception_InvalidAccessToken($message);
|
||||||
|
if ($sc === 500 || $sc === 502) return new Exception_ServerError($message);
|
||||||
|
if ($sc === 503) return new Exception_RetryLater($message);
|
||||||
|
|
||||||
|
return new Exception_BadResponse("Unexpected $message");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $maxRetries
|
||||||
|
* The number of times to retry it the action if it fails with one of the transient
|
||||||
|
* API errors. A value of 1 means we'll try the action once and if it fails, we
|
||||||
|
* will retry once.
|
||||||
|
*
|
||||||
|
* @param callable $action
|
||||||
|
* The the action you want to retry.
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
* Whatever is returned by the $action callable.
|
||||||
|
*/
|
||||||
|
static function runWithRetry($maxRetries, $action)
|
||||||
|
{
|
||||||
|
Checker::argNat("maxRetries", $maxRetries);
|
||||||
|
|
||||||
|
$retryDelay = 1;
|
||||||
|
$numRetries = 0;
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
return $action();
|
||||||
|
}
|
||||||
|
// These exception types are the ones we think are possibly transient errors.
|
||||||
|
catch (Exception_NetworkIO $ex) {
|
||||||
|
$savedEx = $ex;
|
||||||
|
}
|
||||||
|
catch (Exception_ServerError $ex) {
|
||||||
|
$savedEx = $ex;
|
||||||
|
}
|
||||||
|
catch (Exception_RetryLater $ex) {
|
||||||
|
$savedEx = $ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We maxed out our retries. Propagate the last exception we got.
|
||||||
|
if ($numRetries >= $maxRetries) throw $savedEx;
|
||||||
|
|
||||||
|
$numRetries++;
|
||||||
|
sleep($retryDelay);
|
||||||
|
$retryDelay *= 2; // Exponential back-off.
|
||||||
|
}
|
||||||
|
throw new \RuntimeException("unreachable");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
65
modules/Dropbox/Security.php
Normal file
65
modules/Dropbox/Security.php
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper functions for security-related things.
|
||||||
|
*/
|
||||||
|
class Security
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* A string equality function that compares strings in a way that isn't suceptible to timing
|
||||||
|
* attacks. An attacker can figure out the length of the string, but not the string's value.
|
||||||
|
*
|
||||||
|
* Use this when comparing two strings where:
|
||||||
|
* - one string could be influenced by an attacker
|
||||||
|
* - the other string contains data an attacker shouldn't know
|
||||||
|
*
|
||||||
|
* @param string $a
|
||||||
|
* @param string $b
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
static function stringEquals($a, $b)
|
||||||
|
{
|
||||||
|
// Be strict with arguments. PHP's liberal types could get us pwned.
|
||||||
|
if (func_num_args() !== 2) {
|
||||||
|
throw \InvalidArgumentException("Expecting 2 args, got ".func_num_args().".");
|
||||||
|
}
|
||||||
|
Checker::argString("a", $a);
|
||||||
|
Checker::argString("b", $b);
|
||||||
|
|
||||||
|
if (strlen($a) !== strlen($b)) return false;
|
||||||
|
$result = 0;
|
||||||
|
for ($i = 0; $i < strlen($a); $i++) {
|
||||||
|
$result |= ord($a[$i]) ^ ord($b[$i]);
|
||||||
|
}
|
||||||
|
return $result === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns cryptographically strong secure random bytes (as a PHP string).
|
||||||
|
*
|
||||||
|
* @param int $numBytes
|
||||||
|
* The number of bytes of random data to return.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
static function getRandomBytes($numBytes)
|
||||||
|
{
|
||||||
|
Checker::argIntPositive("numBytes", $numBytes);
|
||||||
|
|
||||||
|
// openssl_random_pseudo_bytes had some issues prior to PHP 5.3.4
|
||||||
|
if (function_exists('openssl_random_pseudo_bytes')
|
||||||
|
&& version_compare(PHP_VERSION, '5.3.4') >= 0) {
|
||||||
|
$s = openssl_random_pseudo_bytes($numBytes, $isCryptoStrong);
|
||||||
|
if ($isCryptoStrong) return $s;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (function_exists('mcrypt_create_iv')) {
|
||||||
|
return mcrypt_create_iv($numBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hopefully the above two options cover all our users. But if not, there are
|
||||||
|
// other platform-specific options we could add.
|
||||||
|
assert(False, "no suitable random number source available");
|
||||||
|
}
|
||||||
|
}
|
16
modules/Dropbox/StreamReadException.php
Normal file
16
modules/Dropbox/StreamReadException.php
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when there's an error reading from a stream that was passed in by the caller.
|
||||||
|
*/
|
||||||
|
class StreamReadException extends \Exception
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function __construct($message, $cause = null)
|
||||||
|
{
|
||||||
|
parent::__construct($message, 0, $cause);
|
||||||
|
}
|
||||||
|
}
|
61
modules/Dropbox/ValueStore.php
Normal file
61
modules/Dropbox/ValueStore.php
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A contract for a class which provides simple get/set/clear access to a single string
|
||||||
|
* value. {@link ArrayEntryStore} provides an implementation of this for storing a value
|
||||||
|
* in a single array element.
|
||||||
|
*
|
||||||
|
* Example implementation for a Memcache-based backing store:
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* class MemcacheValueStore implements ValueStore
|
||||||
|
* {
|
||||||
|
* private $key;
|
||||||
|
* private $memcache;
|
||||||
|
*
|
||||||
|
* function __construct($memcache, $key)
|
||||||
|
* {
|
||||||
|
* $this->memcache = $memcache;
|
||||||
|
* $this->key = $key;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* function get()
|
||||||
|
* {
|
||||||
|
* $value = $this->memcache->get($this->getKey());
|
||||||
|
* return $value === false ? null : base64_decode($value);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* function set($value)
|
||||||
|
* {
|
||||||
|
* $this->memcache->set($this->key, base64_encode($value));
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* function clear()
|
||||||
|
* {
|
||||||
|
* $this->memcache->delete($this->key);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* </code>
|
||||||
|
*/
|
||||||
|
interface ValueStore
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns the entry's current value or <code>null</code> if nothing is set.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the entry to the given value.
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
*/
|
||||||
|
function set($value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the value.
|
||||||
|
*/
|
||||||
|
function clear();
|
||||||
|
}
|
276
modules/Dropbox/WebAuth.php
Normal file
276
modules/Dropbox/WebAuth.php
Normal file
|
@ -0,0 +1,276 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OAuth 2 "authorization code" flow. (This SDK does not support the "token" flow.)
|
||||||
|
*
|
||||||
|
* Use {@link WebAuth::start()} and {@link WebAuth::finish()} to guide your
|
||||||
|
* user through the process of giving your app access to their Dropbox account.
|
||||||
|
* At the end, you will have an access token, which you can pass to {@link Client}
|
||||||
|
* and start making API calls.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* use \Dropbox as dbx;
|
||||||
|
*
|
||||||
|
* function getWebAuth()
|
||||||
|
* {
|
||||||
|
* $appInfo = dbx\AppInfo::loadFromJsonFile(...);
|
||||||
|
* $clientIdentifier = "my-app/1.0";
|
||||||
|
* $redirectUri = "https://example.org/dropbox-auth-finish";
|
||||||
|
* $csrfTokenStore = new dbx\ArrayEntryStore($_SESSION, 'dropbox-auth-csrf-token');
|
||||||
|
* return new dbx\WebAuth($appInfo, $clientIdentifier, $redirectUri, $csrfTokenStore, ...);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // ----------------------------------------------------------
|
||||||
|
* // In the URL handler for "/dropbox-auth-start"
|
||||||
|
*
|
||||||
|
* $authorizeUrl = getWebAuth()->start();
|
||||||
|
* header("Location: $authorizeUrl");
|
||||||
|
*
|
||||||
|
* // ----------------------------------------------------------
|
||||||
|
* // In the URL handler for "/dropbox-auth-finish"
|
||||||
|
*
|
||||||
|
* try {
|
||||||
|
* list($accessToken, $userId, $urlState) = getWebAuth()->finish($_GET);
|
||||||
|
* assert($urlState === null); // Since we didn't pass anything in start()
|
||||||
|
* }
|
||||||
|
* catch (dbx\WebAuthException_BadRequest $ex) {
|
||||||
|
* error_log("/dropbox-auth-finish: bad request: " . $ex->getMessage());
|
||||||
|
* // Respond with an HTTP 400 and display error page...
|
||||||
|
* }
|
||||||
|
* catch (dbx\WebAuthException_BadState $ex) {
|
||||||
|
* // Auth session expired. Restart the auth process.
|
||||||
|
* header('Location: /dropbox-auth-start');
|
||||||
|
* }
|
||||||
|
* catch (dbx\WebAuthException_Csrf $ex) {
|
||||||
|
* error_log("/dropbox-auth-finish: CSRF mismatch: " . $ex->getMessage());
|
||||||
|
* // Respond with HTTP 403 and display error page...
|
||||||
|
* }
|
||||||
|
* catch (dbx\WebAuthException_NotApproved $ex) {
|
||||||
|
* error_log("/dropbox-auth-finish: not approved: " . $ex->getMessage());
|
||||||
|
* }
|
||||||
|
* catch (dbx\WebAuthException_Provider $ex) {
|
||||||
|
* error_log("/dropbox-auth-finish: error redirect from Dropbox: " . $ex->getMessage());
|
||||||
|
* }
|
||||||
|
* catch (dbx\Exception $ex) {
|
||||||
|
* error_log("/dropbox-auth-finish: error communicating with Dropbox API: " . $ex->getMessage());
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // We can now use $accessToken to make API requests.
|
||||||
|
* $client = dbx\Client($accessToken, ...);
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class WebAuth extends WebAuthBase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The URI that the Dropbox server will redirect the user to after the user finishes
|
||||||
|
* authorizing your app. This URI must be HTTPS-based and
|
||||||
|
* <a href="https://www.dropbox.com/developers/apps">pre-registered with Dropbox</a>,
|
||||||
|
* though "localhost"-based and "127.0.0.1"-based URIs are allowed without pre-registration
|
||||||
|
* and can be either HTTP or HTTPS.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function getRedirectUri() { return $this->redirectUri; }
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $redirectUri;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A object that lets us save CSRF token string to the user's session. If you're using the
|
||||||
|
* standard PHP <code>$_SESSION</code>, you can pass in something like
|
||||||
|
* <code>new ArrayEntryStore($_SESSION, 'dropbox-auth-csrf-token')</code>.
|
||||||
|
*
|
||||||
|
* If you're not using $_SESSION, you might have to create your own class that provides
|
||||||
|
* the same <code>get()</code>/<code>set()</code>/<code>clear()</code> methods as
|
||||||
|
* {@link ArrayEntryStore}.
|
||||||
|
*
|
||||||
|
* @return ValueStore
|
||||||
|
*/
|
||||||
|
function getCsrfTokenStore() { return $this->csrfTokenStore; }
|
||||||
|
|
||||||
|
/** @var object */
|
||||||
|
private $csrfTokenStore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param AppInfo $appInfo
|
||||||
|
* See {@link getAppInfo()}
|
||||||
|
* @param string $clientIdentifier
|
||||||
|
* See {@link getClientIdentifier()}
|
||||||
|
* @param null|string $redirectUri
|
||||||
|
* See {@link getRedirectUri()}
|
||||||
|
* @param null|ValueStore $csrfTokenStore
|
||||||
|
* See {@link getCsrfTokenStore()}
|
||||||
|
* @param null|string $userLocale
|
||||||
|
* See {@link getUserLocale()}
|
||||||
|
*/
|
||||||
|
function __construct($appInfo, $clientIdentifier, $redirectUri, $csrfTokenStore, $userLocale = null)
|
||||||
|
{
|
||||||
|
parent::__construct($appInfo, $clientIdentifier, $userLocale);
|
||||||
|
|
||||||
|
Checker::argStringNonEmpty("redirectUri", $redirectUri);
|
||||||
|
|
||||||
|
$this->csrfTokenStore = $csrfTokenStore;
|
||||||
|
$this->redirectUri = $redirectUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the OAuth 2 authorization process, which involves redirecting the user to the
|
||||||
|
* returned authorization URL (a URL on the Dropbox website). When the user then
|
||||||
|
* either approves or denies your app access, Dropbox will redirect them to the
|
||||||
|
* <code>$redirectUri</code> given to constructor, at which point you should
|
||||||
|
* call {@link finish()} to complete the authorization process.
|
||||||
|
*
|
||||||
|
* This function will also save a CSRF token using the <code>$csrfTokenStore</code> given to
|
||||||
|
* the constructor. This CSRF token will be checked on {@link finish()} to prevent
|
||||||
|
* request forgery.
|
||||||
|
*
|
||||||
|
* @param string|null $urlState
|
||||||
|
* Any data you would like to keep in the URL through the authorization process.
|
||||||
|
* This exact state will be returned to you by {@link finish()}.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* The URL to redirect the user to.
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
function start($urlState = null)
|
||||||
|
{
|
||||||
|
Checker::argStringOrNull("urlState", $urlState);
|
||||||
|
|
||||||
|
$csrfToken = self::encodeCsrfToken(Security::getRandomBytes(16));
|
||||||
|
$state = $csrfToken;
|
||||||
|
if ($urlState !== null) {
|
||||||
|
$state .= "|";
|
||||||
|
$state .= $urlState;
|
||||||
|
}
|
||||||
|
$this->csrfTokenStore->set($csrfToken);
|
||||||
|
|
||||||
|
return $this->_getAuthorizeUrl($this->redirectUri, $state);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function encodeCsrfToken($string)
|
||||||
|
{
|
||||||
|
return strtr(base64_encode($string), '+/', '-_');
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function decodeCsrfToken($string)
|
||||||
|
{
|
||||||
|
return base64_decode(strtr($string, '-_', '+/'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this after the user has visited the authorize URL ({@link start()}), approved your app,
|
||||||
|
* and was redirected to your redirect URI.
|
||||||
|
*
|
||||||
|
* @param array $queryParams
|
||||||
|
* The query parameters on the GET request to your redirect URI.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* A <code>list(string $accessToken, string $userId, string $urlState)</code>, where
|
||||||
|
* <code>$accessToken</code> can be used to construct a {@link Client}, <code>$userId</code>
|
||||||
|
* is the user ID of the user's Dropbox account, and <code>$urlState</code> is the
|
||||||
|
* value you originally passed in to {@link start()}.
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
* Thrown if there's an error getting the access token from Dropbox.
|
||||||
|
* @throws WebAuthException_BadRequest
|
||||||
|
* @throws WebAuthException_BadState
|
||||||
|
* @throws WebAuthException_Csrf
|
||||||
|
* @throws WebAuthException_NotApproved
|
||||||
|
* @throws WebAuthException_Provider
|
||||||
|
*/
|
||||||
|
function finish($queryParams)
|
||||||
|
{
|
||||||
|
Checker::argArray("queryParams", $queryParams);
|
||||||
|
|
||||||
|
$csrfTokenFromSession = $this->csrfTokenStore->get();
|
||||||
|
Checker::argStringOrNull("this->csrfTokenStore->get()", $csrfTokenFromSession);
|
||||||
|
|
||||||
|
// Check well-formedness of request.
|
||||||
|
|
||||||
|
if (!isset($queryParams['state'])) {
|
||||||
|
throw new WebAuthException_BadRequest("Missing query parameter 'state'.");
|
||||||
|
}
|
||||||
|
$state = $queryParams['state'];
|
||||||
|
Checker::argString("queryParams['state']", $state);
|
||||||
|
|
||||||
|
$error = null;
|
||||||
|
if (isset($queryParams['error'])) {
|
||||||
|
$error = $queryParams['error'];
|
||||||
|
Checker::argString("queryParams['error']", $error);
|
||||||
|
$errorDescription = null;
|
||||||
|
if (isset($queryParams['error_description'])) {
|
||||||
|
$errorDescription = $queryParams['error_description'];
|
||||||
|
Checker::argString("queryParams['error_description']", $errorDescription);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$code = null;
|
||||||
|
if (isset($queryParams['code'])) {
|
||||||
|
$code = $queryParams['code'];
|
||||||
|
Checker::argString("queryParams['code']", $code);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($code !== null && $error !== null) {
|
||||||
|
throw new WebAuthException_BadRequest("Query parameters 'code' and 'error' are both set;".
|
||||||
|
" only one must be set.");
|
||||||
|
}
|
||||||
|
if ($code === null && $error === null) {
|
||||||
|
throw new WebAuthException_BadRequest("Neither query parameter 'code' or 'error' is set.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check CSRF token
|
||||||
|
|
||||||
|
if ($csrfTokenFromSession === null) {
|
||||||
|
throw new WebAuthException_BadState();
|
||||||
|
}
|
||||||
|
// Sanity check to make sure something hasn't gone terribly wrong.
|
||||||
|
assert(strlen($csrfTokenFromSession) > 20);
|
||||||
|
|
||||||
|
$splitPos = strpos($state, "|");
|
||||||
|
if ($splitPos === false) {
|
||||||
|
$givenCsrfToken = $state;
|
||||||
|
$urlState = null;
|
||||||
|
} else {
|
||||||
|
$givenCsrfToken = substr($state, 0, $splitPos);
|
||||||
|
$urlState = substr($state, $splitPos + 1);
|
||||||
|
}
|
||||||
|
if (!Security::stringEquals($csrfTokenFromSession, $givenCsrfToken)) {
|
||||||
|
throw new WebAuthException_Csrf("Expected ".Client::q($csrfTokenFromSession).
|
||||||
|
", got ".Client::q($givenCsrfToken).".");
|
||||||
|
}
|
||||||
|
$this->csrfTokenStore->clear();
|
||||||
|
|
||||||
|
// Check for error identifier
|
||||||
|
|
||||||
|
if ($error !== null) {
|
||||||
|
if ($error === 'access_denied') {
|
||||||
|
// When the user clicks "Deny".
|
||||||
|
if ($errorDescription === null) {
|
||||||
|
throw new WebAuthException_NotApproved("No additional description from Dropbox.");
|
||||||
|
} else {
|
||||||
|
throw new WebAuthException_NotApproved("Additional description from Dropbox: $errorDescription");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// All other errors.
|
||||||
|
$fullMessage = $error;
|
||||||
|
if ($errorDescription !== null) {
|
||||||
|
$fullMessage .= ": ";
|
||||||
|
$fullMessage .= $errorDescription;
|
||||||
|
}
|
||||||
|
throw new WebAuthException_Provider($fullMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If everything went ok, make the network call to get an access token.
|
||||||
|
|
||||||
|
list($accessToken, $userId) = $this->_finish($code, $this->redirectUri);
|
||||||
|
return array($accessToken, $userId, $urlState);
|
||||||
|
}
|
||||||
|
}
|
136
modules/Dropbox/WebAuthBase.php
Normal file
136
modules/Dropbox/WebAuthBase.php
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base class for the two auth options.
|
||||||
|
*/
|
||||||
|
class WebAuthBase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Whatever AppInfo was passed into the constructor.
|
||||||
|
*
|
||||||
|
* @return AppInfo
|
||||||
|
*/
|
||||||
|
function getAppInfo() { return $this->appInfo; }
|
||||||
|
|
||||||
|
/** @var AppInfo */
|
||||||
|
private $appInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An identifier for the API client, typically of the form "Name/Version".
|
||||||
|
* This is used to set the HTTP <code>User-Agent</code> header when making API requests.
|
||||||
|
* Example: <code>"PhotoEditServer/1.3"</code>
|
||||||
|
*
|
||||||
|
* If you're the author a higher-level library on top of the basic SDK, and the
|
||||||
|
* "Photo Edit" app's server code is using your library to access Dropbox, you should append
|
||||||
|
* your library's name and version to form the full identifier. For example,
|
||||||
|
* if your library is called "File Picker", you might set this field to:
|
||||||
|
* <code>"PhotoEditServer/1.3 FilePicker/0.1-beta"</code>
|
||||||
|
*
|
||||||
|
* The exact format of the <code>User-Agent</code> header is described in
|
||||||
|
* <a href="http://tools.ietf.org/html/rfc2616#section-3.8">section 3.8 of the HTTP specification</a>.
|
||||||
|
*
|
||||||
|
* Note that underlying HTTP client may append other things to the <code>User-Agent</code>, such as
|
||||||
|
* the name of the library being used to actually make the HTTP request (such as cURL).
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function getClientIdentifier() { return $this->clientIdentifier; }
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $clientIdentifier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The locale of the user of your application. Some API calls return localized
|
||||||
|
* data and error messages; this "user locale" setting determines which locale
|
||||||
|
* the server should use to localize those strings.
|
||||||
|
*
|
||||||
|
* @return null|string
|
||||||
|
*/
|
||||||
|
function getUserLocale() { return $this->userLocale; }
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $userLocale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param AppInfo $appInfo
|
||||||
|
* See {@link getAppInfo()}
|
||||||
|
* @param string $clientIdentifier
|
||||||
|
* See {@link getClientIdentifier()}
|
||||||
|
* @param null|string $userLocale
|
||||||
|
* See {@link getUserLocale()}
|
||||||
|
*/
|
||||||
|
function __construct($appInfo, $clientIdentifier, $userLocale = null)
|
||||||
|
{
|
||||||
|
AppInfo::checkArg("appInfo", $appInfo);
|
||||||
|
Checker::argStringNonEmpty("clientIdentifier", $clientIdentifier);
|
||||||
|
Checker::argStringNonEmptyOrNull("userLocale", $userLocale);
|
||||||
|
|
||||||
|
$this->appInfo = $appInfo;
|
||||||
|
$this->clientIdentifier = $clientIdentifier;
|
||||||
|
$this->userLocale = $userLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _getAuthorizeUrl($redirectUri, $state)
|
||||||
|
{
|
||||||
|
return RequestUtil::buildUrl(
|
||||||
|
$this->userLocale,
|
||||||
|
$this->appInfo->getHost()->getWeb(),
|
||||||
|
"1/oauth2/authorize",
|
||||||
|
array(
|
||||||
|
"client_id" => $this->appInfo->getKey(),
|
||||||
|
"response_type" => "code",
|
||||||
|
"redirect_uri" => $redirectUri,
|
||||||
|
"state" => $state,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _finish($code, $originalRedirectUri)
|
||||||
|
{
|
||||||
|
$url = RequestUtil::buildUri($this->appInfo->getHost()->getApi(), "1/oauth2/token");
|
||||||
|
$params = array(
|
||||||
|
"grant_type" => "authorization_code",
|
||||||
|
"code" => $code,
|
||||||
|
"redirect_uri" => $originalRedirectUri,
|
||||||
|
"locale" => $this->userLocale,
|
||||||
|
);
|
||||||
|
|
||||||
|
$curl = RequestUtil::mkCurlWithoutAuth($this->clientIdentifier, $url);
|
||||||
|
|
||||||
|
// Add Basic auth header.
|
||||||
|
$basic_auth = $this->appInfo->getKey() . ":" . $this->appInfo->getSecret();
|
||||||
|
$curl->addHeader("Authorization: Basic ".base64_encode($basic_auth));
|
||||||
|
|
||||||
|
$curl->set(CURLOPT_POST, true);
|
||||||
|
$curl->set(CURLOPT_POSTFIELDS, RequestUtil::buildPostBody($params));
|
||||||
|
|
||||||
|
$curl->set(CURLOPT_RETURNTRANSFER, true);
|
||||||
|
$response = $curl->exec();
|
||||||
|
|
||||||
|
if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
|
||||||
|
|
||||||
|
$parts = RequestUtil::parseResponseJson($response->body);
|
||||||
|
|
||||||
|
if (!array_key_exists('token_type', $parts) or !is_string($parts['token_type'])) {
|
||||||
|
throw new Exception_BadResponse("Missing \"token_type\" field.");
|
||||||
|
}
|
||||||
|
$tokenType = $parts['token_type'];
|
||||||
|
if (!array_key_exists('access_token', $parts) or !is_string($parts['access_token'])) {
|
||||||
|
throw new Exception_BadResponse("Missing \"access_token\" field.");
|
||||||
|
}
|
||||||
|
$accessToken = $parts['access_token'];
|
||||||
|
if (!array_key_exists('uid', $parts) or !is_string($parts['uid'])) {
|
||||||
|
throw new Exception_BadResponse("Missing \"uid\" string field.");
|
||||||
|
}
|
||||||
|
$userId = $parts['uid'];
|
||||||
|
|
||||||
|
if ($tokenType !== "Bearer" && $tokenType !== "bearer") {
|
||||||
|
throw new Exception_BadResponse("Unknown \"token_type\"; expecting \"Bearer\", got "
|
||||||
|
.Client::q($tokenType));
|
||||||
|
}
|
||||||
|
|
||||||
|
return array($accessToken, $userId);
|
||||||
|
}
|
||||||
|
}
|
20
modules/Dropbox/WebAuthException/BadRequest.php
Normal file
20
modules/Dropbox/WebAuthException/BadRequest.php
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown if the redirect URL was missing parameters or if the given parameters were not valid.
|
||||||
|
*
|
||||||
|
* The recommended action is to show an HTTP 400 error page.
|
||||||
|
*/
|
||||||
|
class WebAuthException_BadRequest extends \Exception
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $message
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function __construct($message)
|
||||||
|
{
|
||||||
|
parent::__construct($message);
|
||||||
|
}
|
||||||
|
}
|
21
modules/Dropbox/WebAuthException/BadState.php
Normal file
21
modules/Dropbox/WebAuthException/BadState.php
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown if all the parameters are correct, but there's no CSRF token in the session. This
|
||||||
|
* probably means that the session expired.
|
||||||
|
*
|
||||||
|
* The recommended action is to redirect the user's browser to try the approval process again.
|
||||||
|
*/
|
||||||
|
class WebAuthException_BadState extends \Exception
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $message
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct("Missing CSRF token in session.");
|
||||||
|
}
|
||||||
|
}
|
21
modules/Dropbox/WebAuthException/Csrf.php
Normal file
21
modules/Dropbox/WebAuthException/Csrf.php
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown if the given 'state' parameter doesn't contain the CSRF token from the user's session.
|
||||||
|
* This is blocked to prevent CSRF attacks.
|
||||||
|
*
|
||||||
|
* The recommended action is to respond with an HTTP 403 error page.
|
||||||
|
*/
|
||||||
|
class WebAuthException_Csrf extends \Exception
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $message
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function __construct($message)
|
||||||
|
{
|
||||||
|
parent::__construct($message);
|
||||||
|
}
|
||||||
|
}
|
18
modules/Dropbox/WebAuthException/NotApproved.php
Normal file
18
modules/Dropbox/WebAuthException/NotApproved.php
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown if the user chose not to grant your app access to their Dropbox account.
|
||||||
|
*/
|
||||||
|
class WebAuthException_NotApproved extends \Exception
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $message
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function __construct($message)
|
||||||
|
{
|
||||||
|
parent::__construct($message);
|
||||||
|
}
|
||||||
|
}
|
18
modules/Dropbox/WebAuthException/Provider.php
Normal file
18
modules/Dropbox/WebAuthException/Provider.php
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown if Dropbox returns some other error about the authorization request.
|
||||||
|
*/
|
||||||
|
class WebAuthException_Provider extends \Exception
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $message
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function __construct($message)
|
||||||
|
{
|
||||||
|
parent::__construct($message);
|
||||||
|
}
|
||||||
|
}
|
78
modules/Dropbox/WebAuthNoRedirect.php
Normal file
78
modules/Dropbox/WebAuthNoRedirect.php
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OAuth 2 code-based authorization for apps that can't provide a redirect URI, typically
|
||||||
|
* command-line example apps.
|
||||||
|
*
|
||||||
|
* Use {@link WebAuth::start()} and {@link WebAuth::getToken()} to guide your
|
||||||
|
* user through the process of giving your app access to their Dropbox account. At the end, you
|
||||||
|
* will have an {@link AccessToken}, which you can pass to {@link Client} and start making
|
||||||
|
* API calls.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* use \Dropbox as dbx;
|
||||||
|
* $appInfo = dbx\AppInfo::loadFromJsonFile(...);
|
||||||
|
* $clientIdentifier = "my-app/1.0";
|
||||||
|
* $webAuth = new dbx\WebAuthNoRedirect($appInfo, $clientIdentifier, ...);
|
||||||
|
*
|
||||||
|
* $authorizeUrl = $webAuth->start();
|
||||||
|
*
|
||||||
|
* print("1. Go to: $authorizeUrl\n");
|
||||||
|
* print("2. Click "Allow" (you might have to log in first).\n");
|
||||||
|
* print("3. Copy the authorization code.\n");
|
||||||
|
* $code = \trim(\readline("4. Enter the authorization code here: "));
|
||||||
|
*
|
||||||
|
* try {
|
||||||
|
* list($accessToken, $userId) = $webAuth->finish($code);
|
||||||
|
* }
|
||||||
|
* catch (dbx\Exception $ex) {
|
||||||
|
* print("Error communicating with Dropbox API: " . $ex->getMessage() . "\n");
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* $client = dbx\Client($accessToken, $clientIdentifier, ...);
|
||||||
|
* </code>
|
||||||
|
*/
|
||||||
|
class WebAuthNoRedirect extends WebAuthBase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns the URL of the authorization page the user must visit. If the user approves
|
||||||
|
* your app, they will be shown the authorization code on the web page. They will need to
|
||||||
|
* copy/paste that code into your application so your app can pass it to
|
||||||
|
* {@link finish}.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* An authorization URL. Redirect the user's browser to this URL. After the user decides
|
||||||
|
* whether to authorize your app or not, Dropbox will show the user an authorization code,
|
||||||
|
* which the user will need to give to your application (e.g. via copy/paste).
|
||||||
|
*/
|
||||||
|
function start()
|
||||||
|
{
|
||||||
|
return $this->_getAuthorizeUrl(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this after the user has visited the authorize URL returned by {@link start()},
|
||||||
|
* approved your app, was presented with an authorization code by Dropbox, and has copy/paste'd
|
||||||
|
* that authorization code into your app.
|
||||||
|
*
|
||||||
|
* @param string $code
|
||||||
|
* The authorization code provided to the user by Dropbox.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* A <code>list(string $accessToken, string $userId)</code>, where
|
||||||
|
* <code>$accessToken</code> can be used to construct a {@link Client} and
|
||||||
|
* <code>$userId</code> is the user ID of the user's Dropbox account.
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
* Thrown if there's an error getting the access token from Dropbox.
|
||||||
|
*/
|
||||||
|
function finish($code)
|
||||||
|
{
|
||||||
|
Checker::argStringNonEmpty("code", $code);
|
||||||
|
return $this->_finish($code, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
116
modules/Dropbox/WriteMode.php
Normal file
116
modules/Dropbox/WriteMode.php
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes how a file should be saved when it is written to Dropbox.
|
||||||
|
*/
|
||||||
|
final class WriteMode
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The URL parameters to pass to the file uploading endpoint to achieve the
|
||||||
|
* desired write mode.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $extraParams;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
private function __construct($extraParams)
|
||||||
|
{
|
||||||
|
$this->extraParams = $extraParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function getExtraParams()
|
||||||
|
{
|
||||||
|
return $this->extraParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link WriteMode} for adding a new file. If a file at the specified path already
|
||||||
|
* exists, the new file will be renamed automatically.
|
||||||
|
*
|
||||||
|
* For example, if you're trying to upload a file to "/Notes/Groceries.txt", but there's
|
||||||
|
* already a file there, your file will be written to "/Notes/Groceries (1).txt".
|
||||||
|
*
|
||||||
|
* You can determine whether your file was renamed by checking the "path" field of the
|
||||||
|
* metadata object returned by the API call.
|
||||||
|
*
|
||||||
|
* @return WriteMode
|
||||||
|
*/
|
||||||
|
static function add()
|
||||||
|
{
|
||||||
|
if (self::$addInstance === null) {
|
||||||
|
self::$addInstance = new WriteMode(array("overwrite" => "false"));
|
||||||
|
}
|
||||||
|
return self::$addInstance;
|
||||||
|
}
|
||||||
|
private static $addInstance = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link WriteMode} for forcing a file to be at a certain path. If there's already
|
||||||
|
* a file at that path, the existing file will be overwritten. If there's a folder at that
|
||||||
|
* path, however, it will not be overwritten and the API call will fail.
|
||||||
|
*
|
||||||
|
* @return WriteMode
|
||||||
|
*/
|
||||||
|
static function force()
|
||||||
|
{
|
||||||
|
if (self::$forceInstance === null) {
|
||||||
|
self::$forceInstance = new WriteMode(array("overwrite" => "true"));
|
||||||
|
}
|
||||||
|
return self::$forceInstance;
|
||||||
|
}
|
||||||
|
private static $forceInstance = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link WriteMode} for updating an existing file. This is useful for when you
|
||||||
|
* have downloaded a file, made modifications, and want to save your modifications back to
|
||||||
|
* Dropbox. You need to specify the revision of the copy of the file you downloaded (it's
|
||||||
|
* the "rev" parameter of the file's metadata object).
|
||||||
|
*
|
||||||
|
* If, when you attempt to save, the revision of the file currently on Dropbox matches
|
||||||
|
* $revToReplace, the file on Dropbox will be overwritten with the new contents you provide.
|
||||||
|
*
|
||||||
|
* If the revision of the file currently on Dropbox doesn't match $revToReplace, Dropbox will
|
||||||
|
* create a new file and save your contents to that file. For example, if the original file
|
||||||
|
* path is "/Notes/Groceries.txt", the new file's path might be
|
||||||
|
* "/Notes/Groceries (conflicted copy).txt".
|
||||||
|
*
|
||||||
|
* You can determine whether your file was renamed by checking the "path" field of the
|
||||||
|
* metadata object returned by the API call.
|
||||||
|
*
|
||||||
|
* @param string $revToReplace
|
||||||
|
* @return WriteMode
|
||||||
|
*/
|
||||||
|
static function update($revToReplace)
|
||||||
|
{
|
||||||
|
return new WriteMode(array("parent_rev" => $revToReplace));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that a function argument is of type <code>WriteMode</code>.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
static function checkArg($argName, $argValue)
|
||||||
|
{
|
||||||
|
if (!($argValue instanceof self)) Checker::throwError($argName, $argValue, __CLASS__);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that a function argument is either <code>null</code> or of type
|
||||||
|
* <code>WriteMode</code>.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
static function checkArgOrNull($argName, $argValue)
|
||||||
|
{
|
||||||
|
if ($argValue === null) return;
|
||||||
|
if (!($argValue instanceof self)) Checker::throwError($argName, $argValue, __CLASS__);
|
||||||
|
}
|
||||||
|
}
|
31
modules/Dropbox/autoload.php
Normal file
31
modules/Dropbox/autoload.php
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
namespace Dropbox;
|
||||||
|
|
||||||
|
// The Dropbox SDK autoloader. You probably shouldn't be using this. Instead,
|
||||||
|
// use a global autoloader, like the Composer autoloader.
|
||||||
|
//
|
||||||
|
// But if you really don't want to use a global autoloader, do this:
|
||||||
|
//
|
||||||
|
// require_once "<path-to-here>/Dropbox/autoload.php"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function autoload($name)
|
||||||
|
{
|
||||||
|
// If the name doesn't start with "Dropbox\", then its not once of our classes.
|
||||||
|
if (\substr_compare($name, "Dropbox\\", 0, 8) !== 0) return;
|
||||||
|
|
||||||
|
// Take the "Dropbox\" prefix off.
|
||||||
|
$stem = \substr($name, 8);
|
||||||
|
|
||||||
|
// Convert "\" and "_" to path separators.
|
||||||
|
$pathified_stem = \str_replace(array("\\", "_"), '/', $stem);
|
||||||
|
|
||||||
|
$path = __DIR__ . "/" . $pathified_stem . ".php";
|
||||||
|
if (\is_file($path)) {
|
||||||
|
require_once $path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
\spl_autoload_register('Dropbox\autoload');
|
341
modules/Dropbox/trusted-certs.crt
Normal file
341
modules/Dropbox/trusted-certs.crt
Normal file
|
@ -0,0 +1,341 @@
|
||||||
|
# Subject: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Server CA/emailAddress=server-certs@thawte.com <server-certs@thawte.com>
|
||||||
|
# Issuer: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Server CA/emailAddress= server-certs@thawte.com
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx
|
||||||
|
FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
|
||||||
|
VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
|
||||||
|
biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm
|
||||||
|
MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx
|
||||||
|
MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT
|
||||||
|
DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3
|
||||||
|
dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl
|
||||||
|
cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3
|
||||||
|
DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD
|
||||||
|
gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91
|
||||||
|
yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX
|
||||||
|
L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj
|
||||||
|
EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG
|
||||||
|
7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e
|
||||||
|
QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ
|
||||||
|
qdq5snUb9kLy78fyGPmJvKP/iiMucEc=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
# Subject: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com <premium-server@thawte.com>
|
||||||
|
# Issuer: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com <premium-server@thawte.com>
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx
|
||||||
|
FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
|
||||||
|
VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
|
||||||
|
biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy
|
||||||
|
dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t
|
||||||
|
MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB
|
||||||
|
MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG
|
||||||
|
A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp
|
||||||
|
b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl
|
||||||
|
cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv
|
||||||
|
bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE
|
||||||
|
VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ
|
||||||
|
ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR
|
||||||
|
uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG
|
||||||
|
9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI
|
||||||
|
hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM
|
||||||
|
pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
# Subject: C=US, O=VeriSign, Inc., OU=Class 1 Public Primary Certification Authority
|
||||||
|
# Issuer: C=US, O=VeriSign, Inc., OU=Class 1 Public Primary Certification Authority
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICPDCCAaUCEDJQM89Q0VbzXIGtZVxPyCUwDQYJKoZIhvcNAQECBQAwXzELMAkG
|
||||||
|
A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
|
||||||
|
cyAxIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
|
||||||
|
MDEyOTAwMDAwMFoXDTIwMDEwNzIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
|
||||||
|
BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAxIFB1YmxpYyBQcmlt
|
||||||
|
YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
|
||||||
|
ADCBiQKBgQDlGb9to1ZhLZlIcfZn3rmN67eehoAKkQ76OCWvRoiC5XOooJskXQ0f
|
||||||
|
zGVuDLDQVoQYh5oGmxChc9+0WDlrbsH2FdWoqD+qEgaNMax/sDTXjzRniAnNFBHi
|
||||||
|
TkVWaR94AoDa3EeRKbs2yWNcxeDXLYd7obcysHswuiovMaruo2fa2wIDAQABMA0G
|
||||||
|
CSqGSIb3DQEBAgUAA4GBAEtEZmBoZOSYG/OwcuaViXzde7OVwB0u2NgZ0C00PcZQ
|
||||||
|
mhCGjKo/O6gE/DdSlcPZydvN8oYGxLEb8IKIMEKOF1AcZHq4PplJdJf8rAJD+5YM
|
||||||
|
VgQlDHx8h50kp9jwMim1pN9dokzFFjKoQvZFprY2ueC/ZTaTwtLXa9zeWdaiNfhF
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
# Subject: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority
|
||||||
|
# Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG
|
||||||
|
A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
|
||||||
|
cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
|
||||||
|
MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
|
||||||
|
BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
|
||||||
|
YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
|
||||||
|
ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
|
||||||
|
BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
|
||||||
|
I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
|
||||||
|
CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do
|
||||||
|
lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc
|
||||||
|
AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
# Subject: C=US, O=RSA Data Security, Inc., OU=Secure Server Certification Authority
|
||||||
|
# Issuer: C=US, O=RSA Data Security, Inc., OU=Secure Server Certification Authority
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICNDCCAaECEAKtZn5ORf5eV288mBle3cAwDQYJKoZIhvcNAQECBQAwXzELMAkG
|
||||||
|
A1UEBhMCVVMxIDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYD
|
||||||
|
VQQLEyVTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk0
|
||||||
|
MTEwOTAwMDAwMFoXDTEwMDEwNzIzNTk1OVowXzELMAkGA1UEBhMCVVMxIDAeBgNV
|
||||||
|
BAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQLEyVTZWN1cmUgU2Vy
|
||||||
|
dmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGbMA0GCSqGSIb3DQEBAQUAA4GJ
|
||||||
|
ADCBhQJ+AJLOesGugz5aqomDV6wlAXYMra6OLDfO6zV4ZFQD5YRAUcm/jwjiioII
|
||||||
|
0haGN1XpsSECrXZogZoFokvJSyVmIlZsiAeP94FZbYQHZXATcXY+m3dM41CJVphI
|
||||||
|
uR2nKRoTLkoRWZweFdVJVCxzOmmCsZc5nG1wZ0jl3S3WyB57AgMBAAEwDQYJKoZI
|
||||||
|
hvcNAQECBQADfgBl3X7hsuyw4jrg7HFGmhkRuNPHoLQDQCYCPgmc4RKz0Vr2N6W3
|
||||||
|
YQO2WxZpO8ZECAyIUwxrl0nHPjXcbLm7qt9cuzovk2C2qUtN8iD3zV9/ZHuO3ABc
|
||||||
|
1/p3yjkWWW8O6tO1g39NTUJWdrTJXwT4OPjr0l91X817/OWOgHz8UA==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
# Subject: C=US, O=Equifax Secure Inc., CN=Equifax Secure Global eBusiness CA-1
|
||||||
|
# Issuer: C=US, O=Equifax Secure Inc., CN=Equifax Secure Global eBusiness CA-1
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc
|
||||||
|
MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT
|
||||||
|
ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw
|
||||||
|
MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj
|
||||||
|
dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l
|
||||||
|
c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC
|
||||||
|
UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc
|
||||||
|
58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/
|
||||||
|
o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH
|
||||||
|
MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr
|
||||||
|
aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA
|
||||||
|
A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA
|
||||||
|
Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv
|
||||||
|
8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
# Subject: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware
|
||||||
|
# Issuer: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB
|
||||||
|
lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
|
||||||
|
Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
|
||||||
|
dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt
|
||||||
|
SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG
|
||||||
|
A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe
|
||||||
|
MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v
|
||||||
|
d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh
|
||||||
|
cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn
|
||||||
|
0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ
|
||||||
|
M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a
|
||||||
|
MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd
|
||||||
|
oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI
|
||||||
|
DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy
|
||||||
|
oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD
|
||||||
|
VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0
|
||||||
|
dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy
|
||||||
|
bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF
|
||||||
|
BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM
|
||||||
|
//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli
|
||||||
|
CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE
|
||||||
|
CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t
|
||||||
|
3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS
|
||||||
|
KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
# Subject: C=US, O=Network Solutions L.L.C., CN=Network Solutions Certificate Authority
|
||||||
|
# Issuer: C=US, O=Network Solutions L.L.C., CN=Network Solutions Certificate Authority
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi
|
||||||
|
MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu
|
||||||
|
MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp
|
||||||
|
dHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJV
|
||||||
|
UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO
|
||||||
|
ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG
|
||||||
|
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwz
|
||||||
|
c7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPP
|
||||||
|
OCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl
|
||||||
|
mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnF
|
||||||
|
BgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4
|
||||||
|
qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcw
|
||||||
|
gZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB
|
||||||
|
BjAPBgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwu
|
||||||
|
bmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3Jp
|
||||||
|
dHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc8
|
||||||
|
6fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVRDuwduIj/
|
||||||
|
h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH
|
||||||
|
/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv
|
||||||
|
wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN
|
||||||
|
pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
# Subject: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority
|
||||||
|
# Issuer: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh
|
||||||
|
MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE
|
||||||
|
YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3
|
||||||
|
MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo
|
||||||
|
ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg
|
||||||
|
MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN
|
||||||
|
ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA
|
||||||
|
PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w
|
||||||
|
wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi
|
||||||
|
EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY
|
||||||
|
avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+
|
||||||
|
YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE
|
||||||
|
sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h
|
||||||
|
/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5
|
||||||
|
IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj
|
||||||
|
YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD
|
||||||
|
ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy
|
||||||
|
OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P
|
||||||
|
TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
|
||||||
|
HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER
|
||||||
|
dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf
|
||||||
|
ReYNnyicsbkqWletNw+vHX/bvZ8=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
# Subject: C=US, ST=Arizona, L=Scottsdale, O=GoDaddy.com, Inc., OU=http://certificates.godaddy.com/repository<http://certificates.godaddy.com/repository>, CN=Go Daddy Secure Certification Authority/serialNumber=07969287
|
||||||
|
# Issuer: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVMx
|
||||||
|
ITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g
|
||||||
|
RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMTYw
|
||||||
|
MTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UECBMH
|
||||||
|
QXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5j
|
||||||
|
b20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5j
|
||||||
|
b20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlcnRpZmlj
|
||||||
|
YXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJKoZIhvcN
|
||||||
|
AQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTtwY6vj3D3H
|
||||||
|
KrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqVTr9vcyOdQm
|
||||||
|
VZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aLGbqGmu75RpR
|
||||||
|
SgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo7RJlbmr2EkRT
|
||||||
|
cDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgWJCJjPOq8lh8BJ
|
||||||
|
6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAwEAAaOCATIwggEu
|
||||||
|
MB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVHSMEGDAWgBTSxLDS
|
||||||
|
kdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEAMDMGCCsGAQUFBwEB
|
||||||
|
BCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWRkeS5jb20wRgYDVR0f
|
||||||
|
BD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBv
|
||||||
|
c2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVHSAAMDgwNgYIKwYBBQUH
|
||||||
|
AgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTAO
|
||||||
|
BgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBANKGwOy9+aG2Z+5mC6IG
|
||||||
|
OgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPIUyIXvJxwqoJKSQ3kbTJSMU
|
||||||
|
A2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL5CkKSkB2XIsKd83ASe8T+5o
|
||||||
|
0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9p0iRFEUOOjZv2kWzRaJBydTX
|
||||||
|
RE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsxuxN89txJx9OjxUUAiKEngHUuH
|
||||||
|
qDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZEjYx8WnM25sgVjOuH0aBsXBTWV
|
||||||
|
U+4=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
# Subject: C=US, ST=Arizona, L=Scottsdale, O=GoDaddy.com, Inc., CN=Go Daddy Root Certificate Authority - G2
|
||||||
|
# Issuer: C=US, ST=Arizona, L=Scottsdale, O=GoDaddy.com, Inc., CN=Go Daddy Root Certificate Authority - G2
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx
|
||||||
|
EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT
|
||||||
|
EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp
|
||||||
|
ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz
|
||||||
|
NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH
|
||||||
|
EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE
|
||||||
|
AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw
|
||||||
|
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD
|
||||||
|
E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH
|
||||||
|
/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy
|
||||||
|
DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh
|
||||||
|
GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR
|
||||||
|
tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA
|
||||||
|
AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
|
||||||
|
FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX
|
||||||
|
WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu
|
||||||
|
9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr
|
||||||
|
gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo
|
||||||
|
2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO
|
||||||
|
LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI
|
||||||
|
4uJEvlz36hz1
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
# Subject: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA
|
||||||
|
# Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
|
||||||
|
MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
|
||||||
|
YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG
|
||||||
|
EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg
|
||||||
|
R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9
|
||||||
|
9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq
|
||||||
|
fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv
|
||||||
|
iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU
|
||||||
|
1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+
|
||||||
|
bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW
|
||||||
|
MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA
|
||||||
|
ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l
|
||||||
|
uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn
|
||||||
|
Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS
|
||||||
|
tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF
|
||||||
|
PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un
|
||||||
|
hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV
|
||||||
|
5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
# Subject: C=US, O=GeoTrust Inc., CN=GeoTrust Primary Certification Authority
|
||||||
|
# Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Primary Certification Authority
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY
|
||||||
|
MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo
|
||||||
|
R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx
|
||||||
|
MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK
|
||||||
|
Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp
|
||||||
|
ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
|
||||||
|
AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9
|
||||||
|
AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA
|
||||||
|
ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0
|
||||||
|
7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W
|
||||||
|
kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI
|
||||||
|
mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G
|
||||||
|
A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ
|
||||||
|
KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1
|
||||||
|
6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl
|
||||||
|
4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K
|
||||||
|
oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj
|
||||||
|
UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU
|
||||||
|
AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
# Subject: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority
|
||||||
|
# Issuer: L=ValiCert Validation Network, O=ValiCert, Inc., OU=ValiCert Class 2 Policy Validation Authority, CN=http://www.valicert.com//emailAddress=info@valicert.com<http://www.valicert.com//emailAddress=info@valicert.com>
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIE+zCCBGSgAwIBAgICAQ0wDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1Zh
|
||||||
|
bGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIElu
|
||||||
|
Yy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24g
|
||||||
|
QXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAe
|
||||||
|
BgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTA0MDYyOTE3MDYyMFoX
|
||||||
|
DTI0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBE
|
||||||
|
YWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0
|
||||||
|
aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgC
|
||||||
|
ggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv
|
||||||
|
2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+q
|
||||||
|
N1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiO
|
||||||
|
r18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lN
|
||||||
|
f4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+YihfukEH
|
||||||
|
U1jPEX44dMX4/7VpkI+EdOqXG68CAQOjggHhMIIB3TAdBgNVHQ4EFgQU0sSw0pHU
|
||||||
|
TBFxs2HLPaH+3ahq1OMwgdIGA1UdIwSByjCBx6GBwaSBvjCBuzEkMCIGA1UEBxMb
|
||||||
|
VmFsaUNlcnQgVmFsaWRhdGlvbiBOZXR3b3JrMRcwFQYDVQQKEw5WYWxpQ2VydCwg
|
||||||
|
SW5jLjE1MDMGA1UECxMsVmFsaUNlcnQgQ2xhc3MgMiBQb2xpY3kgVmFsaWRhdGlv
|
||||||
|
biBBdXRob3JpdHkxITAfBgNVBAMTGGh0dHA6Ly93d3cudmFsaWNlcnQuY29tLzEg
|
||||||
|
MB4GCSqGSIb3DQEJARYRaW5mb0B2YWxpY2VydC5jb22CAQEwDwYDVR0TAQH/BAUw
|
||||||
|
AwEB/zAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLmdv
|
||||||
|
ZGFkZHkuY29tMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jZXJ0aWZpY2F0ZXMu
|
||||||
|
Z29kYWRkeS5jb20vcmVwb3NpdG9yeS9yb290LmNybDBLBgNVHSAERDBCMEAGBFUd
|
||||||
|
IAAwODA2BggrBgEFBQcCARYqaHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNv
|
||||||
|
bS9yZXBvc2l0b3J5MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOBgQC1
|
||||||
|
QPmnHfbq/qQaQlpE9xXUhUaJwL6e4+PrxeNYiY+Sn1eocSxI0YGyeR+sBjUZsE4O
|
||||||
|
WBsUs5iB0QQeyAfJg594RAoYC5jcdnplDQ1tgMQLARzLrUc+cb53S8wGd9D0Vmsf
|
||||||
|
SxOaFIqII6hR8INMqzW/Rn453HWkrugp++85j09VZw==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
# Subject: L=ValiCert Validation Network, O=ValiCert, Inc., OU=ValiCert Class 2 Policy Validation Authority, CN=http://www.valicert.com//emailAddress=info@valicert.com<http://www.valicert.com//emailAddress=info@valicert.com>
|
||||||
|
# Issuer: L=ValiCert Validation Network, O=ValiCert, Inc., OU=ValiCert Class 2 Policy Validation Authority, CN=http://www.valicert.com//emailAddress=info@valicert.com<http://www.valicert.com//emailAddress=info@valicert.com>
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
|
||||||
|
IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
|
||||||
|
BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
|
||||||
|
aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
|
||||||
|
9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy
|
||||||
|
NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
|
||||||
|
azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
|
||||||
|
YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
|
||||||
|
Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
|
||||||
|
cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY
|
||||||
|
dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9
|
||||||
|
WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS
|
||||||
|
v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v
|
||||||
|
UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu
|
||||||
|
IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC
|
||||||
|
W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
|
491
modules/catalog/dropbox.catalog.php
Normal file
491
modules/catalog/dropbox.catalog.php
Normal file
|
@ -0,0 +1,491 @@
|
||||||
|
<?php
|
||||||
|
/* vim:set softtabstop=4 shiftwidth=4 expandtab: */
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* LICENSE: GNU General Public License, version 2 (GPLv2)
|
||||||
|
* Copyright 2001 - 2013 Ampache.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License v2
|
||||||
|
* as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once Config::get('prefix') . '/modules/Dropbox/autoload.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dropbox Catalog Class
|
||||||
|
*
|
||||||
|
* This class handles all actual work in regards to remote Dropbox catalogs.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class Catalog_dropbox extends Catalog {
|
||||||
|
|
||||||
|
private $version = '000001';
|
||||||
|
private $type = 'dropbox';
|
||||||
|
private $description = 'Remote Dropbox Catalog';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get_description
|
||||||
|
* This returns the description of this catalog
|
||||||
|
*/
|
||||||
|
public function get_description() {
|
||||||
|
|
||||||
|
return $this->description;
|
||||||
|
|
||||||
|
} // get_description
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get_version
|
||||||
|
* This returns the current version
|
||||||
|
*/
|
||||||
|
public function get_version() {
|
||||||
|
|
||||||
|
return $this->version;
|
||||||
|
|
||||||
|
} // get_version
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get_type
|
||||||
|
* This returns the current catalog type
|
||||||
|
*/
|
||||||
|
public function get_type() {
|
||||||
|
|
||||||
|
return $this->type;
|
||||||
|
|
||||||
|
} // get_type
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get_create_help
|
||||||
|
* This returns hints on catalog creation
|
||||||
|
*/
|
||||||
|
public function get_create_help() {
|
||||||
|
|
||||||
|
$help = "<ul><li>Go to https://www.dropbox.com/developers/apps/create</li>" .
|
||||||
|
"<li>Select 'Dropbox API app'</li>" .
|
||||||
|
"<li>Select 'Files and datastores'</li>" .
|
||||||
|
"<li>Select 'No' at 'Can your app be limited to its own, private folder?'</li>" .
|
||||||
|
"<li>Select 'Specific file types' at 'What type of files does your app need access to?'</li>" .
|
||||||
|
"<li>Check Videos and Audio files</li>" .
|
||||||
|
"<li>Give a name to your application and create it</li>" .
|
||||||
|
//"<li>Add the following OAuth redirect URIs: <i>" . Config::get('web_path') . "/admin/catalog.php</i></li>" .
|
||||||
|
"<li>Copy your App key and App secret here</li></ul>";
|
||||||
|
return $help;
|
||||||
|
|
||||||
|
} // get_create_help
|
||||||
|
|
||||||
|
/**
|
||||||
|
* is_installed
|
||||||
|
* This returns true or false if remote catalog is installed
|
||||||
|
*/
|
||||||
|
public function is_installed() {
|
||||||
|
|
||||||
|
$sql = "DESCRIBE `catalog_dropbox`";
|
||||||
|
$db_results = Dba::query($sql);
|
||||||
|
|
||||||
|
return Dba::num_rows($db_results);
|
||||||
|
|
||||||
|
|
||||||
|
} // is_installed
|
||||||
|
|
||||||
|
/**
|
||||||
|
* install
|
||||||
|
* This function installs the remote catalog
|
||||||
|
*/
|
||||||
|
public function install() {
|
||||||
|
|
||||||
|
$sql = "CREATE TABLE `catalog_dropbox` (`id` INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY , ".
|
||||||
|
"`apikey` VARCHAR( 255 ) COLLATE utf8_unicode_ci NOT NULL , " .
|
||||||
|
"`secret` VARCHAR( 255 ) COLLATE utf8_unicode_ci NOT NULL , " .
|
||||||
|
"`path` VARCHAR( 255 ) COLLATE utf8_unicode_ci NOT NULL , " .
|
||||||
|
"`authtoken` VARCHAR( 255 ) COLLATE utf8_unicode_ci NOT NULL , " .
|
||||||
|
"`getchunk` TINYINT(1) NOT NULL, " .
|
||||||
|
"`catalog_id` INT( 11 ) NOT NULL" .
|
||||||
|
") ENGINE = MYISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci";
|
||||||
|
$db_results = Dba::query($sql);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} // install
|
||||||
|
|
||||||
|
public function catalog_fields() {
|
||||||
|
|
||||||
|
$fields['apikey'] = array('description' => T_('API Key'), 'type'=>'textbox');
|
||||||
|
$fields['secret'] = array('description' => T_('Secret'), 'type'=>'password');
|
||||||
|
$fields['path'] = array('description' => T_('Path'), 'type'=>'textbox', 'value' => '/');
|
||||||
|
$fields['getchunk'] = array('description' => T_('Get chunked files on analyze'), 'type'=>'checkbox', 'value' => true);
|
||||||
|
|
||||||
|
return $fields;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public $apikey;
|
||||||
|
public $secret;
|
||||||
|
public $path;
|
||||||
|
public $authtoken;
|
||||||
|
public $getchunk;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* Catalog class constructor, pulls catalog information
|
||||||
|
*/
|
||||||
|
public function __construct($catalog_id = null) {
|
||||||
|
if ($catalog_id) {
|
||||||
|
$this->id = intval($catalog_id);
|
||||||
|
$info = $this->get_info($catalog_id);
|
||||||
|
|
||||||
|
foreach ($info as $key=>$value) {
|
||||||
|
$this->$key = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create_type
|
||||||
|
*
|
||||||
|
* This creates a new catalog type entry for a catalog
|
||||||
|
* It checks to make sure its parameters is not already used before creating
|
||||||
|
* the catalog.
|
||||||
|
*/
|
||||||
|
public static function create_type($catalog_id, $data) {
|
||||||
|
|
||||||
|
$apikey = $data['apikey'];
|
||||||
|
$secret = $data['secret'];
|
||||||
|
$path = $data['path'];
|
||||||
|
$getchunk = $data['getchunk'];
|
||||||
|
|
||||||
|
if (!strlen($apikey) OR !strlen($secret)) {
|
||||||
|
Error::add('general', T_('Error: API Key and Secret Required for Dropbox Catalogs'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pathError = Dropbox\Path::findError($path);
|
||||||
|
if ($pathError !== null) {
|
||||||
|
Error::add('general', T_('Invalid <dropbox-path>: ' . $pathError));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure this app isn't already in use by an existing catalog
|
||||||
|
$sql = 'SELECT `id` FROM `catalog_dropbox` WHERE `apikey` = ?';
|
||||||
|
$db_results = Dba::read($sql, array($apikey));
|
||||||
|
|
||||||
|
if (Dba::num_rows($db_results)) {
|
||||||
|
debug_event('catalog', 'Cannot add catalog with duplicate key ' . $apikey, 1);
|
||||||
|
Error::add('general', sprintf(T_('Error: Catalog with %s already exists'), $apikey));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = 'INSERT INTO `catalog_dropbox` (`apikey`, `secret`, `path`, `getchunk`, `catalog_id`) VALUES (?, ?, ?, ?, ?)';
|
||||||
|
Dba::write($sql, array($apikey, $secret, $path, ($getchunk ? 1 : 0), $catalog_id));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getWebAuth() {
|
||||||
|
$appInfo = new Dropbox\AppInfo($this->apikey, $this->secret);
|
||||||
|
$webAuth = new Dropbox\WebAuthNoRedirect($appInfo, "ampache", "en");
|
||||||
|
|
||||||
|
return $webAuth;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function showAuthToken() {
|
||||||
|
$webAuth = $this->getWebAuth();
|
||||||
|
$authurl = $webAuth->start();
|
||||||
|
echo "<br />Go to <strong><a href='" . $authurl . "' target='_blank'>" . $authurl . "</a></strong> to generate the authorization code, then enter it bellow.<br />";
|
||||||
|
echo "<form action='' method='post' enctype='multipart/form-data'>";
|
||||||
|
if ($_POST['action']) {
|
||||||
|
echo "<input type='hidden' name='action' value='add_to_catalog' />";
|
||||||
|
echo "<input type='hidden' name='catalogs[]' value='". $this->id ."' />";
|
||||||
|
}
|
||||||
|
echo "<input type='text' name='authcode' size='30' />";
|
||||||
|
echo "<input type='submit' value='Ok' />";
|
||||||
|
echo "</form>";
|
||||||
|
echo "<br />";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function completeAuthToken() {
|
||||||
|
$webAuth = $this->getWebAuth();
|
||||||
|
list($accessToken, $userId) = $webAuth->finish($this->authcode);
|
||||||
|
debug_event('dropbox_catalog', 'Dropbox authentication token generated for user ' . $userId . '.', 1);
|
||||||
|
$this->authtoken = $accessToken;
|
||||||
|
|
||||||
|
$sql = 'UPDATE `catalog_dropbox` SET `authtoken` = ? WHERE `catalog_id` = ?';
|
||||||
|
Dba::write($sql, array($this->authtoken, $this->catalog_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add_to_catalog
|
||||||
|
* this function adds new files to an
|
||||||
|
* existing catalog
|
||||||
|
*/
|
||||||
|
public function add_to_catalog($options = null) {
|
||||||
|
// Prevent the script from timing out
|
||||||
|
set_time_limit(0);
|
||||||
|
|
||||||
|
if ($options != null) {
|
||||||
|
$this->authcode = $options['authcode'];
|
||||||
|
}
|
||||||
|
|
||||||
|
UI::show_box_top(T_('Running Dropbox Remote Update') . '. . .');
|
||||||
|
$this->update_remote_catalog();
|
||||||
|
UI::show_box_bottom();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} // add_to_catalog
|
||||||
|
|
||||||
|
public function createClient() {
|
||||||
|
if ($this->authcode) {
|
||||||
|
$this->completeAuthToken();
|
||||||
|
}
|
||||||
|
if (!$this->authtoken) {
|
||||||
|
$this->showAuthToken();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new Dropbox\Client($this->authtoken, "ampache", "en");
|
||||||
|
} catch (Dropbox\Exception $e) {
|
||||||
|
debug_event('dropbox_catalog', 'Dropbox authentication error: ' . $ex->getMessage(), 1);
|
||||||
|
$this->showAuthToken();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* update_remote_catalog
|
||||||
|
*
|
||||||
|
* Pulls the data from a remote catalog and adds any missing songs to the
|
||||||
|
* database.
|
||||||
|
*/
|
||||||
|
public function update_remote_catalog() {
|
||||||
|
$client = $this->createClient();
|
||||||
|
if ($client != null) {
|
||||||
|
$this->count = 0;
|
||||||
|
$this->add_files($client, $this->path);
|
||||||
|
|
||||||
|
echo "\n<br />" .
|
||||||
|
printf(T_('Catalog Update Finished. Total Songs: [%s]'), $this->count);
|
||||||
|
echo '<br />';
|
||||||
|
if ($this->count == 0) {
|
||||||
|
echo T_('No songs updated, do you respect the patterns?') . '<br />';
|
||||||
|
}
|
||||||
|
echo '<br />';
|
||||||
|
}else {
|
||||||
|
echo "<p>" . T_('API Error: cannot connect to Dropbox.') . "</p><hr />\n";
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add_files
|
||||||
|
*
|
||||||
|
* Recurses through directories and pulls out all media files
|
||||||
|
*/
|
||||||
|
public function add_files($client, $path) {
|
||||||
|
$metadata = $client->getMetadataWithChildren($path);
|
||||||
|
if ($metadata != null) {
|
||||||
|
// If it's a folder, remove the 'contents' list from $metadata; print that stuff out after.
|
||||||
|
$children = null;
|
||||||
|
if ($metadata['is_dir']) {
|
||||||
|
$children = $metadata['contents'];
|
||||||
|
if ($children !== null && count($children) > 0) {
|
||||||
|
foreach ($children as $child) {
|
||||||
|
if ($child['is_dir']) {
|
||||||
|
$this->add_files($client, $child['path']);
|
||||||
|
} else {
|
||||||
|
$this->add_file($client, $child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->add_file($client, $metadata);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "<p>" . T_('API Error: Cannot access file/folder at ' . $this->path . '.') . "</p><hr />\n";
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function add_file($client, $data) {
|
||||||
|
$file = $data['path'];
|
||||||
|
$filesize = $data['bytes'];
|
||||||
|
if ($filesize > 0) {
|
||||||
|
$is_audio_file = Catalog::is_audio_file($file);
|
||||||
|
|
||||||
|
if ($is_audio_file) {
|
||||||
|
$this->insert_song($client, $file, $filesize);
|
||||||
|
}else {
|
||||||
|
debug_event('read', $data['path'] . " ignored, unknown media file type", 5);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug_event('read', $data['path'] . " ignored, 0 bytes", 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _insert_local_song
|
||||||
|
*
|
||||||
|
* Insert a song that isn't already in the database.
|
||||||
|
*/
|
||||||
|
private function insert_song($client, $file, $filesize) {
|
||||||
|
|
||||||
|
if ($this->check_remote_song($this->get_virtual_path($file))) {
|
||||||
|
debug_event('dropbox_catalog', 'Skipping existing song ' . $file, 5);
|
||||||
|
} else {
|
||||||
|
$origin = $file;
|
||||||
|
$islocal = false;
|
||||||
|
$fpchunk = 0;
|
||||||
|
// Get temporary chunked file from Dropbox to (hope) read metadata
|
||||||
|
if ($this->getchunk) {
|
||||||
|
$fpchunk = tmpfile();
|
||||||
|
$metadata = $client->getFile($file, $fpchunk, null, 40960);
|
||||||
|
if ($metadata == null) {
|
||||||
|
debug_event('dropbox_catalog', 'Cannot get Dropbox file: ' . $file, 5);
|
||||||
|
}
|
||||||
|
$file = stream_get_meta_data($fpchunk)['uri'];
|
||||||
|
$islocal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$vainfo = new vainfo($file, '', '', '', $this->sort_pattern, $this->rename_pattern, $islocal);
|
||||||
|
$vainfo->forceSize($filesize);
|
||||||
|
$vainfo->get_info();
|
||||||
|
|
||||||
|
$key = vainfo::get_tag_type($vainfo->tags);
|
||||||
|
$results = vainfo::clean_tag_info($vainfo->tags, $key, $file);
|
||||||
|
|
||||||
|
// Remove temp file
|
||||||
|
if ($fpchunk) {
|
||||||
|
fclose($fpchunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the remote path
|
||||||
|
$results['file'] = $origin;
|
||||||
|
$results['catalog'] = $this->id;
|
||||||
|
|
||||||
|
if (!empty($results['artist']) && !empty($results['album'])) {
|
||||||
|
$results['file'] = $this->get_virtual_path($results['file']);
|
||||||
|
|
||||||
|
Song::insert($results);
|
||||||
|
$this->count++;
|
||||||
|
} else {
|
||||||
|
debug_event('results', $results['file'] . " ignored because it is an orphan songs. Please check your catalog patterns.", 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function verify_catalog_proc() {
|
||||||
|
return array('total' => 0, 'updated' => 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* clean_catalog_proc
|
||||||
|
*
|
||||||
|
* Removes songs that no longer exist.
|
||||||
|
*/
|
||||||
|
public function clean_catalog_proc() {
|
||||||
|
$dead = 0;
|
||||||
|
|
||||||
|
$client = $this->createClient();
|
||||||
|
if ($client != null) {
|
||||||
|
$sql = 'SELECT `id`, `file` FROM `song` WHERE `catalog` = ?';
|
||||||
|
$db_results = Dba::read($sql, array($this->id));
|
||||||
|
while ($row = Dba::fetch_assoc($db_results)) {
|
||||||
|
debug_event('dropbox-clean', 'Starting work on ' . $row['file'] . '(' . $row['id'] . ')', 5, 'ampache-catalog');
|
||||||
|
$file = $this->get_rel_path($row['file']);
|
||||||
|
$metadata = $client->getMetadata($file);
|
||||||
|
if ($metadata) {
|
||||||
|
debug_event('dropbox-clean', 'keeping song', 5, 'ampache-catalog');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
debug_event('dropbox-clean', 'removing song', 5, 'ampache-catalog');
|
||||||
|
$dead++;
|
||||||
|
Dba::write('DELETE FROM `song` WHERE `id` = ?', array($row['id']));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "<p>" . T_('API Error: cannot connect to Dropbox.') . "</p><hr />\n";
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $dead;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check_remote_song
|
||||||
|
*
|
||||||
|
* checks to see if a remote song exists in the database or not
|
||||||
|
* if it find a song it returns the UID
|
||||||
|
*/
|
||||||
|
public function check_remote_song($file) {
|
||||||
|
|
||||||
|
$sql = 'SELECT `id` FROM `song` WHERE `file` = ?';
|
||||||
|
$db_results = Dba::read($sql, array($file));
|
||||||
|
|
||||||
|
if ($results = Dba::fetch_assoc($db_results)) {
|
||||||
|
return $results['id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_virtual_path($file) {
|
||||||
|
return $this->apikey . '|' . $file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_rel_path($file_path) {
|
||||||
|
$p = strpos($file_path, "|");
|
||||||
|
if ($p !== false) {
|
||||||
|
$p++;
|
||||||
|
}
|
||||||
|
return substr($file_path, $p);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* format
|
||||||
|
*
|
||||||
|
* This makes the object human-readable.
|
||||||
|
*/
|
||||||
|
public function format() {
|
||||||
|
parent::format();
|
||||||
|
$this->f_info = UI::truncate($this->apikey, Config::get('ellipse_threshold_title'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function prepare_media($media) {
|
||||||
|
|
||||||
|
$client = $this->createClient();
|
||||||
|
if ($client != null) {
|
||||||
|
|
||||||
|
set_time_limit(0);
|
||||||
|
|
||||||
|
// Generate browser class for sending headers
|
||||||
|
$browser = new Horde_Browser();
|
||||||
|
$media_name = $media->f_artist_full . " - " . $media->title . "." . $media->type;
|
||||||
|
$browser->downloadHeaders($media_name, $media->mime, false, $media->size);
|
||||||
|
$file = $this->get_rel_path($media->file);
|
||||||
|
|
||||||
|
$output = fopen('php://output', 'w');
|
||||||
|
$metadata = $client->getFile($file, $output);
|
||||||
|
if ($metadata == null) {
|
||||||
|
debug_event('play', 'File not found on Dropbox: ' . $file, 5);
|
||||||
|
}
|
||||||
|
fclose($output);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // end of catalog class
|
||||||
|
|
||||||
|
?>
|
|
@ -62,6 +62,16 @@ class Catalog_googlemusic extends Catalog {
|
||||||
|
|
||||||
} // get_type
|
} // get_type
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get_create_help
|
||||||
|
* This returns hints on catalog creation
|
||||||
|
*/
|
||||||
|
public function get_create_help() {
|
||||||
|
|
||||||
|
return "";
|
||||||
|
|
||||||
|
} // get_create_help
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* is_installed
|
* is_installed
|
||||||
* This returns true or false if remote catalog is installed
|
* This returns true or false if remote catalog is installed
|
||||||
|
@ -94,25 +104,11 @@ class Catalog_googlemusic extends Catalog {
|
||||||
|
|
||||||
} // install
|
} // install
|
||||||
|
|
||||||
/**
|
|
||||||
* uninstall
|
|
||||||
* This removes the remote catalog
|
|
||||||
*/
|
|
||||||
public function uninstall() {
|
|
||||||
|
|
||||||
$sql = "DROP TABLE `catalog_googlemusic`";
|
|
||||||
$db_results = Dba::query($sql);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} // uninstall
|
|
||||||
|
|
||||||
public function catalog_fields() {
|
public function catalog_fields() {
|
||||||
|
|
||||||
$fields['email'] = array('description' => T_('Email'),'type'=>'textbox');
|
$fields['email'] = array('description' => T_('Email'),'type'=>'textbox');
|
||||||
$fields['password'] = array('description' => T_('Password'),'type'=>'password');
|
$fields['password'] = array('description' => T_('Password'),'type'=>'password');
|
||||||
// devicdeid not required for streaming
|
$fields['deviceid'] = array('description' => T_('Device ID'),'type'=>'textbox');
|
||||||
//$fields['deviceid'] = array('description' => T_('Device ID'),'type'=>'textbox');
|
|
||||||
|
|
||||||
return $fields;
|
return $fields;
|
||||||
|
|
||||||
|
@ -173,30 +169,12 @@ class Catalog_googlemusic extends Catalog {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* run_add
|
|
||||||
*
|
|
||||||
* This runs the add to catalog function
|
|
||||||
* it includes the javascript refresh stuff and then starts rolling
|
|
||||||
* throught the path for this catalog
|
|
||||||
*/
|
|
||||||
public function run_add($options) {
|
|
||||||
// Prevent the script from timing out
|
|
||||||
set_time_limit(0);
|
|
||||||
|
|
||||||
UI::show_box_top(T_('Running Google Music Remote Sync') . '. . .');
|
|
||||||
$this->update_remote_catalog();
|
|
||||||
UI::show_box_bottom();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* add_to_catalog
|
* add_to_catalog
|
||||||
* this function adds new files to an
|
* this function adds new files to an
|
||||||
* existing catalog
|
* existing catalog
|
||||||
*/
|
*/
|
||||||
public function add_to_catalog() {
|
public function add_to_catalog($options = null) {
|
||||||
|
|
||||||
UI::show_box_top(T_('Running Google Music Remote Update') . '. . .');
|
UI::show_box_top(T_('Running Google Music Remote Update') . '. . .');
|
||||||
$this->update_remote_catalog();
|
$this->update_remote_catalog();
|
||||||
|
@ -209,7 +187,7 @@ class Catalog_googlemusic extends Catalog {
|
||||||
$api = new GMApi();
|
$api = new GMApi();
|
||||||
$api->setDebug(Config::get('debug'));
|
$api->setDebug(Config::get('debug'));
|
||||||
$api->enableRestore(false);
|
$api->enableRestore(false);
|
||||||
$api->enableMACAddressCheck(false);
|
$api->enableMACAddressCheck(true);
|
||||||
$api->enableSessionFile(false);
|
$api->enableSessionFile(false);
|
||||||
|
|
||||||
if(!$api->login($this->email, $this->password, $this->deviceid)) {
|
if(!$api->login($this->email, $this->password, $this->deviceid)) {
|
||||||
|
@ -290,7 +268,7 @@ class Catalog_googlemusic extends Catalog {
|
||||||
/**
|
/**
|
||||||
* clean_catalog_proc
|
* clean_catalog_proc
|
||||||
*
|
*
|
||||||
* Removes subsonic songs that no longer exist.
|
* Removes songs that no longer exist.
|
||||||
*/
|
*/
|
||||||
public function clean_catalog_proc() {
|
public function clean_catalog_proc() {
|
||||||
$api = $this->createClient();
|
$api = $this->createClient();
|
||||||
|
@ -343,7 +321,7 @@ class Catalog_googlemusic extends Catalog {
|
||||||
|
|
||||||
public function get_rel_path($file_path) {
|
public function get_rel_path($file_path) {
|
||||||
$info = $this->_get_info();
|
$info = $this->_get_info();
|
||||||
$catalog_path = rtrim($info->uri, "/");
|
$catalog_path = rtrim($info->email, "/");
|
||||||
return( str_replace( $catalog_path . "/", "", $file_path ) );
|
return( str_replace( $catalog_path . "/", "", $file_path ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -371,10 +349,11 @@ class Catalog_googlemusic extends Catalog {
|
||||||
$api = $this->createClient();
|
$api = $this->createClient();
|
||||||
if ($api != null) {
|
if ($api != null) {
|
||||||
$songid = $this->url_to_songid($media->file);
|
$songid = $this->url_to_songid($media->file);
|
||||||
|
|
||||||
$song = $api->get_stream_url($songid);
|
$song = $api->get_stream_url($songid);
|
||||||
if ($song) {
|
if ($song) {
|
||||||
header('Location: ' . $url['url']);
|
header('Location: ' . $song);
|
||||||
debug_event('play', 'Started remote stream - ' . $url['url'], 5);
|
debug_event('play', 'Started remote stream - ' . $song, 5);
|
||||||
} else {
|
} else {
|
||||||
debug_event('play', 'Cannot get remote stream for song ' . $media->file, 5);
|
debug_event('play', 'Cannot get remote stream for song ' . $media->file, 5);
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,16 @@ class Catalog_local extends Catalog {
|
||||||
|
|
||||||
} // get_type
|
} // get_type
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get_create_help
|
||||||
|
* This returns hints on catalog creation
|
||||||
|
*/
|
||||||
|
public function get_create_help() {
|
||||||
|
|
||||||
|
return "";
|
||||||
|
|
||||||
|
} // get_create_help
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* is_installed
|
* is_installed
|
||||||
* This returns true or false if local catalog is installed
|
* This returns true or false if local catalog is installed
|
||||||
|
@ -92,19 +102,6 @@ class Catalog_local extends Catalog {
|
||||||
|
|
||||||
} // install
|
} // install
|
||||||
|
|
||||||
/**
|
|
||||||
* uninstall
|
|
||||||
* This removes the local catalog
|
|
||||||
*/
|
|
||||||
public function uninstall() {
|
|
||||||
|
|
||||||
$sql = "DROP TABLE `catalog_local`";
|
|
||||||
$db_results = Dba::query($sql);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} // uninstall
|
|
||||||
|
|
||||||
public function catalog_fields() {
|
public function catalog_fields() {
|
||||||
|
|
||||||
$fields['path'] = array('description' => T_('Path'),'type'=>'textbox');
|
$fields['path'] = array('description' => T_('Path'),'type'=>'textbox');
|
||||||
|
@ -209,42 +206,6 @@ class Catalog_local extends Catalog {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* run_add
|
|
||||||
*
|
|
||||||
* This runs the add to catalog function
|
|
||||||
* it includes the javascript refresh stuff and then starts rolling
|
|
||||||
* throught the path for this catalog
|
|
||||||
*/
|
|
||||||
public function run_add($options) {
|
|
||||||
// Prevent the script from timing out
|
|
||||||
set_time_limit(0);
|
|
||||||
|
|
||||||
$start_time = time();
|
|
||||||
|
|
||||||
require Config::get('prefix') . '/templates/show_adds_catalog.inc.php';
|
|
||||||
flush();
|
|
||||||
|
|
||||||
$this->add_files($this->path, $options);
|
|
||||||
|
|
||||||
// If they have checked the box then go ahead and gather the art
|
|
||||||
if ($options['gather_art']) {
|
|
||||||
$catalog_id = $this->id;
|
|
||||||
require Config::get('prefix') . '/templates/show_gather_art.inc.php';
|
|
||||||
flush();
|
|
||||||
$this->gather_art();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle m3u-ness
|
|
||||||
if ($options['parse_m3u'] AND count($this->_playlists)) {
|
|
||||||
foreach ($this->_playlists as $playlist_file) {
|
|
||||||
$result = $this->import_m3u($playlist_file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* add_files
|
* add_files
|
||||||
*
|
*
|
||||||
|
@ -336,24 +297,9 @@ class Catalog_local extends Catalog {
|
||||||
continue;
|
continue;
|
||||||
} //it's a directory
|
} //it's a directory
|
||||||
|
|
||||||
/* If it's not a dir let's roll with it
|
$is_audio_file = Catalog::is_audio_file($file);
|
||||||
* next we need to build the pattern that we will use
|
|
||||||
* to detect if it's an audio file
|
|
||||||
*/
|
|
||||||
$pattern = "/\.(" . Config::get('catalog_file_pattern');
|
|
||||||
if ($options['parse_m3u']) {
|
|
||||||
$pattern .= "|m3u)$/i";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$pattern .= ")$/i";
|
|
||||||
}
|
|
||||||
|
|
||||||
$is_audio_file = preg_match($pattern,$file);
|
|
||||||
|
|
||||||
// Define the Video file pattern
|
|
||||||
if (!$is_audio_file AND Config::get('catalog_video_pattern')) {
|
if (!$is_audio_file AND Config::get('catalog_video_pattern')) {
|
||||||
$video_pattern = "/\.(" . Config::get('catalog_video_pattern') . ")$/i";
|
$is_video_file = Catalog::is_video_file($file);
|
||||||
$is_video_file = preg_match($video_pattern,$file);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* see if this is a valid audio file or playlist file */
|
/* see if this is a valid audio file or playlist file */
|
||||||
|
@ -431,7 +377,14 @@ class Catalog_local extends Catalog {
|
||||||
* this function adds new files to an
|
* this function adds new files to an
|
||||||
* existing catalog
|
* existing catalog
|
||||||
*/
|
*/
|
||||||
public function add_to_catalog() {
|
public function add_to_catalog($options = null) {
|
||||||
|
|
||||||
|
if ($options == null) {
|
||||||
|
$options = array(
|
||||||
|
'gather_art' => true,
|
||||||
|
'parse_m3u' => true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
require Config::get('prefix') . '/templates/show_adds_catalog.inc.php';
|
require Config::get('prefix') . '/templates/show_adds_catalog.inc.php';
|
||||||
flush();
|
flush();
|
||||||
|
@ -447,27 +400,27 @@ class Catalog_local extends Catalog {
|
||||||
set_time_limit(0);
|
set_time_limit(0);
|
||||||
|
|
||||||
/* Get the songs and then insert them into the db */
|
/* Get the songs and then insert them into the db */
|
||||||
$this->add_files($this->path,$type,0,$verbose);
|
$this->add_files($this->path, $options);
|
||||||
|
|
||||||
|
if ($options['parse_m3u'] && count($this->_playlists)) {
|
||||||
// Foreach Playlists we found
|
// Foreach Playlists we found
|
||||||
foreach ($this->_playlists as $full_file) {
|
foreach ($this->_playlists as $full_file) {
|
||||||
$result = $this->import_m3u($full_file);
|
$result = $this->import_m3u($full_file);
|
||||||
if ($result['success']) {
|
if ($result['success']) {
|
||||||
$file = basename($full_file);
|
$file = basename($full_file);
|
||||||
if ($verbose) {
|
|
||||||
echo " " . T_('Added Playlist From') . " $file . . . .<br />\n";
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
} // end if import worked
|
} // end if import worked
|
||||||
} // end foreach playlist files
|
} // end foreach playlist files
|
||||||
|
}
|
||||||
|
|
||||||
/* Do a little stats mojo here */
|
/* Do a little stats mojo here */
|
||||||
$current_time = time();
|
$current_time = time();
|
||||||
|
|
||||||
|
if ($options['gather_art']) {
|
||||||
$catalog_id = $this->id;
|
$catalog_id = $this->id;
|
||||||
require Config::get('prefix') . '/templates/show_gather_art.inc.php';
|
require Config::get('prefix') . '/templates/show_gather_art.inc.php';
|
||||||
flush();
|
flush();
|
||||||
$this->gather_art();
|
$this->gather_art();
|
||||||
|
}
|
||||||
|
|
||||||
/* Update the Catalog last_update */
|
/* Update the Catalog last_update */
|
||||||
$this->update_last_add();
|
$this->update_last_add();
|
||||||
|
|
|
@ -62,6 +62,16 @@ class Catalog_remote extends Catalog {
|
||||||
|
|
||||||
} // get_type
|
} // get_type
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get_create_help
|
||||||
|
* This returns hints on catalog creation
|
||||||
|
*/
|
||||||
|
public function get_create_help() {
|
||||||
|
|
||||||
|
return "";
|
||||||
|
|
||||||
|
} // get_create_help
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* is_installed
|
* is_installed
|
||||||
* This returns true or false if remote catalog is installed
|
* This returns true or false if remote catalog is installed
|
||||||
|
@ -94,19 +104,6 @@ class Catalog_remote extends Catalog {
|
||||||
|
|
||||||
} // install
|
} // install
|
||||||
|
|
||||||
/**
|
|
||||||
* uninstall
|
|
||||||
* This removes the remote catalog
|
|
||||||
*/
|
|
||||||
public function uninstall() {
|
|
||||||
|
|
||||||
$sql = "DROP TABLE `catalog_remote`";
|
|
||||||
$db_results = Dba::query($sql);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} // uninstall
|
|
||||||
|
|
||||||
public function catalog_fields() {
|
public function catalog_fields() {
|
||||||
|
|
||||||
$fields['uri'] = array('description' => T_('Uri'),'type'=>'textbox');
|
$fields['uri'] = array('description' => T_('Uri'),'type'=>'textbox');
|
||||||
|
@ -176,30 +173,12 @@ class Catalog_remote extends Catalog {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* run_add
|
|
||||||
*
|
|
||||||
* This runs the add to catalog function
|
|
||||||
* it includes the javascript refresh stuff and then starts rolling
|
|
||||||
* throught the path for this catalog
|
|
||||||
*/
|
|
||||||
public function run_add($options) {
|
|
||||||
// Prevent the script from timing out
|
|
||||||
set_time_limit(0);
|
|
||||||
|
|
||||||
UI::show_box_top(T_('Running Remote Sync') . '. . .');
|
|
||||||
$this->update_remote_catalog();
|
|
||||||
UI::show_box_bottom();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* add_to_catalog
|
* add_to_catalog
|
||||||
* this function adds new files to an
|
* this function adds new files to an
|
||||||
* existing catalog
|
* existing catalog
|
||||||
*/
|
*/
|
||||||
public function add_to_catalog() {
|
public function add_to_catalog($options = null) {
|
||||||
|
|
||||||
UI::show_box_top(T_('Running Remote Update') . '. . .');
|
UI::show_box_top(T_('Running Remote Update') . '. . .');
|
||||||
$this->update_remote_catalog();
|
$this->update_remote_catalog();
|
||||||
|
|
|
@ -62,6 +62,16 @@ class Catalog_subsonic extends Catalog {
|
||||||
|
|
||||||
} // get_type
|
} // get_type
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get_create_help
|
||||||
|
* This returns hints on catalog creation
|
||||||
|
*/
|
||||||
|
public function get_create_help() {
|
||||||
|
|
||||||
|
return "";
|
||||||
|
|
||||||
|
} // get_create_help
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* is_installed
|
* is_installed
|
||||||
* This returns true or false if remote catalog is installed
|
* This returns true or false if remote catalog is installed
|
||||||
|
@ -94,19 +104,6 @@ class Catalog_subsonic extends Catalog {
|
||||||
|
|
||||||
} // install
|
} // install
|
||||||
|
|
||||||
/**
|
|
||||||
* uninstall
|
|
||||||
* This removes the remote catalog
|
|
||||||
*/
|
|
||||||
public function uninstall() {
|
|
||||||
|
|
||||||
$sql = "DROP TABLE `catalog_subsonic`";
|
|
||||||
$db_results = Dba::query($sql);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} // uninstall
|
|
||||||
|
|
||||||
public function catalog_fields() {
|
public function catalog_fields() {
|
||||||
|
|
||||||
$fields['uri'] = array('description' => T_('Uri'),'type'=>'textbox');
|
$fields['uri'] = array('description' => T_('Uri'),'type'=>'textbox');
|
||||||
|
@ -177,30 +174,14 @@ class Catalog_subsonic extends Catalog {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* run_add
|
|
||||||
*
|
|
||||||
* This runs the add to catalog function
|
|
||||||
* it includes the javascript refresh stuff and then starts rolling
|
|
||||||
* throught the path for this catalog
|
|
||||||
*/
|
|
||||||
public function run_add($options) {
|
|
||||||
// Prevent the script from timing out
|
|
||||||
set_time_limit(0);
|
|
||||||
|
|
||||||
UI::show_box_top(T_('Running Subsonic Remote Sync') . '. . .');
|
|
||||||
$this->update_remote_catalog();
|
|
||||||
UI::show_box_bottom();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* add_to_catalog
|
* add_to_catalog
|
||||||
* this function adds new files to an
|
* this function adds new files to an
|
||||||
* existing catalog
|
* existing catalog
|
||||||
*/
|
*/
|
||||||
public function add_to_catalog() {
|
public function add_to_catalog($options = null) {
|
||||||
|
// Prevent the script from timing out
|
||||||
|
set_time_limit(0);
|
||||||
|
|
||||||
UI::show_box_top(T_('Running Subsonic Remote Update') . '. . .');
|
UI::show_box_top(T_('Running Subsonic Remote Update') . '. . .');
|
||||||
$this->update_remote_catalog();
|
$this->update_remote_catalog();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue