Initial commit: Atomaste website
This commit is contained in:
@@ -0,0 +1,412 @@
|
||||
<?php
|
||||
// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped
|
||||
/**
|
||||
* Dropbox API base class
|
||||
* @author Ben Tadiar <ben@handcraftedbyben.co.uk>
|
||||
* @link https://github.com/benthedesigner/dropbox
|
||||
* @link https://www.dropbox.com/developers
|
||||
* @link https://status.dropbox.com Dropbox status
|
||||
* @package Dropbox
|
||||
*/
|
||||
class UpdraftPlus_Dropbox_API {
|
||||
// API Endpoints
|
||||
const API_URL_V2 = 'https://api.dropboxapi.com/';
|
||||
const CONTENT_URL_V2 = 'https://content.dropboxapi.com/2/';
|
||||
|
||||
/**
|
||||
* OAuth consumer object
|
||||
* @var null|OAuth\Consumer
|
||||
*/
|
||||
private $OAuth;
|
||||
|
||||
/**
|
||||
* The root level for file paths
|
||||
* Either `dropbox` or `sandbox` (preferred)
|
||||
* @var null|string
|
||||
*/
|
||||
private $root;
|
||||
|
||||
/**
|
||||
* JSONP callback
|
||||
* @var string
|
||||
*/
|
||||
private $callback = 'dropboxCallback';
|
||||
|
||||
/**
|
||||
* Chunk size used for chunked uploads
|
||||
* @see \Dropbox\API::chunkedUpload()
|
||||
*/
|
||||
private $chunkSize = 4194304;
|
||||
|
||||
/**
|
||||
* Set the OAuth consumer object
|
||||
* See 'General Notes' at the link below for information on access type
|
||||
* @link https://www.dropbox.com/developers/reference/api
|
||||
* @param OAuth\Consumer\ConsumerAbstract $OAuth
|
||||
* @param string $root Dropbox app access type
|
||||
*/
|
||||
public function __construct(Dropbox_ConsumerAbstract $OAuth, $root = 'sandbox') {
|
||||
$this->OAuth = $OAuth;
|
||||
$this->setRoot($root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the root level
|
||||
* @param mixed $root
|
||||
* @throws Exception
|
||||
* @return void
|
||||
*/
|
||||
public function setRoot($root) {
|
||||
if ($root !== 'sandbox' && $root !== 'dropbox') {
|
||||
throw new Exception("Expected a root of either 'dropbox' or 'sandbox', got '$root'");
|
||||
} else {
|
||||
$this->root = $root;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will make a request to refresh the access token
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function refreshAccessToken() {
|
||||
$this->OAuth->refreshAccessToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves information about the user's account
|
||||
* @return object stdClass
|
||||
*/
|
||||
public function accountInfo() {
|
||||
$call = '2/users/get_current_account';
|
||||
$params = array('api_v2' => true);
|
||||
$response = $this->fetch('POST', self::API_URL_V2, $call, $params);
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves information about the user's quota
|
||||
* @param array $options - valid keys are 'timeout'
|
||||
* @return object stdClass
|
||||
*/
|
||||
public function quotaInfo($options = array()) {
|
||||
$call = '2/users/get_space_usage';
|
||||
// Cases have been seen (Apr 2019) where a response came back (HTTP/2.0 response header - suspected outgoing web hosting proxy, as everyone else seems to get HTTP/1.0 and I'm not aware that current Curl versions would do HTTP/2.0 without specifically being told to) after 180 seconds; a valid response, but took a long time.
|
||||
$params = array(
|
||||
'api_v2' => true,
|
||||
'timeout' => isset($options['timeout']) ? $options['timeout'] : 20
|
||||
);
|
||||
$response = $this->fetch('POST', self::API_URL_V2, $call, $params);
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads large files to Dropbox in mulitple chunks
|
||||
* @param string $file Absolute path to the file to be uploaded
|
||||
* @param string|bool $filename The destination filename of the uploaded file
|
||||
* @param string $path Path to upload the file to, relative to root
|
||||
* @param boolean $overwrite Should the file be overwritten? (Default: true)
|
||||
* @param integer $offset position to seek to when opening the file
|
||||
* @param string $uploadID existing upload_id to resume an upload
|
||||
* @param string|array function to call back to upon each chunk
|
||||
* @return stdClass
|
||||
*/
|
||||
public function chunkedUpload($file, $filename = false, $path = '', $overwrite = true, $offset = 0, $uploadID = null, $callback = null) {
|
||||
|
||||
if (file_exists($file)) {
|
||||
if ($handle = @fopen($file, 'r')) {
|
||||
// Set initial upload ID and offset
|
||||
if ($offset > 0) {
|
||||
fseek($handle, $offset);
|
||||
}
|
||||
|
||||
/*
|
||||
Set firstCommit to true so that the upload session start endpoint is called.
|
||||
*/
|
||||
$firstCommit = (0 == $offset);
|
||||
|
||||
// Read from the file handle until EOF, uploading each chunk
|
||||
while ($data = fread($handle, $this->chunkSize)) {
|
||||
|
||||
// Set the file, request parameters and send the request
|
||||
$this->OAuth->setInFile($data);
|
||||
|
||||
if ($firstCommit) {
|
||||
$params = array(
|
||||
'close' => false,
|
||||
'api_v2' => true,
|
||||
'content_upload' => true
|
||||
);
|
||||
$response = $this->fetch('POST', self::CONTENT_URL_V2, 'files/upload_session/start', $params);
|
||||
$firstCommit = false;
|
||||
} else {
|
||||
$params = array(
|
||||
'cursor' => array(
|
||||
'session_id' => $uploadID,
|
||||
// If you send it as a string, Dropbox will be unhappy
|
||||
'offset' => (int)$offset
|
||||
),
|
||||
'api_v2' => true,
|
||||
'content_upload' => true
|
||||
);
|
||||
$response = $this->append_upload($params, false);
|
||||
}
|
||||
|
||||
// On subsequent chunks, use the upload ID returned by the previous request
|
||||
if (isset($response['body']->session_id)) {
|
||||
$uploadID = $response['body']->session_id;
|
||||
}
|
||||
|
||||
/*
|
||||
API v2 no longer returns the offset, we need to manually work this out. So check that there are no errors and update the offset as well as calling the callback method.
|
||||
*/
|
||||
if (!isset($response['body']->error)) {
|
||||
$offset = ftell($handle);
|
||||
if ($callback) {
|
||||
call_user_func($callback, $offset, $uploadID, $file);
|
||||
}
|
||||
$this->OAuth->setInFile(null);
|
||||
}
|
||||
}
|
||||
|
||||
// Complete the chunked upload
|
||||
$filename = (is_string($filename)) ? $filename : basename($file);
|
||||
$params = array(
|
||||
'cursor' => array(
|
||||
'session_id' => $uploadID,
|
||||
'offset' => $offset
|
||||
),
|
||||
'commit' => array(
|
||||
'path' => '/' . $this->encodePath($path . $filename),
|
||||
'mode' => 'add'
|
||||
),
|
||||
'api_v2' => true,
|
||||
'content_upload' => true
|
||||
);
|
||||
$response = $this->append_upload($params, true);
|
||||
return $response;
|
||||
} else {
|
||||
throw new Exception('Could not open ' . $file . ' for reading');
|
||||
}
|
||||
}
|
||||
|
||||
// Throw an Exception if the file does not exist
|
||||
throw new Exception('Local file ' . $file . ' does not exist');
|
||||
}
|
||||
|
||||
private function append_upload($params, $last_call) {
|
||||
try {
|
||||
if ($last_call){
|
||||
$response = $this->fetch('POST', self::CONTENT_URL_V2, 'files/upload_session/finish', $params);
|
||||
} else {
|
||||
$response = $this->fetch('POST', self::CONTENT_URL_V2, 'files/upload_session/append_v2', $params);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$responseCheck = json_decode($e->getMessage());
|
||||
if (empty($responseCheck)) {
|
||||
throw $e;
|
||||
} else {
|
||||
|
||||
$extract_message = (is_object($responseCheck[0]) && isset($responseCheck[0]->{'.tag'})) ? $responseCheck[0]->{'.tag'} : $responseCheck[0];
|
||||
|
||||
if (strpos($extract_message, 'incorrect_offset') !== false) {
|
||||
$expected_offset = $responseCheck[1];
|
||||
throw new Exception('Submitted input out of alignment: got ['.$params['cursor']['offset'].'] expected ['.$expected_offset.']');
|
||||
|
||||
// $params['cursor']['offset'] = $responseCheck[1];
|
||||
// $response = $this->append_upload($params, $last_call);
|
||||
} elseif (strpos($extract_message, 'closed') !== false) {
|
||||
throw new Exception("Upload with upload_id {$params['cursor']['session_id']} already completed");
|
||||
} elseif (strpos($extract_message, 'too_many_requests') !== false) {
|
||||
throw new Exception("Dropbox API error: too_many_requests");
|
||||
}
|
||||
}
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Chunked downloads a file from Dropbox, it will return false if a file handle is not passed and will return true if the call was successful.
|
||||
*
|
||||
* @param string $file Path - to file, relative to root, including path
|
||||
* @param resource $outFile - the local file handle
|
||||
* @param array $options - any extra options to be passed e.g headers
|
||||
* @return boolean - a boolean to indicate success or failure
|
||||
*/
|
||||
public function download($file, $outFile = null, $options = array()) {
|
||||
|
||||
if ($outFile) {
|
||||
$this->OAuth->setOutFile($outFile);
|
||||
|
||||
$params = array('path' => '/' . $file, 'api_v2' => true, 'content_download' => true);
|
||||
|
||||
if (isset($options['headers'])) {
|
||||
foreach ($options['headers'] as $key => $header) {
|
||||
$headers[] = $key . ': ' . $header;
|
||||
}
|
||||
$params['headers'] = $headers;
|
||||
}
|
||||
|
||||
$file = $this->encodePath($file);
|
||||
$call = 'files/download';
|
||||
|
||||
$response = $this->fetch('GET', self::CONTENT_URL_V2, $call, $params);
|
||||
|
||||
fclose($outFile);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the relevant method to return metadata for all files and folders that match the search query
|
||||
* @param mixed $query The search string. Must be at least 3 characters long
|
||||
* @param string [$path=''] The path to the folder you want to search in
|
||||
* @param integer [$limit=1000] Maximum number of results to return (1-1000)
|
||||
* @param integer [$cursor=''] A Dropbox ID to start the search from
|
||||
* @return array
|
||||
*/
|
||||
public function search($query, $path = '', $limit = 1000, $cursor = '') {
|
||||
if (empty($cursor)) {
|
||||
return $this->start_search($query, $path, $limit);
|
||||
} else {
|
||||
return $this->continue_search($cursor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will start a search for all files and folders that match the search query
|
||||
*
|
||||
* @param mixed $query - the search string, must be at least 3 characters long
|
||||
* @param string $path - the path to the folder you want to search in
|
||||
* @param integer $limit - maximum number of results to return (1-1000)
|
||||
*
|
||||
* @return array - an array of search results
|
||||
*/
|
||||
private function start_search($query, $path, $limit) {
|
||||
$call = '2/files/search_v2';
|
||||
$path = $this->encodePath($path);
|
||||
// APIv2 requires that the path match this regex: String(pattern="(/(.|[\r\n])*)?|(ns:[0-9]+(/.*)?)")
|
||||
if ($path && '/' != substr($path, 0, 1)) $path = "/$path";
|
||||
$params = array(
|
||||
'query' => $query,
|
||||
'options' => array(
|
||||
'path' => $path,
|
||||
'filename_only' => true,
|
||||
'max_results' => ($limit < 1) ? 1 : (($limit > 1000) ? 1000 : (int) $limit),
|
||||
),
|
||||
'api_v2' => true,
|
||||
);
|
||||
$response = $this->fetch('POST', self::API_URL_V2, $call, $params);
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will continue a previous search for all files and folders that match the previous search query
|
||||
*
|
||||
* @param string $cursor - a Dropbox ID to continue the search
|
||||
*
|
||||
* @return array - an array of search results
|
||||
*/
|
||||
private function continue_search($cursor) {
|
||||
$call = '2/files/search/continue_v2';
|
||||
$params = array(
|
||||
'cursor' => $cursor,
|
||||
'api_v2' => true,
|
||||
);
|
||||
$response = $this->fetch('POST', self::API_URL_V2, $call, $params);
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a file or folder
|
||||
* @param string $path The path to the file or folder to be deleted
|
||||
* @return object stdClass
|
||||
*/
|
||||
public function delete($path) {
|
||||
$call = '2/files/delete_v2';
|
||||
$params = array('path' => '/' . $this->normalisePath($path), 'api_v2' => true);
|
||||
$response = $this->fetch('POST', self::API_URL_V2, $call, $params);
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Intermediate fetch function
|
||||
* @param string $method The HTTP method
|
||||
* @param string $url The API endpoint
|
||||
* @param string $call The API method to call
|
||||
* @param array $params Additional parameters
|
||||
* @return mixed
|
||||
*/
|
||||
private function fetch($method, $url, $call, array $params = array()) {
|
||||
// Make the API call via the consumer
|
||||
return $this->OAuth->fetch($method, $url, $call, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the chunk size for chunked uploads
|
||||
* If $chunkSize is empty, set to 4194304 bytes (4 MB)
|
||||
* @see \Dropbox\API\chunkedUpload()
|
||||
*/
|
||||
public function setChunkSize($chunkSize = 4194304) {
|
||||
if (!is_int($chunkSize)) {
|
||||
throw new Exception('Expecting chunk size to be an integer, got ' . gettype($chunkSize));
|
||||
} elseif ($chunkSize > 157286400) {
|
||||
throw new Exception('Chunk size must not exceed 157286400 bytes, got ' . $chunkSize);
|
||||
} else {
|
||||
$this->chunkSize = $chunkSize;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the JSONP callback function
|
||||
* @param string $function
|
||||
* @return void
|
||||
*/
|
||||
public function setCallback($function) {
|
||||
$this->callback = $function;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mime type of downloaded file
|
||||
* If the Fileinfo extension is not loaded, return false
|
||||
* @param string $data File contents as a string or filename
|
||||
* @param string $isFilename Is $data a filename?
|
||||
* @return boolean|string Mime type and encoding of the file
|
||||
*/
|
||||
private function getMimeType($data, $isFilename = false) {
|
||||
if (extension_loaded('fileinfo')) {
|
||||
$finfo = new finfo(FILEINFO_MIME);
|
||||
if ($isFilename !== false) {
|
||||
return $finfo->file($data);
|
||||
}
|
||||
return $finfo->buffer($data);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim the path of forward slashes and replace
|
||||
* consecutive forward slashes with a single slash
|
||||
* @param string $path The path to normalise
|
||||
* @return string
|
||||
*/
|
||||
private function normalisePath($path) {
|
||||
$path = preg_replace('#/+#', '/', trim($path, '/'));
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the path, then replace encoded slashes
|
||||
* with literal forward slash characters
|
||||
* @param string $path The path to encode
|
||||
* @return string
|
||||
*/
|
||||
private function encodePath($path) {
|
||||
// in APIv1, encoding was needed because parameters were passed as part of the URL; this is no longer done in our APIv2 SDK; hence, all that we now do here is normalise.
|
||||
return $this->normalisePath($path);
|
||||
}
|
||||
}
|
||||
// phpcs:enable
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Dropbox Exception class
|
||||
* @author Ben Tadiar <ben@handcraftedbyben.co.uk>
|
||||
* @link https://github.com/benthedesigner/dropbox
|
||||
* @package Dropbox
|
||||
*/
|
||||
class Dropbox_Exception extends Exception {
|
||||
}
|
||||
|
||||
class Dropbox_BadRequestException extends Exception {
|
||||
}
|
||||
|
||||
class Dropbox_CurlException extends Exception {
|
||||
}
|
||||
|
||||
class Dropbox_NotAcceptableException extends Exception {
|
||||
}
|
||||
|
||||
class Dropbox_NotFoundException extends Exception {
|
||||
}
|
||||
|
||||
class Dropbox_NotModifiedException extends Exception {
|
||||
}
|
||||
|
||||
class Dropbox_UnsupportedMediaTypeException extends Exception {
|
||||
}
|
||||
@@ -0,0 +1,567 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Abstract OAuth consumer
|
||||
* @author Ben Tadiar <ben@handcraftedbyben.co.uk>
|
||||
* @link https://github.com/benthedesigner/dropbox
|
||||
* @package Dropbox\OAuth
|
||||
* @subpackage Consumer
|
||||
*/
|
||||
|
||||
abstract class Dropbox_ConsumerAbstract
|
||||
{
|
||||
// Dropbox web endpoint. v2 API has just dropped the 1/ suffix to the below.
|
||||
const WEB_URL = 'https://www.dropbox.com/';
|
||||
|
||||
// OAuth flow methods
|
||||
const AUTHORISE_METHOD = 'oauth2/authorize';
|
||||
// Beware - the documentation in one place says oauth2/token/revoke, but that appears to be wrong
|
||||
const DEAUTHORISE_METHOD = '2/auth/token/revoke';
|
||||
const ACCESS_TOKEN_METHOD = 'oauth2/token';
|
||||
// The next endpoint only exists with APIv1
|
||||
const OAUTH_UPGRADE = 'oauth2/token_from_oauth1';
|
||||
|
||||
private $scopes = array(
|
||||
'account_info.read',
|
||||
'files.content.write',
|
||||
'files.content.read',
|
||||
'files.metadata.read',
|
||||
);
|
||||
|
||||
/**
|
||||
* Signature method, either PLAINTEXT or HMAC-SHA1
|
||||
* @var string
|
||||
*/
|
||||
private $sigMethod = 'PLAINTEXT';
|
||||
|
||||
/**
|
||||
* Output file handle
|
||||
* @var null|resource
|
||||
*/
|
||||
protected $outFile = null;
|
||||
|
||||
/**
|
||||
* Input file handle
|
||||
* @var null|resource
|
||||
*/
|
||||
protected $inFile = null;
|
||||
|
||||
/**
|
||||
* Authenticate using 3-legged OAuth flow, firstly
|
||||
* checking we don't already have tokens to use
|
||||
* @return void
|
||||
*/
|
||||
protected function authenticate()
|
||||
{
|
||||
global $updraftplus;
|
||||
|
||||
$access_token = $this->storage->get('access_token');
|
||||
//Check if the new token type is set if not they need to be upgraded to OAuth2
|
||||
if (!empty($access_token) && isset($access_token->oauth_token) && !isset($access_token->token_type)) {
|
||||
$updraftplus->log('OAuth v1 token found: upgrading to v2');
|
||||
$this->upgradeOAuth();
|
||||
$updraftplus->log('OAuth token upgrade successful');
|
||||
}
|
||||
|
||||
if (!empty($access_token) && isset($access_token->refresh_token) && isset($access_token->expires_in)) {
|
||||
if ($access_token->expires_in < time()) $this->refreshAccessToken();
|
||||
}
|
||||
|
||||
if (empty($access_token) || !isset($access_token->oauth_token)) {
|
||||
try {
|
||||
$this->getAccessToken();
|
||||
} catch(Exception $e) {
|
||||
$excep_class = get_class($e);
|
||||
// 04-Sep-2015 - Dropbox started throwing a 400, which caused a Dropbox_BadRequestException which previously wasn't being caught
|
||||
if ('Dropbox_BadRequestException' == $excep_class || 'Dropbox_Exception' == $excep_class) {
|
||||
global $updraftplus;
|
||||
$updraftplus->log($e->getMessage().' - need to reauthenticate this site with Dropbox (if this fails, then you can also try wiping your settings from the Expert Settings section)');
|
||||
//$this->getRequestToken();
|
||||
$this->authorise();
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade the user's OAuth1 token to a OAuth2 token
|
||||
* @return void
|
||||
*/
|
||||
private function upgradeOAuth()
|
||||
{
|
||||
// N.B. This call only exists under API v1 - i.e. there is no APIv2 equivalent. Hence the APIv1 endpoint (API_URL) is used, and not the v2 (API_URL_V2)
|
||||
$url = 'https://api.dropbox.com/1/' . self::OAUTH_UPGRADE;
|
||||
$response = $this->fetch('POST', $url, '');
|
||||
$token = new stdClass();
|
||||
/*
|
||||
oauth token secret and oauth token were needed by oauth1
|
||||
these are replaced in oauth2 with an access token
|
||||
currently they are still there just in case a method somewhere is expecting them to both be set
|
||||
as far as I can tell only the oauth token is used
|
||||
after more testing token secret can be removed.
|
||||
*/
|
||||
|
||||
$token->oauth_token_secret = $response['body']->access_token;
|
||||
$token->oauth_token = $response['body']->access_token;
|
||||
$token->token_type = $response['body']->token_type;
|
||||
$this->storage->set($token, 'access_token');
|
||||
$this->storage->set('true','upgraded');
|
||||
$this->storage->do_unset('request_token');
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain user authorisation
|
||||
* The user will be redirected to Dropbox' web endpoint
|
||||
* @link http://tools.ietf.org/html/rfc5849#section-2.2
|
||||
* @return void
|
||||
*/
|
||||
private function authorise()
|
||||
{
|
||||
// Only redirect if not using CLI
|
||||
if (PHP_SAPI !== 'cli' && (!defined('DOING_CRON') || !DOING_CRON) && (!defined('DOING_AJAX') || !DOING_AJAX)) {
|
||||
$url = $this->getAuthoriseUrl();
|
||||
if (!headers_sent()) {
|
||||
header('Location: ' . $url);
|
||||
exit;
|
||||
} else {
|
||||
throw new Dropbox_Exception(sprintf(__('The %s authentication could not go ahead, because something else on your site is breaking it.', 'updraftplus'), 'Dropbox').' '.__('Try disabling your other plugins and switching to a default theme.', 'updraftplus').' ('.__('Specifically, you are looking for the component that sends output (most likely PHP warnings/errors) before the page begins.', 'updraftplus').' '.__('Turning off any debugging settings may also help.', 'updraftplus').')'); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- The escaping should be happening when the exception is printed
|
||||
}
|
||||
?><?php
|
||||
return false;
|
||||
}
|
||||
global $updraftplus;
|
||||
$updraftplus->log('Dropbox reauthorisation needed; but we are running from cron, AJAX or the CLI, so this is not possible');
|
||||
$this->storage->do_unset('access_token');
|
||||
throw new Dropbox_Exception(sprintf(__('You need to re-authenticate with %s, as your existing credentials are not working.', 'updraftplus'), 'Dropbox')); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- The escaping should be happening when the exception is printed
|
||||
#$updraftplus->log(sprintf(__('You need to re-authenticate with %s, as your existing credentials are not working.', 'updraftplus'), 'Dropbox'), 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the user authorisation URL
|
||||
* @return string
|
||||
*/
|
||||
public function getAuthoriseUrl()
|
||||
{
|
||||
/*
|
||||
Generate a random key to be passed to Dropbox and stored in session to be checked to prevent CSRF
|
||||
Uses OpenSSL or Mcrypt or defaults to pure PHP implementaion if neither are available.
|
||||
*/
|
||||
|
||||
global $updraftplus;
|
||||
$updraftplus->ensure_phpseclib();
|
||||
|
||||
$CSRF = base64_encode(phpseclib_Crypt_Random::string(16));
|
||||
$this->storage->set($CSRF,'CSRF');
|
||||
// Prepare request parameters
|
||||
/*
|
||||
For OAuth v2 Dropbox needs to use a authorisation url that matches one that is set inside the
|
||||
Dropbox developer console. In order to check this it needs the client ID for the OAuth v2 app
|
||||
This will use the default one unless the user is using their own Dropbox App
|
||||
|
||||
For users that use their own Dropbox App there is also no need to provide the callbackhome as
|
||||
part of the CSRF as there is no need to go to auth.updraftplus.com also the redirect uri can
|
||||
then be set to the home as default
|
||||
|
||||
Check if the key has dropbox: if so then remove it to stop the request from being invalid
|
||||
*/
|
||||
$appkey = $this->storage->get('appkey');
|
||||
|
||||
if (!empty($appkey) && 'dropbox:' == substr($appkey, 0, 8)) {
|
||||
$key = substr($appkey, 8);
|
||||
} else if (!empty($appkey)) {
|
||||
$key = $appkey;
|
||||
}
|
||||
|
||||
if ('' != $this->instance_id) $this->instance_id = ':'.$this->instance_id;
|
||||
|
||||
$params = array(
|
||||
'client_id' => empty($key) ? $this->oauth2_id : $key,
|
||||
'response_type' => 'code',
|
||||
'redirect_uri' => empty($key) ? $this->callback : $this->callbackhome,
|
||||
'state' => empty($key) ? "POST:".$CSRF.$this->instance_id.$this->callbackhome : $CSRF.$this->instance_id,
|
||||
'scope' => implode(' ', $this->scopes),
|
||||
'token_access_type' => 'offline'
|
||||
);
|
||||
|
||||
// Build the URL and redirect the user
|
||||
$query = '?' . http_build_query($params, '', '&');
|
||||
$url = self::WEB_URL . self::AUTHORISE_METHOD . $query;
|
||||
return $url;
|
||||
}
|
||||
|
||||
protected function deauthenticate()
|
||||
{
|
||||
$url = UpdraftPlus_Dropbox_API::API_URL_V2 . self::DEAUTHORISE_METHOD;
|
||||
$response = $this->fetch('POST', $url, '', array('api_v2' => true));
|
||||
$this->storage->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquire an access token
|
||||
* Tokens acquired at this point should be stored to
|
||||
* prevent having to request new tokens for each API call
|
||||
* @link http://tools.ietf.org/html/rfc5849#section-2.3
|
||||
*/
|
||||
public function getAccessToken()
|
||||
{
|
||||
|
||||
// If this is non-empty, then we just received a code. It is stored in 'code' - our next job is to put it into the proper place.
|
||||
$code = $this->storage->get('code');
|
||||
/*
|
||||
Checks to see if the user is using their own Dropbox App if so then they need to get
|
||||
a request token. If they are using our App then we just need to save these details
|
||||
*/
|
||||
if (!empty($code)){
|
||||
$appkey = $this->storage->get('appkey');
|
||||
if (!empty($appkey)){
|
||||
// Get the signed request URL
|
||||
$url = UpdraftPlus_Dropbox_API::API_URL_V2 . self::ACCESS_TOKEN_METHOD;
|
||||
$params = array(
|
||||
'code' => $code,
|
||||
'grant_type' => 'authorization_code',
|
||||
'redirect_uri' => $this->callbackhome,
|
||||
'client_id' => $this->consumerKey,
|
||||
'client_secret' => $this->consumerSecret,
|
||||
);
|
||||
$response = $this->fetch('POST', $url, '' , $params);
|
||||
|
||||
$code = json_decode(json_encode($response['body']),true);
|
||||
|
||||
} else {
|
||||
$code = base64_decode($code);
|
||||
$code = json_decode($code, true);
|
||||
}
|
||||
|
||||
/*
|
||||
Again oauth token secret and oauth token were needed by oauth1
|
||||
these are replaced in oauth2 with an access token
|
||||
currently they are still there just in case a method somewhere is expecting them to both be set
|
||||
as far as I can tell only the oauth token is used
|
||||
after more testing token secret can be removed.
|
||||
*/
|
||||
$token = new stdClass();
|
||||
$token->oauth_token_secret = $code['access_token'];
|
||||
$token->oauth_token = $code['access_token'];
|
||||
$token->account_id = $code['account_id'];
|
||||
$token->token_type = $code['token_type'];
|
||||
$token->uid = $code['uid'];
|
||||
$token->refresh_token = $code['refresh_token'];
|
||||
$token->expires_in = time() + $code['expires_in'] - 30;
|
||||
$this->storage->set($token, 'access_token');
|
||||
$this->storage->do_unset('upgraded');
|
||||
|
||||
//reset code
|
||||
$this->storage->do_unset('code');
|
||||
} else {
|
||||
throw new Dropbox_BadRequestException("No Dropbox Code found, will try to get one now", 400);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will make a request to the auth server sending the users refresh token to get a new access token
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function refreshAccessToken() {
|
||||
global $updraftplus;
|
||||
|
||||
$access_token = $this->storage->get('access_token');
|
||||
|
||||
if ($this->callback == $this->callbackhome) {
|
||||
$url = UpdraftPlus_Dropbox_API::API_URL_V2 . self::ACCESS_TOKEN_METHOD;
|
||||
|
||||
$params = array(
|
||||
'grant_type' => 'refresh_token',
|
||||
'refresh_token' => $access_token->refresh_token,
|
||||
'client_id' => $this->consumerKey,
|
||||
'client_secret' => $this->consumerSecret,
|
||||
);
|
||||
} else {
|
||||
$url = $this->callback;
|
||||
|
||||
$params = array(
|
||||
'code' => 'ud_dropbox_code',
|
||||
'refresh_token' => $access_token->refresh_token,
|
||||
'headers' => apply_filters('updraftplus_auth_headers', ''),
|
||||
);
|
||||
}
|
||||
|
||||
$response = $this->fetch('POST', $url, '' , $params);
|
||||
|
||||
if ("200" != $response['code']) {
|
||||
$updraftplus->log('Failed to refresh access token error code: '.$response['code']);
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($response['body'])) {
|
||||
$updraftplus->log('Failed to refresh access token empty response body');
|
||||
return;
|
||||
}
|
||||
|
||||
// If the request comes from the auth server (master app refreshing a token) then we need to decode the response into an object before we can use it.
|
||||
$body = is_object($response['body']) ? $response['body'] : json_decode(base64_decode($response['body']));
|
||||
|
||||
if (isset($body->access_token) && isset($body->expires_in)) {
|
||||
$access_token->oauth_token_secret = $body->access_token;
|
||||
$access_token->oauth_token = $body->access_token;
|
||||
$access_token->expires_in = time() + $body->expires_in - 30;
|
||||
$this->storage->set($access_token, 'access_token');
|
||||
$updraftplus->log('Successfully updated and refreshed the access token');
|
||||
} else {
|
||||
$updraftplus->log('Failed to refresh access token missing token and expiry: '.json_encode($body));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the request/access token
|
||||
* This will return the access/request token depending on
|
||||
* which stage we are at in the OAuth flow, or a dummy object
|
||||
* if we have not yet started the authentication process
|
||||
* @return object stdClass
|
||||
*/
|
||||
private function getToken()
|
||||
{
|
||||
if (!$token = $this->storage->get('access_token')) {
|
||||
if (!$token = $this->storage->get('request_token')) {
|
||||
$token = new stdClass();
|
||||
$token->oauth_token = null;
|
||||
$token->oauth_token_secret = null;
|
||||
}
|
||||
}
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate signed request URL
|
||||
* See inline comments for description
|
||||
* @link http://tools.ietf.org/html/rfc5849#section-3.4
|
||||
* @param string $method HTTP request method
|
||||
* @param string $url API endpoint to send the request to
|
||||
* @param string $call API call to send
|
||||
* @param array $additional Additional parameters as an associative array
|
||||
* @return array
|
||||
*/
|
||||
protected function getSignedRequest($method, $url, $call, array $additional = array())
|
||||
{
|
||||
// Get the request/access token
|
||||
$token = $this->getToken();
|
||||
|
||||
// Prepare the standard request parameters differently for OAuth1 and OAuth2; we still need OAuth1 to make the request to the upgrade token endpoint
|
||||
if (isset($token->token_type)) {
|
||||
$params = array(
|
||||
'access_token' => $token->oauth_token,
|
||||
);
|
||||
|
||||
/*
|
||||
To keep this API backwards compatible with the API v1 endpoints all v2 endpoints will also send to this method a api_v2 parameter this will then return just the access token as the signed request is not needed for any calls.
|
||||
*/
|
||||
|
||||
if (isset($additional['api_v2']) && $additional['api_v2'] == true) {
|
||||
unset($additional['api_v2']);
|
||||
if (isset($additional['timeout'])) unset($additional['timeout']);
|
||||
if (isset($additional['content_download']) && $additional['content_download'] == true) {
|
||||
unset($additional['content_download']);
|
||||
$extra_headers = array();
|
||||
if (isset($additional['headers'])) {
|
||||
foreach ($additional['headers'] as $key => $header) {
|
||||
$extra_headers[] = $header;
|
||||
}
|
||||
unset($additional['headers']);
|
||||
}
|
||||
$headers = array(
|
||||
'Authorization: Bearer '.$params['access_token'],
|
||||
'Content-Type:',
|
||||
'Dropbox-API-Arg: '.json_encode($additional),
|
||||
);
|
||||
|
||||
$headers = array_merge($headers, $extra_headers);
|
||||
$additional = '';
|
||||
} else if (isset($additional['content_upload']) && $additional['content_upload'] == true) {
|
||||
unset($additional['content_upload']);
|
||||
$headers = array(
|
||||
'Authorization: Bearer '.$params['access_token'],
|
||||
'Content-Type: application/octet-stream',
|
||||
'Dropbox-API-Arg: '.json_encode($additional),
|
||||
);
|
||||
$additional = '';
|
||||
} else {
|
||||
$headers = array(
|
||||
'Authorization: Bearer '.$params['access_token'],
|
||||
'Content-Type: application/json',
|
||||
);
|
||||
}
|
||||
return array(
|
||||
'url' => $url . $call,
|
||||
'postfields' => $additional,
|
||||
'headers' => $headers,
|
||||
);
|
||||
} elseif (isset($additional['code']) && isset($additional['refresh_token'])) {
|
||||
$extra_headers = array();
|
||||
if (isset($additional['headers']) && !empty($additional['headers'])) {
|
||||
foreach ($additional['headers'] as $key => $header) {
|
||||
$extra_headers[] = $key.': '.$header;
|
||||
}
|
||||
unset($additional['headers']);
|
||||
}
|
||||
$headers = array();
|
||||
$headers = array_merge($headers, $extra_headers);
|
||||
|
||||
return array(
|
||||
'url' => $url . $call,
|
||||
'postfields' => $additional,
|
||||
'headers' => $headers,
|
||||
);
|
||||
}
|
||||
// if grant_type is set and it's value is refresh_token, then this is a custom app trying to get a new access token using their refresh token. So we don't want to send an access token and break the request.
|
||||
if (isset($additional['grant_type']) && 'refresh_token' == $additional['grant_type']) unset($params['access_token']);
|
||||
} else {
|
||||
// Generate a random string for the request
|
||||
$nonce = md5(microtime(true) . uniqid('', true));
|
||||
$params = array(
|
||||
'oauth_consumer_key' => $this->consumerKey,
|
||||
'oauth_token' => $token->oauth_token,
|
||||
'oauth_signature_method' => $this->sigMethod,
|
||||
'oauth_version' => '1.0',
|
||||
// Generate nonce and timestamp if signature method is HMAC-SHA1
|
||||
'oauth_timestamp' => ($this->sigMethod == 'HMAC-SHA1') ? time() : null,
|
||||
'oauth_nonce' => ($this->sigMethod == 'HMAC-SHA1') ? $nonce : null,
|
||||
);
|
||||
}
|
||||
|
||||
// Merge with the additional request parameters
|
||||
$params = array_merge($params, $additional);
|
||||
ksort($params);
|
||||
|
||||
// URL encode each parameter to RFC3986 for use in the base string
|
||||
$encoded = array();
|
||||
foreach($params as $param => $value) {
|
||||
if ($value !== null) {
|
||||
// If the value is a file upload (prefixed with @), replace it with
|
||||
// the destination filename, the file path will be sent in POSTFIELDS
|
||||
if (isset($value[0]) && $value[0] === '@') $value = $params['filename'];
|
||||
# Prevent spurious PHP warning by only doing non-arrays
|
||||
if (!is_array($value)) $encoded[] = $this->encode($param) . '=' . $this->encode($value);
|
||||
} else {
|
||||
unset($params[$param]);
|
||||
}
|
||||
}
|
||||
|
||||
// Build the first part of the string
|
||||
$base = $method . '&' . $this->encode($url . $call) . '&';
|
||||
|
||||
// Re-encode the encoded parameter string and append to $base
|
||||
$base .= $this->encode(implode('&', $encoded));
|
||||
|
||||
// Concatenate the secrets with an ampersand
|
||||
$key = $this->consumerSecret . '&' . $token->oauth_token_secret;
|
||||
|
||||
// Get the signature string based on signature method
|
||||
$signature = $this->getSignature($base, $key);
|
||||
$params['oauth_signature'] = $signature;
|
||||
|
||||
// Build the signed request URL
|
||||
$query = '?' . http_build_query($params, '', '&');
|
||||
|
||||
return array(
|
||||
'url' => $url . $call . $query,
|
||||
'postfields' => $params,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the oauth_signature for a request
|
||||
* @param string $base Signature base string, used by HMAC-SHA1
|
||||
* @param string $key Concatenated consumer and token secrets
|
||||
*/
|
||||
private function getSignature($base, $key)
|
||||
{
|
||||
switch ($this->sigMethod) {
|
||||
case 'PLAINTEXT':
|
||||
$signature = $key;
|
||||
break;
|
||||
case 'HMAC-SHA1':
|
||||
$signature = base64_encode(hash_hmac('sha1', $base, $key, true));
|
||||
break;
|
||||
}
|
||||
|
||||
return $signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the OAuth signature method
|
||||
* @param string $method Either PLAINTEXT or HMAC-SHA1
|
||||
* @return void
|
||||
*/
|
||||
public function setSignatureMethod($method)
|
||||
{
|
||||
$method = strtoupper($method);
|
||||
|
||||
switch ($method) {
|
||||
case 'PLAINTEXT':
|
||||
case 'HMAC-SHA1':
|
||||
$this->sigMethod = $method;
|
||||
break;
|
||||
default:
|
||||
throw new Dropbox_Exception('Unsupported signature method ' . $method); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- The escaping should be happening when the exception is printed
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the output file
|
||||
* @param resource Resource to stream response data to
|
||||
* @return void
|
||||
*/
|
||||
public function setOutFile($handle)
|
||||
{
|
||||
if (!is_resource($handle) || get_resource_type($handle) != 'stream') {
|
||||
throw new Dropbox_Exception('Outfile must be a stream resource');
|
||||
}
|
||||
$this->outFile = $handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the input file
|
||||
* @param resource Resource to read data from
|
||||
* @return void
|
||||
*/
|
||||
public function setInFile($handle) {
|
||||
$this->inFile = $handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse response parameters for a token into an object
|
||||
* Dropbox returns tokens in the response parameters, and
|
||||
* not a JSON encoded object as per other API requests
|
||||
* @link http://oauth.net/core/1.0/#response_parameters
|
||||
* @param string $response
|
||||
* @return object stdClass
|
||||
*/
|
||||
private function parseTokenString($response)
|
||||
{
|
||||
$parts = explode('&', $response);
|
||||
$token = new stdClass();
|
||||
foreach ($parts as $part) {
|
||||
list($k, $v) = explode('=', $part, 2);
|
||||
$k = strtolower($k);
|
||||
$token->$k = $v;
|
||||
}
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a value to RFC3986
|
||||
* This is a convenience method to decode ~ symbols encoded
|
||||
* by rawurldecode. This will encode all characters except
|
||||
* the unreserved set, ALPHA, DIGIT, '-', '.', '_', '~'
|
||||
* @link http://tools.ietf.org/html/rfc5849#section-3.6
|
||||
* @param mixed $value
|
||||
*/
|
||||
private function encode($value)
|
||||
{
|
||||
return str_replace('%7E', '~', rawurlencode($value));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
<?php
|
||||
// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped
|
||||
/**
|
||||
* OAuth consumer using PHP cURL
|
||||
* @author Ben Tadiar <ben@handcraftedbyben.co.uk>
|
||||
* @link https://github.com/benthedesigner/dropbox
|
||||
* @package Dropbox\OAuth
|
||||
* @subpackage Consumer
|
||||
*/
|
||||
|
||||
class Dropbox_Curl extends Dropbox_ConsumerAbstract
|
||||
{
|
||||
/**
|
||||
* Dropbox consumer key
|
||||
* @var String
|
||||
*/
|
||||
protected $consumerKey;
|
||||
|
||||
/**
|
||||
* Dropbox oauth2_id
|
||||
* @var String
|
||||
*/
|
||||
protected $oauth2_id;
|
||||
|
||||
/**
|
||||
* Dropbox consumer secret
|
||||
* @var String
|
||||
*/
|
||||
protected $consumerSecret;
|
||||
|
||||
/**
|
||||
* Dropbox storage object
|
||||
* @var \Dropbox\OAuth\Consumer\StorageInterface|Dropbox_StorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* Not used anywhere but it is set
|
||||
* @var Callable|Null
|
||||
*/
|
||||
protected $callback;
|
||||
|
||||
/**
|
||||
* Callback URL
|
||||
* @var String|null
|
||||
*/
|
||||
protected $callbackhome;
|
||||
|
||||
/**
|
||||
* Dropbox storage instance id
|
||||
* @var String
|
||||
*/
|
||||
protected $instance_id;
|
||||
|
||||
/**
|
||||
* Default cURL options
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultOptions = array(
|
||||
CURLOPT_VERBOSE => true,
|
||||
CURLOPT_HEADER => true,
|
||||
CURLINFO_HEADER_OUT => false,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FOLLOWLOCATION => false,
|
||||
);
|
||||
|
||||
/**
|
||||
* Store the last response form the API
|
||||
* @var mixed
|
||||
*/
|
||||
protected $lastResponse = null;
|
||||
|
||||
/**
|
||||
* Set properties and begin authentication
|
||||
* @param string $key
|
||||
* @param string $secret
|
||||
* @param \Dropbox\OAuth\Consumer\StorageInterface $storage
|
||||
* @param string $callback
|
||||
*/
|
||||
public function __construct($key, $oauth2_id, $secret, Dropbox_StorageInterface $storage, $callback = null, $callbackhome = null, $deauthenticate = false, $instance_id = '')
|
||||
{
|
||||
// Check the cURL extension is loaded
|
||||
if (!extension_loaded('curl')) {
|
||||
throw new Dropbox_Exception('The cURL OAuth consumer requires the cURL extension. Please speak to your web hosting provider so that this missing PHP component can be installed.');
|
||||
}
|
||||
|
||||
$this->consumerKey = $key;
|
||||
$this->oauth2_id = $oauth2_id;
|
||||
$this->consumerSecret = $secret;
|
||||
$this->storage = $storage;
|
||||
$this->callback = $callback;
|
||||
$this->callbackhome = $callbackhome;
|
||||
$this->instance_id = $instance_id;
|
||||
|
||||
if ($deauthenticate) {
|
||||
$this->deauthenticate();
|
||||
} else {
|
||||
$this->authenticate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an API call
|
||||
* @todo Improve error handling
|
||||
* @param string $method The HTTP method
|
||||
* @param string $url The API endpoint
|
||||
* @param string $call The API method to call
|
||||
* @param array $additional Additional parameters
|
||||
* @return string|object stdClass
|
||||
*/
|
||||
public function fetch($method, $url, $call, array $additional = array(), $retry_with_header = false)
|
||||
{
|
||||
// Get the signed request URL
|
||||
$request = $this->getSignedRequest($method, $url, $call, $additional);
|
||||
|
||||
// Initialise and execute a cURL request
|
||||
$handle = curl_init($request['url']);
|
||||
|
||||
// Get the default options array
|
||||
$options = $this->defaultOptions;
|
||||
if (!UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts')) {
|
||||
$options[CURLOPT_CAINFO] = UPDRAFTPLUS_DIR.'/includes/cacert.pem';
|
||||
}
|
||||
if (UpdraftPlus_Options::get_updraft_option('updraft_ssl_disableverify')) {
|
||||
$options[CURLOPT_SSL_VERIFYPEER] = false;
|
||||
} else {
|
||||
$options[CURLOPT_SSL_VERIFYPEER] = true;
|
||||
}
|
||||
|
||||
if (!class_exists('WP_HTTP_Proxy')) require_once(ABSPATH.WPINC.'/class-http.php');
|
||||
$proxy = new WP_HTTP_Proxy();
|
||||
|
||||
if ($proxy->is_enabled()) {
|
||||
# WP_HTTP_Proxy returns empty strings if nothing is set
|
||||
$user = $proxy->username();
|
||||
$pass = $proxy->password();
|
||||
$host = $proxy->host();
|
||||
$port = (int)$proxy->port();
|
||||
if (empty($port)) $port = 8080;
|
||||
if (!empty($host) && $proxy->send_through_proxy($request['url'])) {
|
||||
$options[CURLOPT_PROXY] = $host;
|
||||
$options[CURLOPT_PROXYTYPE] = CURLPROXY_HTTP;
|
||||
$options[CURLOPT_PROXYPORT] = $port;
|
||||
if (!empty($user) && !empty($pass)) {
|
||||
$options[CURLOPT_PROXYAUTH] = CURLAUTH_ANY;
|
||||
$options[CURLOPT_PROXYUSERPWD] = sprintf('%s:%s', $user, $pass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Add check to see if it's an API v2 call if so then json encode the contents. This is so that it is backwards compatible with API v1 endpoints.
|
||||
*/
|
||||
if (isset($additional['api_v2']) && !empty($request['postfields'])) {
|
||||
$request['postfields'] = json_encode($request['postfields']);
|
||||
}
|
||||
|
||||
if (isset($request['headers']) && !empty($request['headers'])) $options[CURLOPT_HTTPHEADER] = $request['headers'];
|
||||
|
||||
if ($method == 'GET' && $this->outFile) { // GET
|
||||
$options[CURLOPT_RETURNTRANSFER] = false;
|
||||
$options[CURLOPT_HEADER] = false;
|
||||
$options[CURLOPT_FILE] = $this->outFile;
|
||||
$options[CURLOPT_BINARYTRANSFER] = true;
|
||||
$options[CURLOPT_FAILONERROR] = true;
|
||||
$this->outFile = null;
|
||||
} elseif ($method == 'POST' && $this->outFile) { // POST
|
||||
$options[CURLOPT_POST] = true;
|
||||
$options[CURLOPT_RETURNTRANSFER] = false;
|
||||
$options[CURLOPT_HEADER] = false;
|
||||
$options[CURLOPT_FILE] = $this->outFile;
|
||||
$options[CURLOPT_BINARYTRANSFER] = true;
|
||||
$options[CURLOPT_FAILONERROR] = true;
|
||||
$this->outFile = null;
|
||||
} elseif ($method == 'POST' && $this->inFile) { // POST
|
||||
$options[CURLOPT_POST] = true;
|
||||
$options[CURLOPT_POSTFIELDS] = $this->inFile;
|
||||
} elseif ($method == 'POST') { // POST
|
||||
$options[CURLOPT_POST] = true;
|
||||
if (!empty($request['postfields'])) {
|
||||
$options[CURLOPT_POSTFIELDS] = $request['postfields'];
|
||||
} elseif (empty($additional['content_upload'])) {
|
||||
// JSON representation of nullity
|
||||
$options[CURLOPT_POSTFIELDS] = 'null';
|
||||
} elseif ($retry_with_header) {
|
||||
// It's a content upload, and there's no data. Versions of php-curl differ as to whether they add a Content-Length header automatically or not. Dropbox complains if it's not there. Here we have had a Dropbox 400 bad request returned so we try again with the header
|
||||
$options[CURLOPT_HTTPHEADER] = array_merge($options[CURLOPT_HTTPHEADER], array('Content-Length: 0'));
|
||||
}
|
||||
} elseif ($method == 'PUT' && $this->inFile) { // PUT
|
||||
$options[CURLOPT_PUT] = true;
|
||||
$options[CURLOPT_INFILE] = $this->inFile;
|
||||
// @todo Update so the data is not loaded into memory to get its size
|
||||
$options[CURLOPT_INFILESIZE] = strlen(stream_get_contents($this->inFile));
|
||||
fseek($this->inFile, 0);
|
||||
$this->inFile = null;
|
||||
}
|
||||
|
||||
if (isset($additional['timeout'])) {
|
||||
$options[CURLOPT_TIMEOUT] = $additional['timeout'];
|
||||
}
|
||||
|
||||
if (function_exists('apply_filters')) $options = apply_filters('updraftplus_dropbox_fetch_curl_options', $options, $method, $url, $call, $additional);
|
||||
|
||||
// Set the cURL options at once
|
||||
curl_setopt_array($handle, $options);
|
||||
// Execute, get any error and close
|
||||
$response = curl_exec($handle);
|
||||
$error = curl_error($handle);
|
||||
$getinfo = curl_getinfo($handle);
|
||||
|
||||
curl_close($handle);
|
||||
|
||||
//Check if a cURL error has occured
|
||||
if ($response === false) {
|
||||
throw new Dropbox_CurlException($error);
|
||||
} else {
|
||||
// Parse the response if it is a string
|
||||
if (is_string($response)) {
|
||||
$response = $this->parse($response);
|
||||
}
|
||||
|
||||
// Set the last response
|
||||
$this->lastResponse = $response;
|
||||
|
||||
$code = (!empty($response['code'])) ? $response['code'] : $getinfo['http_code'];
|
||||
|
||||
// The API doesn't return an error message for the 304 status code...
|
||||
// 304's are only returned when the path supplied during metadata calls has not been modified
|
||||
if ($code == 304) {
|
||||
$response['body'] = new stdClass;
|
||||
$response['body']->error = 'The folder contents have not changed';
|
||||
}
|
||||
|
||||
// Check if an error occurred and throw an Exception
|
||||
if (!empty($response['body']->error) || $code >= 400) {
|
||||
// Dropbox returns error messages inconsistently...
|
||||
if (!empty($response['body']->error) && $response['body']->error instanceof stdClass) {
|
||||
$array = array_values((array) $response['body']->error);
|
||||
// Dropbox API v2 only throws 409 errors if this error is a incorrect_offset then we need the entire error array not just the message. PHP Exception messages have to be a string so JSON encode the array.
|
||||
$extract_message = (is_object($array[0]) && isset($array[0]->{'.tag'})) ? $array[0]->{'.tag'} : $array[0];
|
||||
if (strpos($extract_message, 'incorrect_offset') !== false) {
|
||||
$message = json_encode($array);
|
||||
} elseif (strpos($extract_message, 'lookup_failed') !== false ) {
|
||||
// re-structure the array so it is correctly formatted for API
|
||||
// Note: Dropbox v2 returns different errors at different stages hence this fix
|
||||
$correctOffset = array(
|
||||
'0' => $array[1]->{'.tag'},
|
||||
);
|
||||
// the lookup_failed response doesn't always return a correct_offset this happens when the lookup fails because the session has been closed e.g the file has already been uploaded but the response didn't make it back to the client so we try again
|
||||
if (isset($array[1]->correct_offset)) $correctOffset['1'] = $array[1]->correct_offset;
|
||||
|
||||
$message = json_encode($correctOffset);
|
||||
} else {
|
||||
$message = '';
|
||||
$property = 'error';
|
||||
$resp = $response['body'];
|
||||
while (isset($resp->$property)) {
|
||||
if (is_string($resp->$property)) $message .= $resp->$property.'/';
|
||||
if (!is_object($resp->$property) || empty($resp->$property->{'.tag'})) break;
|
||||
$property = $resp->$property->{'.tag'};
|
||||
$message .= $property.'/';
|
||||
$resp = $response['body']->error;
|
||||
}
|
||||
}
|
||||
} elseif (!empty($response['body']->error)) {
|
||||
$message = $response['body']->error;
|
||||
} elseif (is_string($response['body'])) {
|
||||
// 31 Mar 2017 - This case has been found to exist; though the docs imply that there's always an 'error' property and that what is returned in JSON, we found a case of this being returned just as a simple string, but detectable via an HTTP 400: Error in call to API function "files/upload_session/append_v2": HTTP header "Dropbox-API-Arg": cursor.offset: expected integer, got string
|
||||
$message = $response['body'];
|
||||
} elseif (!empty($response['body']->error_summary)) {
|
||||
$message = $response['body']->error_summary;
|
||||
} else {
|
||||
$message = "HTTP bad response code: $code";
|
||||
}
|
||||
|
||||
// Throw an Exception with the appropriate with the appropriate message and code
|
||||
switch ($code) {
|
||||
case 304:
|
||||
throw new Dropbox_NotModifiedException($message, 304);
|
||||
case 400:
|
||||
if (!$retry_with_header) return $this->fetch($method, $url, $call, $additional, true);
|
||||
throw new Dropbox_BadRequestException($message, 400);
|
||||
case 404:
|
||||
throw new Dropbox_NotFoundException($message, 404);
|
||||
case 406:
|
||||
throw new Dropbox_NotAcceptableException($message, 406);
|
||||
case 415:
|
||||
throw new Dropbox_UnsupportedMediaTypeException($message, 415);
|
||||
case 401:
|
||||
//401 means oauth token is expired continue to manually handle the exception depending on the situation
|
||||
break;
|
||||
case 409:
|
||||
//409 in API V2 every error will return with a 409 to find out what the error is the error description should be checked.
|
||||
throw new Dropbox_Exception($message, $code);
|
||||
default:
|
||||
throw new Dropbox_Exception($message, $code);
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a cURL response
|
||||
* @param string $response
|
||||
* @return array
|
||||
*/
|
||||
private function parse($response)
|
||||
{
|
||||
// Explode the response into headers and body parts (separated by double EOL)
|
||||
list($headers, $response) = explode("\r\n\r\n", $response, 2);
|
||||
|
||||
// Explode response headers
|
||||
$lines = explode("\r\n", $headers);
|
||||
|
||||
// If the status code is 100, the API server must send a final response
|
||||
// We need to explode the response again to get the actual response
|
||||
if (preg_match('#^HTTP/[\.\d]+ 100#i', $lines[0])) {
|
||||
list($headers, $response) = explode("\r\n\r\n", $response, 2);
|
||||
$lines = explode("\r\n", $headers);
|
||||
}
|
||||
|
||||
// Get the HTTP response code from the first line
|
||||
$first = array_shift($lines);
|
||||
$pattern = '#^HTTP/[\.\d]+ ([0-9]{3})#i';
|
||||
preg_match($pattern, $first, $matches);
|
||||
$code = $matches[1];
|
||||
|
||||
// Parse the remaining headers into an associative array
|
||||
$headers = array();
|
||||
foreach ($lines as $line) {
|
||||
list($k, $v) = explode(': ', $line, 2);
|
||||
$headers[strtolower($k)] = $v;
|
||||
}
|
||||
|
||||
// If the response body is not a JSON encoded string
|
||||
// we'll return the entire response body
|
||||
if (!$body = json_decode($response)) {
|
||||
$body = $response;
|
||||
}
|
||||
|
||||
if (is_string($body)) {
|
||||
$body_lines = explode("\r\n", $body);
|
||||
if (preg_match('#^HTTP/[\.\d]+ 100#i', $body_lines[0]) && preg_match('#^HTTP/\d#i', $body_lines[2])) {
|
||||
return $this->parse($body);
|
||||
}
|
||||
}
|
||||
|
||||
return array('code' => $code, 'body' => $body, 'headers' => $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the response for the last API request
|
||||
* @return mixed
|
||||
*/
|
||||
public function getlastResponse()
|
||||
{
|
||||
return $this->lastResponse;
|
||||
}
|
||||
}
|
||||
// phpcs:enable
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* OAuth consumer using the WordPress API
|
||||
* @author David Anderson <david@updraftplus.com>
|
||||
* @link https://github.com/DavidAnderson684/Dropbox
|
||||
* @package Dropbox\OAuth
|
||||
* @subpackage Consumer
|
||||
*/
|
||||
|
||||
class Dropbox_ConsumerWordPress extends Dropbox_ConsumerAbstract
|
||||
{
|
||||
|
||||
/**
|
||||
* Set properties and begin authentication
|
||||
* @param string $key
|
||||
* @param string $secret
|
||||
* @param \Dropbox\OAuth\Consumer\StorageInterface $storage
|
||||
* @param string $callback
|
||||
*/
|
||||
public function __construct($key, $secret, Dropbox_StorageInterface $storage, $callback = null)
|
||||
{
|
||||
// Check we are in a WordPress environment
|
||||
if (!defined('ABSPATH')) {
|
||||
throw new Dropbox_Exception('The WordPress OAuth consumer requires a WordPress environment');
|
||||
}
|
||||
|
||||
$this->consumerKey = $key;
|
||||
$this->consumerSecret = $secret;
|
||||
$this->storage = $storage;
|
||||
$this->callback = $callback;
|
||||
$this->authenticate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an API call
|
||||
* @param string $method The HTTP method
|
||||
* @param string $url The API endpoint
|
||||
* @param string $call The API method to call
|
||||
* @param array $additional Additional parameters
|
||||
* @return array
|
||||
*/
|
||||
public function fetch($method, $url, $call, array $additional = array())
|
||||
{
|
||||
// Get the signed request URL
|
||||
$request = $this->getSignedRequest($method, $url, $call, $additional);
|
||||
if ($method == 'GET') {
|
||||
$args = array ( );
|
||||
$response = wp_remote_get($request['url'], $args);
|
||||
$this->outFile = null;
|
||||
} elseif ($method == 'POST') {
|
||||
$args = array( 'body' => $request['postfields'] );
|
||||
$response = wp_remote_post($request['url'], $args );
|
||||
} elseif ($method == 'PUT' && $this->inFile) {
|
||||
return new WP_Error('unsupported', "WordPress does not have a native HTTP PUT function");
|
||||
}
|
||||
|
||||
// If the response body is not a JSON encoded string
|
||||
// we'll return the entire response body
|
||||
// Important to do this first, as the next section relies on the decoding having taken place
|
||||
if (!$body = json_decode(wp_remote_retrieve_body($response))) {
|
||||
$body = wp_remote_retrieve_body($response);
|
||||
}
|
||||
|
||||
// Check if an error occurred and throw an Exception. This is part of the authentication process - don't modify.
|
||||
if (!empty($body->error)) {
|
||||
$message = $body->error . ' (Status Code: ' . wp_remote_retrieve_response_code($response) . ')';
|
||||
throw new Dropbox_Exception($message); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- The escaping should happen when the exception is caught and printed
|
||||
}
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
$message = $response->get_error_message();
|
||||
throw new Dropbox_Exception($message); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- The escaping should happen when the exception is caught and printed
|
||||
}
|
||||
|
||||
$results = array ( 'body' => $body, 'code' => wp_remote_retrieve_response_code($response), 'headers' => $response['headers'] );
|
||||
return $results;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This class provides the functionality to encrypt
|
||||
* and decrypt access tokens stored by the application
|
||||
* @author Ben Tadiar <ben@handcraftedbyben.co.uk>
|
||||
* @link https://github.com/benthedesigner/dropbox
|
||||
* @package Dropbox\Oauth
|
||||
* @subpackage Storage
|
||||
*/
|
||||
|
||||
/* UpdraftPlus notes
|
||||
Using this was fairly pointless (it encrypts storage credentials at rest). But, it's implemented now, so needs supporting.
|
||||
Investigation shows that mcrypt and phpseclib native encryption using different padding schemes.
|
||||
As a result, that which is encrypted by phpseclib native can be decrypted by mcrypt, but not vice-versa. Each can (as you'd expect) decrypt the results of their own encryption.
|
||||
As a consequence, it makes sense to always encrypt with phpseclib native, and prefer decrypting with with mcrypt if it is available and otherwise fall back to phpseclib.
|
||||
We could deliberately re-encrypt all loaded information with phpseclib native, but there seems little need for that yet. There can only be a problem if mcrypt is disabled - which pre-July-2015 meant that Dropbox wouldn't work at all. Now, it will force a re-authorisation.
|
||||
*/
|
||||
|
||||
class Dropbox_Encrypter
|
||||
{
|
||||
// Encryption settings - default settings yield encryption to AES (256-bit) standard
|
||||
// @todo Provide PHPDOC for each class constant
|
||||
const KEY_SIZE = 32;
|
||||
const IV_SIZE = 16;
|
||||
|
||||
/**
|
||||
* Encryption key
|
||||
* @var null|string
|
||||
*/
|
||||
private $key = null;
|
||||
|
||||
/**
|
||||
* Check Mcrypt is loaded and set the encryption key
|
||||
* @param string $key
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($key)
|
||||
{
|
||||
if (preg_match('/^[A-Za-z0-9]+$/', $key) && $length = strlen($key) === self::KEY_SIZE) {
|
||||
# Short-cut so that the mbstring extension is not required
|
||||
$this->key = $key;
|
||||
} elseif (($length = mb_strlen($key, '8bit')) !== self::KEY_SIZE) {
|
||||
throw new Dropbox_Exception('Expecting a ' . self::KEY_SIZE . ' byte key, got ' . $length); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- The escaping should happen when the exception is caught and printed
|
||||
} else {
|
||||
// Set the encryption key
|
||||
$this->key = $key;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt the OAuth token
|
||||
* @param \stdClass $token Serialized token object
|
||||
* @return string
|
||||
*/
|
||||
public function encrypt($token)
|
||||
{
|
||||
|
||||
// Encryption: we always use phpseclib for this
|
||||
global $updraftplus;
|
||||
$ensure_phpseclib = $updraftplus->ensure_phpseclib();
|
||||
|
||||
if (is_wp_error($ensure_phpseclib)) {
|
||||
$updraftplus->log("Failed to load phpseclib classes (".$ensure_phpseclib->get_error_code()."): ".$ensure_phpseclib->get_error_message());
|
||||
$updraftplus->log("Failed to load phpseclib classes (".$ensure_phpseclib->get_error_code()."): ".$ensure_phpseclib->get_error_message(), 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
$updraftplus->ensure_phpseclib();
|
||||
|
||||
$iv = phpseclib_Crypt_Random::string(self::IV_SIZE);
|
||||
|
||||
// Defaults to CBC mode
|
||||
$rijndael = new phpseclib_Crypt_Rijndael();
|
||||
|
||||
$rijndael->setKey($this->key);
|
||||
|
||||
$rijndael->setIV($iv);
|
||||
|
||||
$cipherText = $rijndael->encrypt($token);
|
||||
|
||||
return base64_encode($iv . $cipherText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt the ciphertext
|
||||
* @param string $cipherText
|
||||
* @return object \stdClass Unserialized token
|
||||
*/
|
||||
public function decrypt($cipherText)
|
||||
{
|
||||
|
||||
// Decryption: prefer mcrypt, if available (since it can decrypt data encrypted by either mcrypt or phpseclib)
|
||||
|
||||
$cipherText = base64_decode($cipherText);
|
||||
$iv = substr($cipherText, 0, self::IV_SIZE);
|
||||
$cipherText = substr($cipherText, self::IV_SIZE);
|
||||
|
||||
$decrypted = false;
|
||||
|
||||
if (function_exists('mcrypt_decrypt')) {
|
||||
// @codingStandardsIgnoreLine
|
||||
$token = @mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $this->key, $cipherText, MCRYPT_MODE_CBC, $iv);
|
||||
// Some plugins provide their own version of mcrypt_* functions and they don't provide the functionality that the original method has, so try and detect if the decryption has failed and if so try rijndael
|
||||
if (false != $token) $decrypted = true;
|
||||
}
|
||||
|
||||
if (!$decrypted) {
|
||||
global $updraftplus;
|
||||
$updraftplus->ensure_phpseclib();
|
||||
|
||||
$rijndael = new phpseclib_Crypt_Rijndael();
|
||||
$rijndael->setKey($this->key);
|
||||
$rijndael->setIV($iv);
|
||||
$token = $rijndael->decrypt($cipherText);
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* OAuth storage handler interface
|
||||
* @author Ben Tadiar <ben@handcraftedbyben.co.uk>
|
||||
* @link https://github.com/benthedesigner/dropbox
|
||||
* @package Dropbox\OAuth
|
||||
* @subpackage Storage
|
||||
*/
|
||||
|
||||
interface Dropbox_StorageInterface
|
||||
{
|
||||
/**
|
||||
* Get a token by type
|
||||
* @param string $type Token type to retrieve
|
||||
*/
|
||||
public function get($type);
|
||||
|
||||
/**
|
||||
* Set a token by type
|
||||
* @param \stdClass $token Token object to set
|
||||
* @param string $type Token type
|
||||
*/
|
||||
public function set($token, $type);
|
||||
|
||||
/**
|
||||
* Delete tokens for the current session/user
|
||||
*/
|
||||
public function delete();
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* OAuth storage handler using WordPress options
|
||||
* This can only be used if you have a WordPress environment loaded, such that the (get|update|delete)_option functions are available
|
||||
* See an example usage in http://wordpress.org/extend/plugins/updraftplus
|
||||
* @author David Anderson <david@updraftplus.com>
|
||||
* @link https://updraftplus.com
|
||||
* @package Dropbox\Oauth
|
||||
* @subpackage Storage
|
||||
*/
|
||||
|
||||
class Dropbox_WordPress implements Dropbox_StorageInterface
|
||||
{
|
||||
/**
|
||||
* Option name
|
||||
* @var string
|
||||
*/
|
||||
protected $option_name_prefix = 'dropbox_token';
|
||||
|
||||
/**
|
||||
* Option name (array storage)
|
||||
* @var string
|
||||
*/
|
||||
protected $option_array = '';
|
||||
|
||||
/**
|
||||
* Encyption object
|
||||
* @var Encrypter|null
|
||||
*/
|
||||
protected $encrypter = null;
|
||||
|
||||
/**
|
||||
* Backup module object
|
||||
* @var Backup_module_object|null
|
||||
*/
|
||||
protected $backup_module_object = null;
|
||||
|
||||
/**
|
||||
* Check if an instance of the encrypter is passed, set the encryption object
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Dropbox_Encrypter $encrypter = null, $option_name_prefix = 'dropbox_token', $option_array = 'dropbox', $backup_module_object = null)
|
||||
{
|
||||
if ($encrypter instanceof Dropbox_Encrypter) {
|
||||
$this->encrypter = $encrypter;
|
||||
}
|
||||
|
||||
if ($backup_module_object instanceof UpdraftPlus_BackupModule) {
|
||||
$this->backup_module_object = $backup_module_object;
|
||||
}
|
||||
|
||||
$this->option_name_prefix = $option_name_prefix;
|
||||
$this->option_array = $option_array;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an entry from the Dropbox options in the database
|
||||
* If the encryption object is set then decrypt the token before returning
|
||||
* @param string $type is the key to retrieve
|
||||
* @return array|bool
|
||||
*/
|
||||
public function get($type)
|
||||
{
|
||||
if ($type != 'request_token' && $type != 'access_token' && $type != 'appkey' && $type != 'CSRF' && $type != 'code') {
|
||||
throw new Dropbox_Exception("Expected a type of either 'request_token', 'access_token', 'CSRF' or 'code', got '$type'"); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- The escaping should happen when the exception is caught and printed
|
||||
} else {
|
||||
if (false !== ($opts = $this->backup_module_object->get_options())) {
|
||||
if ($type == 'request_token' || $type == 'access_token'){
|
||||
if (!empty($opts[$this->option_name_prefix.$type])) {
|
||||
$gettoken = $opts[$this->option_name_prefix.$type];
|
||||
$token = $this->decrypt($gettoken);
|
||||
return $token;
|
||||
}
|
||||
} else {
|
||||
if (!empty($opts[$type])) {
|
||||
return $opts[$type];
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value in the database by type
|
||||
* If the value is a token and the encryption object is set then encrypt the token before storing
|
||||
* @param \stdClass Token object to set
|
||||
* @param string $type Token type
|
||||
* @return void
|
||||
*/
|
||||
public function set($token, $type)
|
||||
{
|
||||
if ($type != 'request_token' && $type != 'access_token' && $type != 'upgraded' && $type != 'CSRF' && $type != 'code') {
|
||||
throw new Dropbox_Exception("Expected a type of either 'request_token', 'access_token', 'CSRF', 'upgraded' or 'code', got '$type'"); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- The escaping should happen when the exception is caught and printed
|
||||
} else {
|
||||
|
||||
$opts = $this->backup_module_object->get_options();
|
||||
|
||||
if ($type == 'access_token'){
|
||||
$token = $this->encrypt($token);
|
||||
$opts[$this->option_name_prefix.$type] = $token;
|
||||
} else if ($type == 'request_token' ) {
|
||||
$opts[$this->option_name_prefix.$type] = $token;
|
||||
} else {
|
||||
$opts[$type] = $token;
|
||||
}
|
||||
|
||||
$this->backup_module_object->set_options($opts, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a value in the database by type rather than setting to null / empty
|
||||
* set the value to null here so that when it gets to the options filter it will
|
||||
* unset the value there, this avoids a bug where if the value is not set then
|
||||
* the option filter will take the value from the database and save that version back.
|
||||
*
|
||||
* N.B. Before PHP 7.0, you can't call a method name unset()
|
||||
*
|
||||
* @param string $type Token type
|
||||
* @return void
|
||||
*/
|
||||
public function do_unset($type)
|
||||
{
|
||||
if ($type != 'request_token' && $type != 'access_token' && $type != 'upgraded' && $type != 'CSRF' && $type != 'code') {
|
||||
throw new Dropbox_Exception("Expected a type of either 'request_token', 'access_token', 'CSRF', 'upgraded' or 'code', got '$type'"); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- The escaping should happen when the exception is caught and printed
|
||||
} else {
|
||||
|
||||
$opts = $this->backup_module_object->get_options();
|
||||
|
||||
if ($type == 'access_token' || $type == 'request_token'){
|
||||
$opts[$this->option_name_prefix.$type] = null;
|
||||
} else {
|
||||
$opts[$type] = null;
|
||||
}
|
||||
$this->backup_module_object->set_options($opts, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the request and access tokens currently stored in the database
|
||||
* @return bool
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
$opts = $this->backup_module_object->get_options();
|
||||
$opts[$this->option_name_prefix.'request_token'] = null;
|
||||
$opts[$this->option_name_prefix.'access_token'] = null;
|
||||
unset($opts['ownername']);
|
||||
unset($opts['upgraded']);
|
||||
$this->backup_module_object->set_options($opts, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the Encrypter to encrypt a token and return it
|
||||
* If there is not encrypter object, return just the
|
||||
* serialized token object for storage
|
||||
* @param stdClass $token OAuth token to encrypt
|
||||
* @return stdClass|string
|
||||
*/
|
||||
protected function encrypt($token)
|
||||
{
|
||||
// Serialize the token object
|
||||
$token = serialize($token);
|
||||
|
||||
// Encrypt the token if there is an Encrypter instance
|
||||
if ($this->encrypter instanceof Dropbox_Encrypter) {
|
||||
$token = $this->encrypter->encrypt($token);
|
||||
}
|
||||
|
||||
// Return the token
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt a token using the Encrypter object and return it
|
||||
* If there is no Encrypter object, assume the token was stored
|
||||
* serialized and return the unserialized token object
|
||||
* @param stdClass $token OAuth token to encrypt
|
||||
* @return stdClass|string
|
||||
*/
|
||||
protected function decrypt($token)
|
||||
{
|
||||
// Decrypt the token if there is an Encrypter instance
|
||||
if ($this->encrypter instanceof Dropbox_Encrypter) {
|
||||
$token = $this->encrypter->decrypt($token);
|
||||
}
|
||||
|
||||
// Return the unserialized token
|
||||
return @unserialize($token);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract class for the Authentication in the API client
|
||||
* @author Chris Chabot <chabotc@google.com>
|
||||
*
|
||||
*/
|
||||
abstract class Google_Auth_Abstract
|
||||
{
|
||||
/**
|
||||
* An utility function that first calls $this->auth->sign($request) and then
|
||||
* executes makeRequest() on that signed request. Used for when a request
|
||||
* should be authenticated
|
||||
* @param Google_Http_Request $request
|
||||
* @return Google_Http_Request $request
|
||||
*/
|
||||
abstract public function authenticatedRequest(UDP_Google_Http_Request $request);
|
||||
abstract public function sign(UDP_Google_Http_Request $request);
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2014 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* WARNING - this class depends on the Google App Engine PHP library
|
||||
* which is 5.3 and above only, so if you include this in a PHP 5.2
|
||||
* setup or one without 5.3 things will blow up.
|
||||
*/
|
||||
use google\appengine\api\app_identity\AppIdentityService;
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Authentication via the Google App Engine App Identity service.
|
||||
*/
|
||||
class Google_Auth_AppIdentity extends Google_Auth_Abstract
|
||||
{
|
||||
const CACHE_PREFIX = "Google_Auth_AppIdentity::";
|
||||
private $client;
|
||||
private $token = false;
|
||||
private $tokenScopes = false;
|
||||
|
||||
public function __construct(UDP_Google_Client $client, $config = null)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an access token for the scopes supplied.
|
||||
*/
|
||||
public function authenticateForScope($scopes)
|
||||
{
|
||||
if ($this->token && $this->tokenScopes == $scopes) {
|
||||
return $this->token;
|
||||
}
|
||||
|
||||
$cacheKey = self::CACHE_PREFIX;
|
||||
if (is_string($scopes)) {
|
||||
$cacheKey .= $scopes;
|
||||
} else if (is_array($scopes)) {
|
||||
$cacheKey .= implode(":", $scopes);
|
||||
}
|
||||
|
||||
$this->token = $this->client->getCache()->get($cacheKey);
|
||||
if (!$this->token) {
|
||||
$this->retrieveToken($scopes, $cacheKey);
|
||||
} else if ($this->token['expiration_time'] < time()) {
|
||||
$this->client->getCache()->delete($cacheKey);
|
||||
$this->retrieveToken($scopes, $cacheKey);
|
||||
}
|
||||
|
||||
$this->tokenScopes = $scopes;
|
||||
return $this->token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a new access token and store it in cache
|
||||
* @param mixed $scopes
|
||||
* @param string $cacheKey
|
||||
*/
|
||||
private function retrieveToken($scopes, $cacheKey)
|
||||
{
|
||||
$this->token = AppIdentityService::getAccessToken($scopes);
|
||||
if ($this->token) {
|
||||
$this->client->getCache()->set(
|
||||
$cacheKey,
|
||||
$this->token
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an authenticated / signed apiHttpRequest.
|
||||
* This function takes the apiHttpRequest, calls apiAuth->sign on it
|
||||
* (which can modify the request in what ever way fits the auth mechanism)
|
||||
* and then calls apiCurlIO::makeRequest on the signed request
|
||||
*
|
||||
* @param Google_Http_Request $request
|
||||
* @return Google_Http_Request The resulting HTTP response including the
|
||||
* responseHttpCode, responseHeaders and responseBody.
|
||||
*/
|
||||
public function authenticatedRequest(UDP_Google_Http_Request $request)
|
||||
{
|
||||
$request = $this->sign($request);
|
||||
return $this->client->getIo()->makeRequest($request);
|
||||
}
|
||||
|
||||
public function sign(UDP_Google_Http_Request $request)
|
||||
{
|
||||
if (!$this->token) {
|
||||
// No token, so nothing to do.
|
||||
return $request;
|
||||
}
|
||||
|
||||
$this->client->getLogger()->debug('App Identity authentication');
|
||||
|
||||
// Add the OAuth2 header to the request
|
||||
$request->setRequestHeaders(
|
||||
array('Authorization' => 'Bearer ' . $this->token['access_token'])
|
||||
);
|
||||
|
||||
return $request;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Credentials object used for OAuth 2.0 Signed JWT assertion grants.
|
||||
*/
|
||||
class Google_Auth_AssertionCredentials
|
||||
{
|
||||
const MAX_TOKEN_LIFETIME_SECS = 3600;
|
||||
|
||||
public $serviceAccountName;
|
||||
public $scopes;
|
||||
public $privateKey;
|
||||
public $privateKeyPassword;
|
||||
public $assertionType;
|
||||
public $sub;
|
||||
/**
|
||||
* @deprecated
|
||||
* @link http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-06
|
||||
*/
|
||||
public $prn;
|
||||
private $useCache;
|
||||
|
||||
/**
|
||||
* @param $serviceAccountName
|
||||
* @param $scopes array List of scopes
|
||||
* @param $privateKey
|
||||
* @param string $privateKeyPassword
|
||||
* @param string $assertionType
|
||||
* @param bool|string $sub The email address of the user for which the
|
||||
* application is requesting delegated access.
|
||||
* @param bool useCache Whether to generate a cache key and allow
|
||||
* automatic caching of the generated token.
|
||||
*/
|
||||
public function __construct(
|
||||
$serviceAccountName,
|
||||
$scopes,
|
||||
$privateKey,
|
||||
$privateKeyPassword = 'notasecret',
|
||||
$assertionType = 'http://oauth.net/grant_type/jwt/1.0/bearer',
|
||||
$sub = false,
|
||||
$useCache = true
|
||||
) {
|
||||
$this->serviceAccountName = $serviceAccountName;
|
||||
$this->scopes = is_string($scopes) ? $scopes : implode(' ', $scopes);
|
||||
$this->privateKey = $privateKey;
|
||||
$this->privateKeyPassword = $privateKeyPassword;
|
||||
$this->assertionType = $assertionType;
|
||||
$this->sub = $sub;
|
||||
$this->prn = $sub;
|
||||
$this->useCache = $useCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a unique key to represent this credential.
|
||||
* @return string
|
||||
*/
|
||||
public function getCacheKey()
|
||||
{
|
||||
if (!$this->useCache) {
|
||||
return false;
|
||||
}
|
||||
$h = $this->sub;
|
||||
$h .= $this->assertionType;
|
||||
$h .= $this->privateKey;
|
||||
$h .= $this->scopes;
|
||||
$h .= $this->serviceAccountName;
|
||||
return md5($h);
|
||||
}
|
||||
|
||||
public function generateAssertion()
|
||||
{
|
||||
$now = time();
|
||||
|
||||
$jwtParams = array(
|
||||
'aud' => Google_Auth_OAuth2::OAUTH2_TOKEN_URI,
|
||||
'scope' => $this->scopes,
|
||||
'iat' => $now,
|
||||
'exp' => $now + self::MAX_TOKEN_LIFETIME_SECS,
|
||||
'iss' => $this->serviceAccountName,
|
||||
);
|
||||
|
||||
if ($this->sub !== false) {
|
||||
$jwtParams['sub'] = $this->sub;
|
||||
} else if ($this->prn !== false) {
|
||||
$jwtParams['prn'] = $this->prn;
|
||||
}
|
||||
|
||||
return $this->makeSignedJwt($jwtParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a signed JWT.
|
||||
* @param array $payload
|
||||
* @return string The signed JWT.
|
||||
*/
|
||||
private function makeSignedJwt($payload)
|
||||
{
|
||||
$header = array('typ' => 'JWT', 'alg' => 'RS256');
|
||||
|
||||
$payload = json_encode($payload);
|
||||
// Handle some overzealous escaping in PHP json that seemed to cause some errors
|
||||
// with claimsets.
|
||||
$payload = str_replace('\/', '/', $payload);
|
||||
|
||||
$segments = array(
|
||||
Google_Utils::urlSafeB64Encode(json_encode($header)),
|
||||
Google_Utils::urlSafeB64Encode($payload)
|
||||
);
|
||||
|
||||
$signingInput = implode('.', $segments);
|
||||
$signer = new Google_Signer_P12($this->privateKey, $this->privateKeyPassword);
|
||||
$signature = $signer->sign($signingInput);
|
||||
$segments[] = Google_Utils::urlSafeB64Encode($signature);
|
||||
|
||||
return implode(".", $segments);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2014 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Authentication via built-in Compute Engine service accounts.
|
||||
* The instance must be pre-configured with a service account
|
||||
* and the appropriate scopes.
|
||||
* @author Jonathan Parrott <jon.wayne.parrott@gmail.com>
|
||||
*/
|
||||
class Google_Auth_ComputeEngine extends Google_Auth_Abstract
|
||||
{
|
||||
const METADATA_AUTH_URL =
|
||||
'http://metadata/computeMetadata/v1/instance/service-accounts/default/token';
|
||||
private $client;
|
||||
private $token;
|
||||
|
||||
public function __construct(UDP_Google_Client $client, $config = null)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an authenticated / signed apiHttpRequest.
|
||||
* This function takes the apiHttpRequest, calls apiAuth->sign on it
|
||||
* (which can modify the request in what ever way fits the auth mechanism)
|
||||
* and then calls apiCurlIO::makeRequest on the signed request
|
||||
*
|
||||
* @param Google_Http_Request $request
|
||||
* @return Google_Http_Request The resulting HTTP response including the
|
||||
* responseHttpCode, responseHeaders and responseBody.
|
||||
*/
|
||||
public function authenticatedRequest(UDP_Google_Http_Request $request)
|
||||
{
|
||||
$request = $this->sign($request);
|
||||
return $this->client->getIo()->makeRequest($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $token
|
||||
* @throws Google_Auth_Exception
|
||||
*/
|
||||
public function setAccessToken($token)
|
||||
{
|
||||
$token = json_decode($token, true);
|
||||
if ($token == null) {
|
||||
throw new Google_Auth_Exception('Could not json decode the token');
|
||||
}
|
||||
if (! isset($token['access_token'])) {
|
||||
throw new Google_Auth_Exception("Invalid token format");
|
||||
}
|
||||
$token['created'] = time();
|
||||
$this->token = $token;
|
||||
}
|
||||
|
||||
public function getAccessToken()
|
||||
{
|
||||
return json_encode($this->token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquires a new access token from the compute engine metadata server.
|
||||
* @throws Google_Auth_Exception
|
||||
*/
|
||||
public function acquireAccessToken()
|
||||
{
|
||||
$request = new UDP_Google_Http_Request(
|
||||
self::METADATA_AUTH_URL,
|
||||
'GET',
|
||||
array(
|
||||
'Metadata-Flavor' => 'Google'
|
||||
)
|
||||
);
|
||||
$request->disableGzip();
|
||||
$response = $this->client->getIo()->makeRequest($request);
|
||||
|
||||
if ($response->getResponseHttpCode() == 200) {
|
||||
$this->setAccessToken($response->getResponseBody());
|
||||
$this->token['created'] = time();
|
||||
return $this->getAccessToken();
|
||||
} else {
|
||||
throw new Google_Auth_Exception(
|
||||
sprintf(
|
||||
"Error fetching service account access token, message: '%s'",
|
||||
$response->getResponseBody() // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error messages should be escaped when caught and printed.
|
||||
),
|
||||
$response->getResponseHttpCode() // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- HTTP code should be escaped when caught and printed.
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Include an accessToken in a given apiHttpRequest.
|
||||
* @param Google_Http_Request $request
|
||||
* @return Google_Http_Request
|
||||
* @throws Google_Auth_Exception
|
||||
*/
|
||||
public function sign(UDP_Google_Http_Request $request)
|
||||
{
|
||||
if ($this->isAccessTokenExpired()) {
|
||||
$this->acquireAccessToken();
|
||||
}
|
||||
|
||||
$this->client->getLogger()->debug('Compute engine service account authentication');
|
||||
|
||||
$request->setRequestHeaders(
|
||||
array('Authorization' => 'Bearer ' . $this->token['access_token'])
|
||||
);
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the access_token is expired.
|
||||
* @return bool Returns True if the access_token is expired.
|
||||
*/
|
||||
public function isAccessTokenExpired()
|
||||
{
|
||||
if (!$this->token || !isset($this->token['created'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the token is set to expire in the next 30 seconds.
|
||||
$expired = ($this->token['created']
|
||||
+ ($this->token['expires_in'] - 30)) < time();
|
||||
|
||||
return $expired;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
class Google_Auth_Exception extends Google_Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to hold information about an authenticated login.
|
||||
*
|
||||
* @author Brian Eaton <beaton@google.com>
|
||||
*/
|
||||
class Google_Auth_LoginTicket
|
||||
{
|
||||
const USER_ATTR = "sub";
|
||||
|
||||
// Information from id token envelope.
|
||||
private $envelope;
|
||||
|
||||
// Information from id token payload.
|
||||
private $payload;
|
||||
|
||||
/**
|
||||
* Creates a user based on the supplied token.
|
||||
*
|
||||
* @param string $envelope Header from a verified authentication token.
|
||||
* @param string $payload Information from a verified authentication token.
|
||||
*/
|
||||
public function __construct($envelope, $payload)
|
||||
{
|
||||
$this->envelope = $envelope;
|
||||
$this->payload = $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the numeric identifier for the user.
|
||||
* @throws Google_Auth_Exception
|
||||
* @return
|
||||
*/
|
||||
public function getUserId()
|
||||
{
|
||||
if (array_key_exists(self::USER_ATTR, $this->payload)) {
|
||||
return $this->payload[self::USER_ATTR];
|
||||
}
|
||||
throw new Google_Auth_Exception("No user_id in token");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns attributes from the login ticket. This can contain
|
||||
* various information about the user session.
|
||||
* @return array
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array("envelope" => $this->envelope, "payload" => $this->payload);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,634 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2008 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Authentication class that deals with the OAuth 2 web-server authentication flow
|
||||
*
|
||||
*/
|
||||
class Google_Auth_OAuth2 extends Google_Auth_Abstract
|
||||
{
|
||||
const OAUTH2_REVOKE_URI = 'https://accounts.google.com/o/oauth2/revoke';
|
||||
const OAUTH2_TOKEN_URI = 'https://accounts.google.com/o/oauth2/token';
|
||||
const OAUTH2_AUTH_URL = 'https://accounts.google.com/o/oauth2/auth';
|
||||
const CLOCK_SKEW_SECS = 300; // five minutes in seconds
|
||||
const AUTH_TOKEN_LIFETIME_SECS = 300; // five minutes in seconds
|
||||
const MAX_TOKEN_LIFETIME_SECS = 86400; // one day in seconds
|
||||
const OAUTH2_ISSUER = 'accounts.google.com';
|
||||
|
||||
/** @var Google_Auth_AssertionCredentials $assertionCredentials */
|
||||
private $assertionCredentials;
|
||||
|
||||
/**
|
||||
* @var string The state parameters for CSRF and other forgery protection.
|
||||
*/
|
||||
private $state;
|
||||
|
||||
/**
|
||||
* @var array The token bundle.
|
||||
*/
|
||||
private $token = array();
|
||||
|
||||
/**
|
||||
* @var UDP_Google_Client the base client
|
||||
*/
|
||||
private $client;
|
||||
|
||||
/**
|
||||
* Instantiates the class, but does not initiate the login flow, leaving it
|
||||
* to the discretion of the caller.
|
||||
*/
|
||||
public function __construct(UDP_Google_Client $client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an authenticated / signed apiHttpRequest.
|
||||
* This function takes the apiHttpRequest, calls apiAuth->sign on it
|
||||
* (which can modify the request in what ever way fits the auth mechanism)
|
||||
* and then calls apiCurlIO::makeRequest on the signed request
|
||||
*
|
||||
* @param Google_Http_Request $request
|
||||
* @return Google_Http_Request The resulting HTTP response including the
|
||||
* responseHttpCode, responseHeaders and responseBody.
|
||||
*/
|
||||
public function authenticatedRequest(UDP_Google_Http_Request $request)
|
||||
{
|
||||
$request = $this->sign($request);
|
||||
return $this->client->getIo()->makeRequest($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $code
|
||||
* @throws Google_Auth_Exception
|
||||
* @return string
|
||||
*/
|
||||
public function authenticate($code)
|
||||
{
|
||||
if (strlen($code) == 0) {
|
||||
throw new Google_Auth_Exception("Invalid code");
|
||||
}
|
||||
|
||||
// We got here from the redirect from a successful authorization grant,
|
||||
// fetch the access token
|
||||
$request = new UDP_Google_Http_Request(
|
||||
self::OAUTH2_TOKEN_URI,
|
||||
'POST',
|
||||
array(),
|
||||
array(
|
||||
'code' => $code,
|
||||
'grant_type' => 'authorization_code',
|
||||
'redirect_uri' => $this->client->getClassConfig($this, 'redirect_uri'),
|
||||
'client_id' => $this->client->getClassConfig($this, 'client_id'),
|
||||
'client_secret' => $this->client->getClassConfig($this, 'client_secret')
|
||||
)
|
||||
);
|
||||
$request->disableGzip();
|
||||
$response = $this->client->getIo()->makeRequest($request);
|
||||
|
||||
if ($response->getResponseHttpCode() == 200) {
|
||||
$this->setAccessToken($response->getResponseBody());
|
||||
$this->token['created'] = time();
|
||||
return $this->getAccessToken();
|
||||
} else {
|
||||
$decodedResponse = json_decode($response->getResponseBody(), true);
|
||||
if ($decodedResponse != null && $decodedResponse['error']) {
|
||||
$errorText = $decodedResponse['error'];
|
||||
if (isset($decodedResponse['error_description'])) {
|
||||
$errorText .= ": " . $decodedResponse['error_description'];
|
||||
}
|
||||
}
|
||||
throw new Google_Auth_Exception(
|
||||
sprintf(
|
||||
"Error fetching OAuth2 access token, message: '%s'",
|
||||
$errorText
|
||||
),
|
||||
$response->getResponseHttpCode()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a URL to obtain user authorization.
|
||||
* The authorization endpoint allows the user to first
|
||||
* authenticate, and then grant/deny the access request.
|
||||
* @param string $scope The scope is expressed as a list of space-delimited strings.
|
||||
* @return string
|
||||
*/
|
||||
public function createAuthUrl($scope)
|
||||
{
|
||||
$params = array(
|
||||
'response_type' => 'code',
|
||||
'redirect_uri' => $this->client->getClassConfig($this, 'redirect_uri'),
|
||||
'client_id' => $this->client->getClassConfig($this, 'client_id'),
|
||||
'scope' => $scope,
|
||||
'access_type' => $this->client->getClassConfig($this, 'access_type'),
|
||||
);
|
||||
|
||||
// Prefer prompt to approval prompt.
|
||||
if ($this->client->getClassConfig($this, 'prompt')) {
|
||||
$params = $this->maybeAddParam($params, 'prompt');
|
||||
} else {
|
||||
$params = $this->maybeAddParam($params, 'approval_prompt');
|
||||
}
|
||||
$params = $this->maybeAddParam($params, 'login_hint');
|
||||
$params = $this->maybeAddParam($params, 'hd');
|
||||
$params = $this->maybeAddParam($params, 'openid.realm');
|
||||
$params = $this->maybeAddParam($params, 'include_granted_scopes');
|
||||
|
||||
// If the list of scopes contains plus.login, add request_visible_actions
|
||||
// to auth URL.
|
||||
$rva = $this->client->getClassConfig($this, 'request_visible_actions');
|
||||
if (strpos($scope, 'plus.login') && strlen($rva) > 0) {
|
||||
$params['request_visible_actions'] = $rva;
|
||||
}
|
||||
|
||||
if (isset($this->state)) {
|
||||
$params['state'] = $this->state;
|
||||
}
|
||||
|
||||
return self::OAUTH2_AUTH_URL . "?" . http_build_query($params, '', '&');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $token
|
||||
* @throws Google_Auth_Exception
|
||||
*/
|
||||
public function setAccessToken($token)
|
||||
{
|
||||
$token = json_decode($token, true);
|
||||
if ($token == null) {
|
||||
throw new Google_Auth_Exception('Could not json decode the token');
|
||||
}
|
||||
if (! isset($token['access_token'])) {
|
||||
throw new Google_Auth_Exception("Invalid token format");
|
||||
}
|
||||
$this->token = $token;
|
||||
}
|
||||
|
||||
public function getAccessToken()
|
||||
{
|
||||
return json_encode($this->token);
|
||||
}
|
||||
|
||||
public function getRefreshToken()
|
||||
{
|
||||
if (array_key_exists('refresh_token', $this->token)) {
|
||||
return $this->token['refresh_token'];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function setState($state)
|
||||
{
|
||||
$this->state = $state;
|
||||
}
|
||||
|
||||
public function setAssertionCredentials(Google_Auth_AssertionCredentials $creds)
|
||||
{
|
||||
$this->assertionCredentials = $creds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include an accessToken in a given apiHttpRequest.
|
||||
* @param Google_Http_Request $request
|
||||
* @return Google_Http_Request
|
||||
* @throws Google_Auth_Exception
|
||||
*/
|
||||
public function sign(UDP_Google_Http_Request $request)
|
||||
{
|
||||
// add the developer key to the request before signing it
|
||||
if ($this->client->getClassConfig($this, 'developer_key')) {
|
||||
$request->setQueryParam('key', $this->client->getClassConfig($this, 'developer_key'));
|
||||
}
|
||||
|
||||
// Cannot sign the request without an OAuth access token.
|
||||
if (null == $this->token && null == $this->assertionCredentials) {
|
||||
return $request;
|
||||
}
|
||||
|
||||
// Check if the token is set to expire in the next 30 seconds
|
||||
// (or has already expired).
|
||||
if ($this->isAccessTokenExpired()) {
|
||||
if ($this->assertionCredentials) {
|
||||
$this->refreshTokenWithAssertion();
|
||||
} else {
|
||||
$this->client->getLogger()->debug('OAuth2 access token expired');
|
||||
if (! array_key_exists('refresh_token', $this->token)) {
|
||||
$error = "The OAuth 2.0 access token has expired,"
|
||||
." and a refresh token is not available. Refresh tokens"
|
||||
." are not returned for responses that were auto-approved.";
|
||||
|
||||
$this->client->getLogger()->error($error);
|
||||
throw new Google_Auth_Exception($error);
|
||||
}
|
||||
$this->refreshToken($this->token['refresh_token']);
|
||||
}
|
||||
}
|
||||
|
||||
$this->client->getLogger()->debug('OAuth2 authentication');
|
||||
|
||||
// Add the OAuth2 header to the request
|
||||
$request->setRequestHeaders(
|
||||
array('Authorization' => 'Bearer ' . $this->token['access_token'])
|
||||
);
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a fresh access token with the given refresh token.
|
||||
* @param string $refreshToken
|
||||
* @return void
|
||||
*/
|
||||
public function refreshToken($refreshToken)
|
||||
{
|
||||
$this->refreshTokenRequest(
|
||||
array(
|
||||
'client_id' => $this->client->getClassConfig($this, 'client_id'),
|
||||
'client_secret' => $this->client->getClassConfig($this, 'client_secret'),
|
||||
'refresh_token' => $refreshToken,
|
||||
'grant_type' => 'refresh_token'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a fresh access token with a given assertion token.
|
||||
* @param Google_Auth_AssertionCredentials $assertionCredentials optional.
|
||||
* @return void
|
||||
*/
|
||||
public function refreshTokenWithAssertion($assertionCredentials = null)
|
||||
{
|
||||
if (!$assertionCredentials) {
|
||||
$assertionCredentials = $this->assertionCredentials;
|
||||
}
|
||||
|
||||
$cacheKey = $assertionCredentials->getCacheKey();
|
||||
|
||||
if ($cacheKey) {
|
||||
// We can check whether we have a token available in the
|
||||
// cache. If it is expired, we can retrieve a new one from
|
||||
// the assertion.
|
||||
$token = $this->client->getCache()->get($cacheKey);
|
||||
if ($token) {
|
||||
$this->setAccessToken($token);
|
||||
}
|
||||
if (!$this->isAccessTokenExpired()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->client->getLogger()->debug('OAuth2 access token expired');
|
||||
$this->refreshTokenRequest(
|
||||
array(
|
||||
'grant_type' => 'assertion',
|
||||
'assertion_type' => $assertionCredentials->assertionType,
|
||||
'assertion' => $assertionCredentials->generateAssertion(),
|
||||
)
|
||||
);
|
||||
|
||||
if ($cacheKey) {
|
||||
// Attempt to cache the token.
|
||||
$this->client->getCache()->set(
|
||||
$cacheKey,
|
||||
$this->getAccessToken()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function refreshTokenRequest($params)
|
||||
{
|
||||
if (isset($params['assertion'])) {
|
||||
$this->client->getLogger()->info(
|
||||
'OAuth2 access token refresh with Signed JWT assertion grants.'
|
||||
);
|
||||
} else {
|
||||
$this->client->getLogger()->info('OAuth2 access token refresh');
|
||||
}
|
||||
|
||||
$http = new UDP_Google_Http_Request(
|
||||
self::OAUTH2_TOKEN_URI,
|
||||
'POST',
|
||||
array(),
|
||||
$params
|
||||
);
|
||||
$http->disableGzip();
|
||||
$request = $this->client->getIo()->makeRequest($http);
|
||||
|
||||
$code = $request->getResponseHttpCode();
|
||||
$body = $request->getResponseBody();
|
||||
if (200 == $code) {
|
||||
$token = json_decode($body, true);
|
||||
if ($token == null) {
|
||||
throw new Google_Auth_Exception("Could not json decode the access token");
|
||||
}
|
||||
|
||||
if (! isset($token['access_token']) || ! isset($token['expires_in'])) {
|
||||
throw new Google_Auth_Exception("Invalid token format");
|
||||
}
|
||||
|
||||
if (isset($token['id_token'])) {
|
||||
$this->token['id_token'] = $token['id_token'];
|
||||
}
|
||||
$this->token['access_token'] = $token['access_token'];
|
||||
$this->token['expires_in'] = $token['expires_in'];
|
||||
$this->token['created'] = time();
|
||||
} else {
|
||||
throw new Google_Auth_Exception("Error refreshing the OAuth2 token, message: '$body'", $code);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke an OAuth2 access token or refresh token. This method will revoke the current access
|
||||
* token, if a token isn't provided.
|
||||
* @throws Google_Auth_Exception
|
||||
* @param string|null $token The token (access token or a refresh token) that should be revoked.
|
||||
* @return boolean Returns True if the revocation was successful, otherwise False.
|
||||
*/
|
||||
public function revokeToken($token = null)
|
||||
{
|
||||
if (!$token) {
|
||||
if (!$this->token) {
|
||||
// Not initialized, no token to actually revoke
|
||||
return false;
|
||||
} elseif (array_key_exists('refresh_token', $this->token)) {
|
||||
$token = $this->token['refresh_token'];
|
||||
} else {
|
||||
$token = $this->token['access_token'];
|
||||
}
|
||||
}
|
||||
$request = new UDP_Google_Http_Request(
|
||||
self::OAUTH2_REVOKE_URI,
|
||||
'POST',
|
||||
array(),
|
||||
"token=$token"
|
||||
);
|
||||
$request->disableGzip();
|
||||
$response = $this->client->getIo()->makeRequest($request);
|
||||
$code = $response->getResponseHttpCode();
|
||||
if ($code == 200) {
|
||||
$this->token = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the access_token is expired.
|
||||
* @return bool Returns True if the access_token is expired.
|
||||
*/
|
||||
public function isAccessTokenExpired()
|
||||
{
|
||||
if (!$this->token || !isset($this->token['created'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the token is set to expire in the next 30 seconds.
|
||||
$expired = ($this->token['created']
|
||||
+ ($this->token['expires_in'] - 30)) < time();
|
||||
|
||||
return $expired;
|
||||
}
|
||||
|
||||
// Gets federated sign-on certificates to use for verifying identity tokens.
|
||||
// Returns certs as array structure, where keys are key ids, and values
|
||||
// are PEM encoded certificates.
|
||||
private function getFederatedSignOnCerts()
|
||||
{
|
||||
return $this->retrieveCertsFromLocation(
|
||||
$this->client->getClassConfig($this, 'federated_signon_certs_url')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve and cache a certificates file.
|
||||
*
|
||||
* @param $url string location
|
||||
* @throws Google_Auth_Exception
|
||||
* @return array certificates
|
||||
*/
|
||||
public function retrieveCertsFromLocation($url)
|
||||
{
|
||||
// If we're retrieving a local file, just grab it.
|
||||
if ("http" != substr($url, 0, 4)) {
|
||||
$file = file_get_contents($url);
|
||||
if ($file) {
|
||||
return json_decode($file, true);
|
||||
} else {
|
||||
throw new Google_Auth_Exception(
|
||||
"Failed to retrieve verification certificates: '" .
|
||||
$url . "'."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// This relies on makeRequest caching certificate responses.
|
||||
$request = $this->client->getIo()->makeRequest(
|
||||
new UDP_Google_Http_Request(
|
||||
$url
|
||||
)
|
||||
);
|
||||
if ($request->getResponseHttpCode() == 200) {
|
||||
$certs = json_decode($request->getResponseBody(), true);
|
||||
if ($certs) {
|
||||
return $certs;
|
||||
}
|
||||
}
|
||||
throw new Google_Auth_Exception(
|
||||
"Failed to retrieve verification certificates: '" .
|
||||
$request->getResponseBody() . "'.",
|
||||
$request->getResponseHttpCode()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies an id token and returns the authenticated apiLoginTicket.
|
||||
* Throws an exception if the id token is not valid.
|
||||
* The audience parameter can be used to control which id tokens are
|
||||
* accepted. By default, the id token must have been issued to this OAuth2 client.
|
||||
*
|
||||
* @param $id_token
|
||||
* @param $audience
|
||||
* @return Google_Auth_LoginTicket
|
||||
*/
|
||||
public function verifyIdToken($id_token = null, $audience = null)
|
||||
{
|
||||
if (!$id_token) {
|
||||
$id_token = $this->token['id_token'];
|
||||
}
|
||||
$certs = $this->getFederatedSignonCerts();
|
||||
if (!$audience) {
|
||||
$audience = $this->client->getClassConfig($this, 'client_id');
|
||||
}
|
||||
|
||||
return $this->verifySignedJwtWithCerts($id_token, $certs, $audience, self::OAUTH2_ISSUER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the id token, returns the verified token contents.
|
||||
*
|
||||
* @param $jwt string the token
|
||||
* @param $certs array of certificates
|
||||
* @param $required_audience string the expected consumer of the token
|
||||
* @param [$issuer] the expected issues, defaults to Google
|
||||
* @param [$max_expiry] the max lifetime of a token, defaults to MAX_TOKEN_LIFETIME_SECS
|
||||
* @throws Google_Auth_Exception
|
||||
* @return mixed token information if valid, false if not
|
||||
*/
|
||||
public function verifySignedJwtWithCerts(
|
||||
$jwt,
|
||||
$certs,
|
||||
$required_audience,
|
||||
$issuer = null,
|
||||
$max_expiry = null
|
||||
) {
|
||||
if (!$max_expiry) {
|
||||
// Set the maximum time we will accept a token for.
|
||||
$max_expiry = self::MAX_TOKEN_LIFETIME_SECS;
|
||||
}
|
||||
|
||||
$segments = explode(".", $jwt);
|
||||
if (count($segments) != 3) {
|
||||
throw new Google_Auth_Exception("Wrong number of segments in token: $jwt");
|
||||
}
|
||||
$signed = $segments[0] . "." . $segments[1];
|
||||
$signature = Google_Utils::urlSafeB64Decode($segments[2]);
|
||||
|
||||
// Parse envelope.
|
||||
$envelope = json_decode(Google_Utils::urlSafeB64Decode($segments[0]), true);
|
||||
if (!$envelope) {
|
||||
throw new Google_Auth_Exception("Can't parse token envelope: " . $segments[0]);
|
||||
}
|
||||
|
||||
// Parse token
|
||||
$json_body = Google_Utils::urlSafeB64Decode($segments[1]);
|
||||
$payload = json_decode($json_body, true);
|
||||
if (!$payload) {
|
||||
throw new Google_Auth_Exception("Can't parse token payload: " . $segments[1]);
|
||||
}
|
||||
|
||||
// Check signature
|
||||
$verified = false;
|
||||
foreach ($certs as $keyName => $pem) {
|
||||
$public_key = new Google_Verifier_Pem($pem);
|
||||
if ($public_key->verify($signed, $signature)) {
|
||||
$verified = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$verified) {
|
||||
throw new Google_Auth_Exception("Invalid token signature: $jwt");
|
||||
}
|
||||
|
||||
// Check issued-at timestamp
|
||||
$iat = 0;
|
||||
if (array_key_exists("iat", $payload)) {
|
||||
$iat = $payload["iat"];
|
||||
}
|
||||
if (!$iat) {
|
||||
throw new Google_Auth_Exception("No issue time in token: $json_body");
|
||||
}
|
||||
$earliest = $iat - self::CLOCK_SKEW_SECS;
|
||||
|
||||
// Check expiration timestamp
|
||||
$now = time();
|
||||
$exp = 0;
|
||||
if (array_key_exists("exp", $payload)) {
|
||||
$exp = $payload["exp"];
|
||||
}
|
||||
if (!$exp) {
|
||||
throw new Google_Auth_Exception("No expiration time in token: $json_body");
|
||||
}
|
||||
if ($exp >= $now + $max_expiry) {
|
||||
throw new Google_Auth_Exception(
|
||||
sprintf("Expiration time too far in future: %s", $json_body)
|
||||
);
|
||||
}
|
||||
|
||||
$latest = $exp + self::CLOCK_SKEW_SECS;
|
||||
if ($now < $earliest) {
|
||||
throw new Google_Auth_Exception(
|
||||
sprintf(
|
||||
"Token used too early, %s < %s: %s",
|
||||
$now,
|
||||
$earliest,
|
||||
$json_body
|
||||
)
|
||||
);
|
||||
}
|
||||
if ($now > $latest) {
|
||||
throw new Google_Auth_Exception(
|
||||
sprintf(
|
||||
"Token used too late, %s > %s: %s",
|
||||
$now,
|
||||
$latest,
|
||||
$json_body
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$iss = $payload['iss'];
|
||||
if ($issuer && $iss != $issuer) {
|
||||
throw new Google_Auth_Exception(
|
||||
sprintf(
|
||||
"Invalid issuer, %s != %s: %s",
|
||||
$iss,
|
||||
$issuer,
|
||||
$json_body
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Check audience
|
||||
$aud = $payload["aud"];
|
||||
if ($aud != $required_audience) {
|
||||
throw new Google_Auth_Exception(
|
||||
sprintf(
|
||||
"Wrong recipient, %s != %s:",
|
||||
$aud,
|
||||
$required_audience,
|
||||
$json_body
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// All good.
|
||||
return new Google_Auth_LoginTicket($envelope, $payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a parameter to the auth params if not empty string.
|
||||
*/
|
||||
private function maybeAddParam($params, $name)
|
||||
{
|
||||
$param = $this->client->getClassConfig($this, $name);
|
||||
if ($param != '') {
|
||||
$params[$name] = $param;
|
||||
}
|
||||
return $params;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple API access implementation. Can either be used to make requests
|
||||
* completely unauthenticated, or by using a Simple API Access developer
|
||||
* key.
|
||||
*/
|
||||
class Google_Auth_Simple extends Google_Auth_Abstract
|
||||
{
|
||||
private $client;
|
||||
|
||||
public function __construct(UDP_Google_Client $client, $config = null)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an authenticated / signed apiHttpRequest.
|
||||
* This function takes the apiHttpRequest, calls apiAuth->sign on it
|
||||
* (which can modify the request in what ever way fits the auth mechanism)
|
||||
* and then calls apiCurlIO::makeRequest on the signed request
|
||||
*
|
||||
* @param Google_Http_Request $request
|
||||
* @return Google_Http_Request The resulting HTTP response including the
|
||||
* responseHttpCode, responseHeaders and responseBody.
|
||||
*/
|
||||
public function authenticatedRequest(UDP_Google_Http_Request $request)
|
||||
{
|
||||
$request = $this->sign($request);
|
||||
return $this->io->makeRequest($request);
|
||||
}
|
||||
|
||||
public function sign(UDP_Google_Http_Request $request)
|
||||
{
|
||||
$key = $this->client->getClassConfig($this, 'developer_key');
|
||||
if ($key) {
|
||||
$this->client->getLogger()->debug(
|
||||
'Simple API Access developer key authentication'
|
||||
);
|
||||
$request->setQueryParam('key', $key);
|
||||
}
|
||||
return $request;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2008 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Abstract storage class
|
||||
*
|
||||
* @author Chris Chabot <chabotc@google.com>
|
||||
*/
|
||||
abstract class Google_Cache_Abstract
|
||||
{
|
||||
|
||||
abstract public function __construct(UDP_Google_Client $client);
|
||||
|
||||
/**
|
||||
* Retrieves the data for the given key, or false if they
|
||||
* key is unknown or expired
|
||||
*
|
||||
* @param String $key The key who's data to retrieve
|
||||
* @param boolean|int $expiration Expiration time in seconds
|
||||
*
|
||||
*/
|
||||
abstract public function get($key, $expiration = false);
|
||||
|
||||
/**
|
||||
* Store the key => $value set. The $value is serialized
|
||||
* by this function so can be of any type
|
||||
*
|
||||
* @param string $key Key of the data
|
||||
* @param string $value data
|
||||
*/
|
||||
abstract public function set($key, $value);
|
||||
|
||||
/**
|
||||
* Removes the key/data pair for the given $key
|
||||
*
|
||||
* @param String $key
|
||||
*/
|
||||
abstract public function delete($key);
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* A persistent storage class based on the APC cache, which is not
|
||||
* really very persistent, as soon as you restart your web server
|
||||
* the storage will be wiped, however for debugging and/or speed
|
||||
* it can be useful, and cache is a lot cheaper then storage.
|
||||
*
|
||||
* @author Chris Chabot <chabotc@google.com>
|
||||
*/
|
||||
class Google_Cache_Apc extends Google_Cache_Abstract
|
||||
{
|
||||
/**
|
||||
* @var Google_Client the current client
|
||||
*/
|
||||
private $client;
|
||||
|
||||
public function __construct(UDP_Google_Client $client)
|
||||
{
|
||||
if (! function_exists('apc_add') ) {
|
||||
$error = "Apc functions not available";
|
||||
|
||||
$client->getLogger()->error($error);
|
||||
throw new Google_Cache_Exception($error); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
|
||||
}
|
||||
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function get($key, $expiration = false)
|
||||
{
|
||||
$ret = apc_fetch($key);
|
||||
if ($ret === false) {
|
||||
$this->client->getLogger()->debug(
|
||||
'APC cache miss',
|
||||
array('key' => $key)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (is_numeric($expiration) && (time() - $ret['time'] > $expiration)) {
|
||||
$this->client->getLogger()->debug(
|
||||
'APC cache miss (expired)',
|
||||
array('key' => $key, 'var' => $ret)
|
||||
);
|
||||
$this->delete($key);
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->client->getLogger()->debug(
|
||||
'APC cache hit',
|
||||
array('key' => $key, 'var' => $ret)
|
||||
);
|
||||
|
||||
return $ret['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function set($key, $value)
|
||||
{
|
||||
$var = array('time' => time(), 'data' => $value);
|
||||
$rc = apc_store($key, $var);
|
||||
|
||||
if ($rc == false) {
|
||||
$this->client->getLogger()->error(
|
||||
'APC cache set failed',
|
||||
array('key' => $key, 'var' => $var)
|
||||
);
|
||||
throw new Google_Cache_Exception("Couldn't store data");
|
||||
}
|
||||
|
||||
$this->client->getLogger()->debug(
|
||||
'APC cache set',
|
||||
array('key' => $key, 'var' => $var)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @param String $key
|
||||
*/
|
||||
public function delete($key)
|
||||
{
|
||||
$this->client->getLogger()->debug(
|
||||
'APC cache delete',
|
||||
array('key' => $key)
|
||||
);
|
||||
apc_delete($key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
class Google_Cache_Exception extends Google_Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2008 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
/*
|
||||
* This class implements a basic on disk storage. While that does
|
||||
* work quite well it's not the most elegant and scalable solution.
|
||||
* It will also get you into a heap of trouble when you try to run
|
||||
* this in a clustered environment.
|
||||
*
|
||||
* @author Chris Chabot <chabotc@google.com>
|
||||
*/
|
||||
class Google_Cache_File extends Google_Cache_Abstract
|
||||
{
|
||||
const MAX_LOCK_RETRIES = 10;
|
||||
private $path;
|
||||
private $fh;
|
||||
|
||||
/**
|
||||
* @var Google_Client the current client
|
||||
*/
|
||||
private $client;
|
||||
|
||||
public function __construct(UDP_Google_Client $client)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->path = $this->client->getClassConfig($this, 'directory');
|
||||
}
|
||||
|
||||
public function get($key, $expiration = false)
|
||||
{
|
||||
$storageFile = $this->getCacheFile($key);
|
||||
$data = false;
|
||||
|
||||
if (!file_exists($storageFile)) {
|
||||
$this->client->getLogger()->debug(
|
||||
'File cache miss',
|
||||
array('key' => $key, 'file' => $storageFile)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($expiration) {
|
||||
$mtime = filemtime($storageFile);
|
||||
if ((time() - $mtime) >= $expiration) {
|
||||
$this->client->getLogger()->debug(
|
||||
'File cache miss (expired)',
|
||||
array('key' => $key, 'file' => $storageFile)
|
||||
);
|
||||
$this->delete($key);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->acquireReadLock($storageFile)) {
|
||||
if (filesize($storageFile) > 0) {
|
||||
$data = fread($this->fh, filesize($storageFile));
|
||||
$data = unserialize($data);
|
||||
} else {
|
||||
$this->client->getLogger()->debug(
|
||||
'Cache file was empty',
|
||||
array('file' => $storageFile)
|
||||
);
|
||||
}
|
||||
$this->unlock($storageFile);
|
||||
}
|
||||
|
||||
$this->client->getLogger()->debug(
|
||||
'File cache hit',
|
||||
array('key' => $key, 'file' => $storageFile, 'var' => $data)
|
||||
);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function set($key, $value)
|
||||
{
|
||||
$storageFile = $this->getWriteableCacheFile($key);
|
||||
if ($this->acquireWriteLock($storageFile)) {
|
||||
// We serialize the whole request object, since we don't only want the
|
||||
// responseContent but also the postBody used, headers, size, etc.
|
||||
$data = serialize($value);
|
||||
$result = fwrite($this->fh, $data);
|
||||
$this->unlock($storageFile);
|
||||
|
||||
$this->client->getLogger()->debug(
|
||||
'File cache set',
|
||||
array('key' => $key, 'file' => $storageFile, 'var' => $value)
|
||||
);
|
||||
} else {
|
||||
$this->client->getLogger()->notice(
|
||||
'File cache set failed',
|
||||
array('key' => $key, 'file' => $storageFile)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete($key)
|
||||
{
|
||||
$file = $this->getCacheFile($key);
|
||||
if (file_exists($file) && !unlink($file)) {
|
||||
$this->client->getLogger()->error(
|
||||
'File cache delete failed',
|
||||
array('key' => $key, 'file' => $file)
|
||||
);
|
||||
throw new Google_Cache_Exception("Cache file could not be deleted");
|
||||
}
|
||||
|
||||
$this->client->getLogger()->debug(
|
||||
'File cache delete',
|
||||
array('key' => $key, 'file' => $file)
|
||||
);
|
||||
}
|
||||
|
||||
private function getWriteableCacheFile($file)
|
||||
{
|
||||
return $this->getCacheFile($file, true);
|
||||
}
|
||||
|
||||
private function getCacheFile($file, $forWrite = false)
|
||||
{
|
||||
return $this->getCacheDir($file, $forWrite) . '/' . md5($file);
|
||||
}
|
||||
|
||||
private function getCacheDir($file, $forWrite)
|
||||
{
|
||||
// use the first 2 characters of the hash as a directory prefix
|
||||
// this should prevent slowdowns due to huge directory listings
|
||||
// and thus give some basic amount of scalability
|
||||
$storageDir = $this->path . '/' . substr(md5($file), 0, 2);
|
||||
if ($forWrite && ! is_dir($storageDir)) {
|
||||
if (! mkdir($storageDir, 0755, true)) {
|
||||
$this->client->getLogger()->error(
|
||||
'File cache creation failed',
|
||||
array('dir' => $storageDir)
|
||||
);
|
||||
throw new Google_Cache_Exception("Could not create storage directory: $storageDir"); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
|
||||
}
|
||||
}
|
||||
return $storageDir;
|
||||
}
|
||||
|
||||
private function acquireReadLock($storageFile)
|
||||
{
|
||||
return $this->acquireLock(LOCK_SH, $storageFile);
|
||||
}
|
||||
|
||||
private function acquireWriteLock($storageFile)
|
||||
{
|
||||
$rc = $this->acquireLock(LOCK_EX, $storageFile);
|
||||
if (!$rc) {
|
||||
$this->client->getLogger()->notice(
|
||||
'File cache write lock failed',
|
||||
array('file' => $storageFile)
|
||||
);
|
||||
$this->delete($storageFile);
|
||||
}
|
||||
return $rc;
|
||||
}
|
||||
|
||||
private function acquireLock($type, $storageFile)
|
||||
{
|
||||
$mode = $type == LOCK_EX ? "w" : "r";
|
||||
$this->fh = fopen($storageFile, $mode);
|
||||
if (!$this->fh) {
|
||||
$this->client->getLogger()->error(
|
||||
'Failed to open file during lock acquisition',
|
||||
array('file' => $storageFile)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$count = 0;
|
||||
while (!flock($this->fh, $type | LOCK_NB)) {
|
||||
// Sleep for 10ms.
|
||||
usleep(10000);
|
||||
if (++$count < self::MAX_LOCK_RETRIES) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function unlock($storageFile)
|
||||
{
|
||||
if ($this->fh) {
|
||||
flock($this->fh, LOCK_UN);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2008 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* A persistent storage class based on the memcache, which is not
|
||||
* really very persistent, as soon as you restart your memcache daemon
|
||||
* the storage will be wiped.
|
||||
*
|
||||
* Will use either the memcache or memcached extensions, preferring
|
||||
* memcached.
|
||||
*
|
||||
* @author Chris Chabot <chabotc@google.com>
|
||||
*/
|
||||
class Google_Cache_Memcache extends Google_Cache_Abstract
|
||||
{
|
||||
private $connection = false;
|
||||
private $mc = false;
|
||||
private $host;
|
||||
private $port;
|
||||
|
||||
/**
|
||||
* @var Google_Client the current client
|
||||
*/
|
||||
private $client;
|
||||
|
||||
public function __construct(UDP_Google_Client $client)
|
||||
{
|
||||
if (!function_exists('memcache_connect') && !class_exists("Memcached")) {
|
||||
$error = "Memcache functions not available";
|
||||
|
||||
$client->getLogger()->error($error);
|
||||
throw new Google_Cache_Exception($error); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
|
||||
}
|
||||
|
||||
$this->client = $client;
|
||||
|
||||
if ($client->isAppEngine()) {
|
||||
// No credentials needed for GAE.
|
||||
$this->mc = new Memcached();
|
||||
$this->connection = true;
|
||||
} else {
|
||||
$this->host = $client->getClassConfig($this, 'host');
|
||||
$this->port = $client->getClassConfig($this, 'port');
|
||||
if (empty($this->host) || (empty($this->port) && (string) $this->port != "0")) {
|
||||
$error = "You need to supply a valid memcache host and port";
|
||||
|
||||
$client->getLogger()->error($error);
|
||||
throw new Google_Cache_Exception($error); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function get($key, $expiration = false)
|
||||
{
|
||||
$this->connect();
|
||||
$ret = false;
|
||||
if ($this->mc) {
|
||||
$ret = $this->mc->get($key);
|
||||
} else {
|
||||
$ret = memcache_get($this->connection, $key);
|
||||
}
|
||||
if ($ret === false) {
|
||||
$this->client->getLogger()->debug(
|
||||
'Memcache cache miss',
|
||||
array('key' => $key)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (is_numeric($expiration) && (time() - $ret['time'] > $expiration)) {
|
||||
$this->client->getLogger()->debug(
|
||||
'Memcache cache miss (expired)',
|
||||
array('key' => $key, 'var' => $ret)
|
||||
);
|
||||
$this->delete($key);
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->client->getLogger()->debug(
|
||||
'Memcache cache hit',
|
||||
array('key' => $key, 'var' => $ret)
|
||||
);
|
||||
|
||||
return $ret['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
* @throws Google_Cache_Exception
|
||||
*/
|
||||
public function set($key, $value)
|
||||
{
|
||||
$this->connect();
|
||||
// we store it with the cache_time default expiration so objects will at
|
||||
// least get cleaned eventually.
|
||||
$data = array('time' => time(), 'data' => $value);
|
||||
$rc = false;
|
||||
if ($this->mc) {
|
||||
$rc = $this->mc->set($key, $data);
|
||||
} else {
|
||||
$rc = memcache_set($this->connection, $key, $data, false);
|
||||
}
|
||||
if ($rc == false) {
|
||||
$this->client->getLogger()->error(
|
||||
'Memcache cache set failed',
|
||||
array('key' => $key, 'var' => $data)
|
||||
);
|
||||
|
||||
throw new Google_Cache_Exception("Couldn't store data in cache");
|
||||
}
|
||||
|
||||
$this->client->getLogger()->debug(
|
||||
'Memcache cache set',
|
||||
array('key' => $key, 'var' => $data)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @param String $key
|
||||
*/
|
||||
public function delete($key)
|
||||
{
|
||||
$this->connect();
|
||||
if ($this->mc) {
|
||||
$this->mc->delete($key, 0);
|
||||
} else {
|
||||
memcache_delete($this->connection, $key, 0);
|
||||
}
|
||||
|
||||
$this->client->getLogger()->debug(
|
||||
'Memcache cache delete',
|
||||
array('key' => $key)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy initialiser for memcache connection. Uses pconnect for to take
|
||||
* advantage of the persistence pool where possible.
|
||||
*/
|
||||
private function connect()
|
||||
{
|
||||
if ($this->connection) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (class_exists("Memcached")) {
|
||||
$this->mc = new Memcached();
|
||||
$this->mc->addServer($this->host, $this->port);
|
||||
$this->connection = true;
|
||||
} else {
|
||||
$this->connection = memcache_pconnect($this->host, $this->port);
|
||||
}
|
||||
|
||||
if (! $this->connection) {
|
||||
$error = "Couldn't connect to memcache server";
|
||||
|
||||
$this->client->getLogger()->error($error);
|
||||
throw new Google_Cache_Exception($error); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2014 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* A blank storage class, for cases where caching is not
|
||||
* required.
|
||||
*/
|
||||
class Google_Cache_Null extends Google_Cache_Abstract
|
||||
{
|
||||
public function __construct(UDP_Google_Client $client)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function get($key, $expiration = false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function set($key, $value)
|
||||
{
|
||||
// Nop.
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @param String $key
|
||||
*/
|
||||
public function delete($key)
|
||||
{
|
||||
// Nop.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,712 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* The Google API Client
|
||||
* http://code.google.com/p/google-api-php-client/
|
||||
*/
|
||||
class UDP_Google_Client
|
||||
{
|
||||
const LIBVER = "1.1.4";
|
||||
const USER_AGENT_SUFFIX = "google-api-php-client-ud/";
|
||||
/**
|
||||
* @var Google_Auth_Abstract $auth
|
||||
*/
|
||||
private $auth;
|
||||
|
||||
/**
|
||||
* @var UDP_Google_IO_Abstract $io
|
||||
*/
|
||||
private $io;
|
||||
|
||||
/**
|
||||
* @var Google_Cache_Abstract $cache
|
||||
*/
|
||||
private $cache;
|
||||
|
||||
/**
|
||||
* @var UDP_Google_Config $config
|
||||
*/
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* @var Google_Logger_Abstract $logger
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* @var boolean $deferExecution
|
||||
*/
|
||||
private $deferExecution = false;
|
||||
|
||||
/** @var array $scopes */
|
||||
// Scopes requested by the client
|
||||
protected $requestedScopes = array();
|
||||
|
||||
// definitions of services that are discovered.
|
||||
protected $services = array();
|
||||
|
||||
// Used to track authenticated state, can't discover services after doing authenticate()
|
||||
private $authenticated = false;
|
||||
|
||||
/**
|
||||
* Construct the Google Client.
|
||||
*
|
||||
* @param $config UDP_Google_Config or string for the ini file to load
|
||||
*/
|
||||
public function __construct($config = null)
|
||||
{
|
||||
if (is_string($config) && strlen($config)) {
|
||||
$config = new UDP_Google_Config($config);
|
||||
} else if ( !($config instanceof UDP_Google_Config)) {
|
||||
$config = new UDP_Google_Config();
|
||||
|
||||
if ($this->isAppEngine()) {
|
||||
// Automatically use Memcache if we're in AppEngine.
|
||||
$config->setCacheClass('Google_Cache_Memcache');
|
||||
}
|
||||
|
||||
if (version_compare(phpversion(), "5.3.4", "<=") || $this->isAppEngine()) {
|
||||
// Automatically disable compress.zlib, as currently unsupported.
|
||||
$config->setClassConfig('UDP_Google_Http_Request', 'disable_gzip', true);
|
||||
}
|
||||
}
|
||||
|
||||
if ($config->getIoClass() == UDP_Google_Config::USE_AUTO_IO_SELECTION) {
|
||||
if (function_exists('curl_version') && function_exists('curl_exec')
|
||||
&& !$this->isAppEngine()) {
|
||||
$config->setIoClass("UDP_Google_IO_Curl");
|
||||
} else {
|
||||
$config->setIoClass("UDP_Google_IO_Stream");
|
||||
}
|
||||
}
|
||||
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string containing the version of the library.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLibraryVersion()
|
||||
{
|
||||
return self::LIBVER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to exchange a code for an valid authentication token.
|
||||
* Helper wrapped around the OAuth 2.0 implementation.
|
||||
*
|
||||
* @param $code string code from accounts.google.com
|
||||
* @return string token
|
||||
*/
|
||||
public function authenticate($code)
|
||||
{
|
||||
$this->authenticated = true;
|
||||
return $this->getAuth()->authenticate($code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a service account key and parameters from a JSON
|
||||
* file from the Google Developer Console. Uses that and the
|
||||
* given array of scopes to return an assertion credential for
|
||||
* use with refreshTokenWithAssertionCredential.
|
||||
*
|
||||
* @param string $jsonLocation File location of the project-key.json.
|
||||
* @param array $scopes The scopes to assert.
|
||||
* @return Google_Auth_AssertionCredentials.
|
||||
* @
|
||||
*/
|
||||
public function loadServiceAccountJson($jsonLocation, $scopes)
|
||||
{
|
||||
$data = json_decode(file_get_contents($jsonLocation));
|
||||
if (isset($data->type) && $data->type == 'service_account') {
|
||||
// Service Account format.
|
||||
$cred = new Google_Auth_AssertionCredentials(
|
||||
$data->client_email,
|
||||
$scopes,
|
||||
$data->private_key
|
||||
);
|
||||
return $cred;
|
||||
} else {
|
||||
throw new Google_Exception("Invalid service account JSON file.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the auth config from the JSON string provided.
|
||||
* This structure should match the file downloaded from
|
||||
* the "Download JSON" button on in the Google Developer
|
||||
* Console.
|
||||
* @param string $json the configuration json
|
||||
* @throws Google_Exception
|
||||
*/
|
||||
public function setAuthConfig($json)
|
||||
{
|
||||
$data = json_decode($json);
|
||||
$key = isset($data->installed) ? 'installed' : 'web';
|
||||
if (!isset($data->$key)) {
|
||||
throw new Google_Exception("Invalid client secret JSON file.");
|
||||
}
|
||||
$this->setClientId($data->$key->client_id);
|
||||
$this->setClientSecret($data->$key->client_secret);
|
||||
if (isset($data->$key->redirect_uris)) {
|
||||
$this->setRedirectUri($data->$key->redirect_uris[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the auth config from the JSON file in the path
|
||||
* provided. This should match the file downloaded from
|
||||
* the "Download JSON" button on in the Google Developer
|
||||
* Console.
|
||||
* @param string $file the file location of the client json
|
||||
*/
|
||||
public function setAuthConfigFile($file)
|
||||
{
|
||||
$this->setAuthConfig(file_get_contents($file));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Google_Auth_Exception
|
||||
* @return array
|
||||
* @visible For Testing
|
||||
*/
|
||||
public function prepareScopes()
|
||||
{
|
||||
if (empty($this->requestedScopes)) {
|
||||
throw new Google_Auth_Exception("No scopes specified");
|
||||
}
|
||||
$scopes = implode(' ', $this->requestedScopes);
|
||||
return $scopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the OAuth 2.0 access token using the string that resulted from calling createAuthUrl()
|
||||
* or UDP_Google_Client#getAccessToken().
|
||||
* @param string $accessToken JSON encoded string containing in the following format:
|
||||
* {"access_token":"TOKEN", "refresh_token":"TOKEN", "token_type":"Bearer",
|
||||
* "expires_in":3600, "id_token":"TOKEN", "created":1320790426}
|
||||
*/
|
||||
public function setAccessToken($accessToken)
|
||||
{
|
||||
if ($accessToken == 'null') {
|
||||
$accessToken = null;
|
||||
}
|
||||
$this->getAuth()->setAccessToken($accessToken);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Set the authenticator object
|
||||
* @param Google_Auth_Abstract $auth
|
||||
*/
|
||||
public function setAuth(Google_Auth_Abstract $auth)
|
||||
{
|
||||
$this->config->setAuthClass(get_class($auth));
|
||||
$this->auth = $auth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the IO object
|
||||
* @param UDP_Google_IO_Abstract $io
|
||||
*/
|
||||
public function setIo(UDP_Google_IO_Abstract $io)
|
||||
{
|
||||
$this->config->setIoClass(get_class($io));
|
||||
$this->io = $io;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Cache object
|
||||
* @param Google_Cache_Abstract $cache
|
||||
*/
|
||||
public function setCache(Google_Cache_Abstract $cache)
|
||||
{
|
||||
$this->config->setCacheClass(get_class($cache));
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Logger object
|
||||
* @param Google_Logger_Abstract $logger
|
||||
*/
|
||||
public function setLogger(Google_Logger_Abstract $logger)
|
||||
{
|
||||
$this->config->setLoggerClass(get_class($logger));
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the OAuth 2.0 authorization request URI.
|
||||
* @return string
|
||||
*/
|
||||
public function createAuthUrl()
|
||||
{
|
||||
$scopes = $this->prepareScopes();
|
||||
return $this->getAuth()->createAuthUrl($scopes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the OAuth 2.0 access token.
|
||||
* @return string $accessToken JSON encoded string in the following format:
|
||||
* {"access_token":"TOKEN", "refresh_token":"TOKEN", "token_type":"Bearer",
|
||||
* "expires_in":3600,"id_token":"TOKEN", "created":1320790426}
|
||||
*/
|
||||
public function getAccessToken()
|
||||
{
|
||||
$token = $this->getAuth()->getAccessToken();
|
||||
// The response is json encoded, so could be the string null.
|
||||
// It is arguable whether this check should be here or lower
|
||||
// in the library.
|
||||
return (null == $token || 'null' == $token || '[]' == $token) ? null : $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the OAuth 2.0 refresh token.
|
||||
* @return string $refreshToken refresh token or null if not available
|
||||
*/
|
||||
public function getRefreshToken()
|
||||
{
|
||||
return $this->getAuth()->getRefreshToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the access_token is expired.
|
||||
* @return bool Returns True if the access_token is expired.
|
||||
*/
|
||||
public function isAccessTokenExpired()
|
||||
{
|
||||
return $this->getAuth()->isAccessTokenExpired();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set OAuth 2.0 "state" parameter to achieve per-request customization.
|
||||
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-3.1.2.2
|
||||
* @param string $state
|
||||
*/
|
||||
public function setState($state)
|
||||
{
|
||||
$this->getAuth()->setState($state);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessType Possible values for access_type include:
|
||||
* {@code "offline"} to request offline access from the user.
|
||||
* {@code "online"} to request online access from the user.
|
||||
*/
|
||||
public function setAccessType($accessType)
|
||||
{
|
||||
$this->config->setAccessType($accessType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $approvalPrompt Possible values for approval_prompt include:
|
||||
* {@code "force"} to force the approval UI to appear. (This is the default value)
|
||||
* {@code "auto"} to request auto-approval when possible.
|
||||
*/
|
||||
public function setApprovalPrompt($approvalPrompt)
|
||||
{
|
||||
$this->config->setApprovalPrompt($approvalPrompt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the login hint, email address or sub id.
|
||||
* @param string $loginHint
|
||||
*/
|
||||
public function setLoginHint($loginHint)
|
||||
{
|
||||
$this->config->setLoginHint($loginHint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the application name, this is included in the User-Agent HTTP header.
|
||||
* @param string $applicationName
|
||||
*/
|
||||
public function setApplicationName($applicationName)
|
||||
{
|
||||
$this->config->setApplicationName($applicationName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the OAuth 2.0 Client ID.
|
||||
* @param string $clientId
|
||||
*/
|
||||
public function setClientId($clientId)
|
||||
{
|
||||
$this->config->setClientId($clientId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the OAuth 2.0 Client Secret.
|
||||
* @param string $clientSecret
|
||||
*/
|
||||
public function setClientSecret($clientSecret)
|
||||
{
|
||||
$this->config->setClientSecret($clientSecret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the OAuth 2.0 Redirect URI.
|
||||
* @param string $redirectUri
|
||||
*/
|
||||
public function setRedirectUri($redirectUri)
|
||||
{
|
||||
$this->config->setRedirectUri($redirectUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* If 'plus.login' is included in the list of requested scopes, you can use
|
||||
* this method to define types of app activities that your app will write.
|
||||
* You can find a list of available types here:
|
||||
* @link https://developers.google.com/+/api/moment-types
|
||||
*
|
||||
* @param array $requestVisibleActions Array of app activity types
|
||||
*/
|
||||
public function setRequestVisibleActions($requestVisibleActions)
|
||||
{
|
||||
if (is_array($requestVisibleActions)) {
|
||||
$requestVisibleActions = join(" ", $requestVisibleActions);
|
||||
}
|
||||
$this->config->setRequestVisibleActions($requestVisibleActions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the developer key to use, these are obtained through the API Console.
|
||||
* @see http://code.google.com/apis/console-help/#generatingdevkeys
|
||||
* @param string $developerKey
|
||||
*/
|
||||
public function setDeveloperKey($developerKey)
|
||||
{
|
||||
$this->config->setDeveloperKey($developerKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the hd (hosted domain) parameter streamlines the login process for
|
||||
* Google Apps hosted accounts. By including the domain of the user, you
|
||||
* restrict sign-in to accounts at that domain.
|
||||
* @param $hd string - the domain to use.
|
||||
*/
|
||||
public function setHostedDomain($hd)
|
||||
{
|
||||
$this->config->setHostedDomain($hd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the prompt hint. Valid values are none, consent and select_account.
|
||||
* If no value is specified and the user has not previously authorized
|
||||
* access, then the user is shown a consent screen.
|
||||
* @param $prompt string
|
||||
*/
|
||||
public function setPrompt($prompt)
|
||||
{
|
||||
$this->config->setPrompt($prompt);
|
||||
}
|
||||
|
||||
/**
|
||||
* openid.realm is a parameter from the OpenID 2.0 protocol, not from OAuth
|
||||
* 2.0. It is used in OpenID 2.0 requests to signify the URL-space for which
|
||||
* an authentication request is valid.
|
||||
* @param $realm string - the URL-space to use.
|
||||
*/
|
||||
public function setOpenidRealm($realm)
|
||||
{
|
||||
$this->config->setOpenidRealm($realm);
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is provided with the value true, and the authorization request is
|
||||
* granted, the authorization will include any previous authorizations
|
||||
* granted to this user/application combination for other scopes.
|
||||
* @param $include boolean - the URL-space to use.
|
||||
*/
|
||||
public function setIncludeGrantedScopes($include)
|
||||
{
|
||||
$this->config->setIncludeGrantedScopes($include);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a fresh OAuth 2.0 access token with the given refresh token.
|
||||
* @param string $refreshToken
|
||||
*/
|
||||
public function refreshToken($refreshToken)
|
||||
{
|
||||
$this->getAuth()->refreshToken($refreshToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke an OAuth2 access token or refresh token. This method will revoke the current access
|
||||
* token, if a token isn't provided.
|
||||
* @throws Google_Auth_Exception
|
||||
* @param string|null $token The token (access token or a refresh token) that should be revoked.
|
||||
* @return boolean Returns True if the revocation was successful, otherwise False.
|
||||
*/
|
||||
public function revokeToken($token = null)
|
||||
{
|
||||
return $this->getAuth()->revokeToken($token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify an id_token. This method will verify the current id_token, if one
|
||||
* isn't provided.
|
||||
* @throws Google_Auth_Exception
|
||||
* @param string|null $token The token (id_token) that should be verified.
|
||||
* @return Google_Auth_LoginTicket Returns an apiLoginTicket if the verification was
|
||||
* successful.
|
||||
*/
|
||||
public function verifyIdToken($token = null)
|
||||
{
|
||||
return $this->getAuth()->verifyIdToken($token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a JWT that was signed with your own certificates.
|
||||
*
|
||||
* @param $id_token string The JWT token
|
||||
* @param $cert_location array of certificates
|
||||
* @param $audience string the expected consumer of the token
|
||||
* @param $issuer string the expected issuer, defaults to Google
|
||||
* @param [$max_expiry] the max lifetime of a token, defaults to MAX_TOKEN_LIFETIME_SECS
|
||||
* @return mixed token information if valid, false if not
|
||||
*/
|
||||
public function verifySignedJwt($id_token, $cert_location, $audience, $issuer, $max_expiry = null)
|
||||
{
|
||||
$auth = new Google_Auth_OAuth2($this);
|
||||
$certs = $auth->retrieveCertsFromLocation($cert_location);
|
||||
return $auth->verifySignedJwtWithCerts($id_token, $certs, $audience, $issuer, $max_expiry);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $creds Google_Auth_AssertionCredentials
|
||||
*/
|
||||
public function setAssertionCredentials(Google_Auth_AssertionCredentials $creds)
|
||||
{
|
||||
$this->getAuth()->setAssertionCredentials($creds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the scopes to be requested. Must be called before createAuthUrl().
|
||||
* Will remove any previously configured scopes.
|
||||
* @param array $scopes, ie: array('https://www.googleapis.com/auth/plus.login',
|
||||
* 'https://www.googleapis.com/auth/moderator')
|
||||
*/
|
||||
public function setScopes($scopes)
|
||||
{
|
||||
$this->requestedScopes = array();
|
||||
$this->addScope($scopes);
|
||||
}
|
||||
|
||||
/**
|
||||
* This functions adds a scope to be requested as part of the OAuth2.0 flow.
|
||||
* Will append any scopes not previously requested to the scope parameter.
|
||||
* A single string will be treated as a scope to request. An array of strings
|
||||
* will each be appended.
|
||||
* @param $scope_or_scopes string|array e.g. "profile"
|
||||
*/
|
||||
public function addScope($scope_or_scopes)
|
||||
{
|
||||
if (is_string($scope_or_scopes) && !in_array($scope_or_scopes, $this->requestedScopes)) {
|
||||
$this->requestedScopes[] = $scope_or_scopes;
|
||||
} else if (is_array($scope_or_scopes)) {
|
||||
foreach ($scope_or_scopes as $scope) {
|
||||
$this->addScope($scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of scopes requested by the client
|
||||
* @return array the list of scopes
|
||||
*
|
||||
*/
|
||||
public function getScopes()
|
||||
{
|
||||
return $this->requestedScopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare whether batch calls should be used. This may increase throughput
|
||||
* by making multiple requests in one connection.
|
||||
*
|
||||
* @param boolean $useBatch True if the batch support should
|
||||
* be enabled. Defaults to False.
|
||||
*/
|
||||
public function setUseBatch($useBatch)
|
||||
{
|
||||
// This is actually an alias for setDefer.
|
||||
$this->setDefer($useBatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare whether making API calls should make the call immediately, or
|
||||
* return a request which can be called with ->execute();
|
||||
*
|
||||
* @param boolean $defer True if calls should not be executed right away.
|
||||
*/
|
||||
public function setDefer($defer)
|
||||
{
|
||||
$this->deferExecution = $defer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to execute deferred HTTP requests.
|
||||
*
|
||||
* @param $request Google_Http_Request|Google_Http_Batch
|
||||
* @throws Google_Exception
|
||||
* @return object of the type of the expected class or array.
|
||||
*/
|
||||
public function execute($request)
|
||||
{
|
||||
if ($request instanceof UDP_Google_Http_Request) {
|
||||
$request->setUserAgent(
|
||||
$this->getApplicationName()
|
||||
. " " . self::USER_AGENT_SUFFIX
|
||||
. $this->getLibraryVersion()
|
||||
);
|
||||
if (!$this->getClassConfig("UDP_Google_Http_Request", "disable_gzip")) {
|
||||
$request->enableGzip();
|
||||
}
|
||||
$request->maybeMoveParametersToBody();
|
||||
return UDP_Google_Http_REST::execute($this, $request);
|
||||
} else if ($request instanceof Google_Http_Batch) {
|
||||
return $request->execute();
|
||||
} else {
|
||||
throw new Google_Exception("Do not know how to execute this type of object.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not to return raw requests
|
||||
* @return boolean
|
||||
*/
|
||||
public function shouldDefer()
|
||||
{
|
||||
return $this->deferExecution;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Google_Auth_Abstract Authentication implementation
|
||||
*/
|
||||
public function getAuth()
|
||||
{
|
||||
if (!isset($this->auth)) {
|
||||
$class = $this->config->getAuthClass();
|
||||
$this->auth = new $class($this);
|
||||
}
|
||||
return $this->auth;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return UDP_Google_IO_Abstract IO implementation
|
||||
*/
|
||||
public function getIo()
|
||||
{
|
||||
if (!isset($this->io)) {
|
||||
$class = $this->config->getIoClass();
|
||||
$this->io = new $class($this);
|
||||
}
|
||||
return $this->io;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Google_Cache_Abstract Cache implementation
|
||||
*/
|
||||
public function getCache()
|
||||
{
|
||||
if (!isset($this->cache)) {
|
||||
$class = $this->config->getCacheClass();
|
||||
$this->cache = new $class($this);
|
||||
}
|
||||
return $this->cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Google_Logger_Abstract Logger implementation
|
||||
*/
|
||||
public function getLogger()
|
||||
{
|
||||
if (!isset($this->logger)) {
|
||||
$class = $this->config->getLoggerClass();
|
||||
$this->logger = new $class($this);
|
||||
}
|
||||
return $this->logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve custom configuration for a specific class.
|
||||
* @param $class string|object - class or instance of class to retrieve
|
||||
* @param $key string optional - key to retrieve
|
||||
* @return array
|
||||
*/
|
||||
public function getClassConfig($class, $key = null)
|
||||
{
|
||||
if (!is_string($class)) {
|
||||
$class = get_class($class);
|
||||
}
|
||||
return $this->config->getClassConfig($class, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set configuration specific to a given class.
|
||||
* $config->setClassConfig('Google_Cache_File',
|
||||
* array('directory' => '/tmp/cache'));
|
||||
* @param $class string|object - The class name for the configuration
|
||||
* @param $config string key or an array of configuration values
|
||||
* @param $value string optional - if $config is a key, the value
|
||||
*
|
||||
*/
|
||||
public function setClassConfig($class, $config, $value = null)
|
||||
{
|
||||
if (!is_string($class)) {
|
||||
$class = get_class($class);
|
||||
}
|
||||
$this->config->setClassConfig($class, $config, $value);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string the base URL to use for calls to the APIs
|
||||
*/
|
||||
public function getBasePath()
|
||||
{
|
||||
return $this->config->getBasePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string the name of the application
|
||||
*/
|
||||
public function getApplicationName()
|
||||
{
|
||||
return $this->config->getApplicationName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Are we running in Google AppEngine?
|
||||
* return bool
|
||||
*/
|
||||
public function isAppEngine()
|
||||
{
|
||||
return (isset($_SERVER['SERVER_SOFTWARE']) &&
|
||||
strpos($_SERVER['SERVER_SOFTWARE'], 'Google App Engine') !== false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension to the regular Google_Model that automatically
|
||||
* exposes the items array for iteration, so you can just
|
||||
* iterate over the object rather than a reference inside.
|
||||
*/
|
||||
class Google_Collection extends Google_Model implements Iterator, Countable
|
||||
{
|
||||
protected $collection_key = 'items';
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function rewind()
|
||||
{
|
||||
if (isset($this->modelData[$this->collection_key])
|
||||
&& is_array($this->modelData[$this->collection_key])) {
|
||||
reset($this->modelData[$this->collection_key]);
|
||||
}
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function current()
|
||||
{
|
||||
$this->coerceType($this->key());
|
||||
if (is_array($this->modelData[$this->collection_key])) {
|
||||
return current($this->modelData[$this->collection_key]);
|
||||
}
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function key()
|
||||
{
|
||||
if (isset($this->modelData[$this->collection_key])
|
||||
&& is_array($this->modelData[$this->collection_key])) {
|
||||
return key($this->modelData[$this->collection_key]);
|
||||
}
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function next()
|
||||
{
|
||||
return next($this->modelData[$this->collection_key]);
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function valid()
|
||||
{
|
||||
$key = $this->key();
|
||||
return $key !== null && $key !== false;
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function count()
|
||||
{
|
||||
if (!isset($this->modelData[$this->collection_key])) {
|
||||
return 0;
|
||||
}
|
||||
return count($this->modelData[$this->collection_key]);
|
||||
}
|
||||
|
||||
public function offsetExists ($offset)
|
||||
{
|
||||
if (!is_numeric($offset)) {
|
||||
return parent::offsetExists($offset);
|
||||
}
|
||||
return isset($this->modelData[$this->collection_key][$offset]);
|
||||
}
|
||||
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
if (!is_numeric($offset)) {
|
||||
return parent::offsetGet($offset);
|
||||
}
|
||||
$this->coerceType($offset);
|
||||
return $this->modelData[$this->collection_key][$offset];
|
||||
}
|
||||
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
if (!is_numeric($offset)) {
|
||||
return parent::offsetSet($offset, $value);
|
||||
}
|
||||
$this->modelData[$this->collection_key][$offset] = $value;
|
||||
}
|
||||
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
if (!is_numeric($offset)) {
|
||||
return parent::offsetUnset($offset);
|
||||
}
|
||||
unset($this->modelData[$this->collection_key][$offset]);
|
||||
}
|
||||
|
||||
private function coerceType($offset)
|
||||
{
|
||||
$typeKey = $this->keyType($this->collection_key);
|
||||
if (isset($this->$typeKey) && !is_object($this->modelData[$this->collection_key][$offset])) {
|
||||
$type = $this->$typeKey;
|
||||
$this->modelData[$this->collection_key][$offset] =
|
||||
new $type($this->modelData[$this->collection_key][$offset]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,453 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A class to contain the library configuration for the Google API client.
|
||||
*/
|
||||
class UDP_Google_Config
|
||||
{
|
||||
const GZIP_DISABLED = true;
|
||||
const GZIP_ENABLED = false;
|
||||
const GZIP_UPLOADS_ENABLED = true;
|
||||
const GZIP_UPLOADS_DISABLED = false;
|
||||
const USE_AUTO_IO_SELECTION = "auto";
|
||||
const TASK_RETRY_NEVER = 0;
|
||||
const TASK_RETRY_ONCE = 1;
|
||||
const TASK_RETRY_ALWAYS = -1;
|
||||
protected $configuration;
|
||||
|
||||
/**
|
||||
* Create a new UDP_Google_Config. Can accept an ini file location with the
|
||||
* local configuration. For example:
|
||||
* application_name="My App"
|
||||
*
|
||||
* @param [$ini_file_location] - optional - The location of the ini file to load
|
||||
*/
|
||||
public function __construct($ini_file_location = null)
|
||||
{
|
||||
$this->configuration = array(
|
||||
// The application_name is included in the User-Agent HTTP header.
|
||||
'application_name' => '',
|
||||
|
||||
// Which Authentication, Storage and HTTP IO classes to use.
|
||||
'auth_class' => 'Google_Auth_OAuth2',
|
||||
'io_class' => self::USE_AUTO_IO_SELECTION,
|
||||
'cache_class' => 'Google_Cache_File',
|
||||
'logger_class' => 'Google_Logger_Null',
|
||||
|
||||
// Don't change these unless you're working against a special development
|
||||
// or testing environment.
|
||||
'base_path' => 'https://www.googleapis.com',
|
||||
|
||||
// Definition of class specific values, like file paths and so on.
|
||||
'classes' => array(
|
||||
'UDP_Google_IO_Abstract' => array(
|
||||
'request_timeout_seconds' => 100,
|
||||
),
|
||||
'Google_Logger_Abstract' => array(
|
||||
'level' => 'debug',
|
||||
'log_format' => "[%datetime%] %level%: %message% %context%\n",
|
||||
'date_format' => 'd/M/Y:H:i:s O',
|
||||
'allow_newlines' => true
|
||||
),
|
||||
'Google_Logger_File' => array(
|
||||
'file' => 'php://stdout',
|
||||
'mode' => 0640,
|
||||
'lock' => false,
|
||||
),
|
||||
'UDP_Google_Http_Request' => array(
|
||||
// Disable the use of gzip on calls if set to true. Defaults to false.
|
||||
'disable_gzip' => self::GZIP_ENABLED,
|
||||
|
||||
// We default gzip to disabled on uploads even if gzip is otherwise
|
||||
// enabled, due to some issues seen with small packet sizes for uploads.
|
||||
// Please test with this option before enabling gzip for uploads in
|
||||
// a production environment.
|
||||
'enable_gzip_for_uploads' => self::GZIP_UPLOADS_DISABLED,
|
||||
),
|
||||
// If you want to pass in OAuth 2.0 settings, they will need to be
|
||||
// structured like this.
|
||||
'Google_Auth_OAuth2' => array(
|
||||
// Keys for OAuth 2.0 access, see the API console at
|
||||
// https://developers.google.com/console
|
||||
'client_id' => '',
|
||||
'client_secret' => '',
|
||||
'redirect_uri' => '',
|
||||
|
||||
// Simple API access key, also from the API console. Ensure you get
|
||||
// a Server key, and not a Browser key.
|
||||
'developer_key' => '',
|
||||
|
||||
// Other parameters.
|
||||
'hd' => '',
|
||||
'prompt' => '',
|
||||
'openid.realm' => '',
|
||||
'include_granted_scopes' => '',
|
||||
'login_hint' => '',
|
||||
'request_visible_actions' => '',
|
||||
'access_type' => 'online',
|
||||
'approval_prompt' => 'auto',
|
||||
'federated_signon_certs_url' =>
|
||||
'https://www.googleapis.com/oauth2/v1/certs',
|
||||
),
|
||||
'Google_Task_Runner' => array(
|
||||
// Delays are specified in seconds
|
||||
'initial_delay' => 1,
|
||||
'max_delay' => 60,
|
||||
// Base number for exponential backoff
|
||||
'factor' => 2,
|
||||
// A random number between -jitter and jitter will be added to the
|
||||
// factor on each iteration to allow for better distribution of
|
||||
// retries.
|
||||
'jitter' => .5,
|
||||
// Maximum number of retries allowed
|
||||
'retries' => 0
|
||||
),
|
||||
'UDP_Google_Service_Exception' => array(
|
||||
'retry_map' => array(
|
||||
'500' => self::TASK_RETRY_ALWAYS,
|
||||
'503' => self::TASK_RETRY_ALWAYS,
|
||||
'rateLimitExceeded' => self::TASK_RETRY_ALWAYS,
|
||||
'userRateLimitExceeded' => self::TASK_RETRY_ALWAYS
|
||||
)
|
||||
),
|
||||
'UDP_Google_IO_Exception' => array(
|
||||
'retry_map' => !extension_loaded('curl') ? array() : array(
|
||||
CURLE_COULDNT_RESOLVE_HOST => self::TASK_RETRY_ALWAYS,
|
||||
CURLE_COULDNT_CONNECT => self::TASK_RETRY_ALWAYS,
|
||||
CURLE_OPERATION_TIMEOUTED => self::TASK_RETRY_ALWAYS,
|
||||
CURLE_SSL_CONNECT_ERROR => self::TASK_RETRY_ALWAYS,
|
||||
CURLE_GOT_NOTHING => self::TASK_RETRY_ALWAYS
|
||||
)
|
||||
),
|
||||
// Set a default directory for the file cache.
|
||||
'Google_Cache_File' => array(
|
||||
'directory' => sys_get_temp_dir() . '/UDP_Google_Client'
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
if ($ini_file_location) {
|
||||
$ini = parse_ini_file($ini_file_location, true);
|
||||
if (is_array($ini) && count($ini)) {
|
||||
$merged_configuration = $ini + $this->configuration;
|
||||
if (isset($ini['classes']) && isset($this->configuration['classes'])) {
|
||||
$merged_configuration['classes'] = $ini['classes'] + $this->configuration['classes'];
|
||||
}
|
||||
$this->configuration = $merged_configuration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set configuration specific to a given class.
|
||||
* $config->setClassConfig('Google_Cache_File',
|
||||
* array('directory' => '/tmp/cache'));
|
||||
* @param $class string The class name for the configuration
|
||||
* @param $config string key or an array of configuration values
|
||||
* @param $value string optional - if $config is a key, the value
|
||||
*/
|
||||
public function setClassConfig($class, $config, $value = null)
|
||||
{
|
||||
if (!is_array($config)) {
|
||||
if (!isset($this->configuration['classes'][$class])) {
|
||||
$this->configuration['classes'][$class] = array();
|
||||
}
|
||||
$this->configuration['classes'][$class][$config] = $value;
|
||||
} else {
|
||||
$this->configuration['classes'][$class] = $config;
|
||||
}
|
||||
}
|
||||
|
||||
public function getClassConfig($class, $key = null)
|
||||
{
|
||||
if (!isset($this->configuration['classes'][$class])) {
|
||||
return null;
|
||||
}
|
||||
if ($key === null) {
|
||||
return $this->configuration['classes'][$class];
|
||||
} else {
|
||||
return $this->configuration['classes'][$class][$key];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured cache class.
|
||||
* @return string
|
||||
*/
|
||||
public function getCacheClass()
|
||||
{
|
||||
return $this->configuration['cache_class'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured logger class.
|
||||
* @return string
|
||||
*/
|
||||
public function getLoggerClass()
|
||||
{
|
||||
return $this->configuration['logger_class'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured Auth class.
|
||||
* @return string
|
||||
*/
|
||||
public function getAuthClass()
|
||||
{
|
||||
return $this->configuration['auth_class'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the auth class.
|
||||
*
|
||||
* @param $class string the class name to set
|
||||
*/
|
||||
public function setAuthClass($class)
|
||||
{
|
||||
$prev = $this->configuration['auth_class'];
|
||||
if (!isset($this->configuration['classes'][$class]) &&
|
||||
isset($this->configuration['classes'][$prev])) {
|
||||
$this->configuration['classes'][$class] =
|
||||
$this->configuration['classes'][$prev];
|
||||
}
|
||||
$this->configuration['auth_class'] = $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the IO class.
|
||||
*
|
||||
* @param $class string the class name to set
|
||||
*/
|
||||
public function setIoClass($class)
|
||||
{
|
||||
$prev = $this->configuration['io_class'];
|
||||
if (!isset($this->configuration['classes'][$class]) &&
|
||||
isset($this->configuration['classes'][$prev])) {
|
||||
$this->configuration['classes'][$class] =
|
||||
$this->configuration['classes'][$prev];
|
||||
}
|
||||
$this->configuration['io_class'] = $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cache class.
|
||||
*
|
||||
* @param $class string the class name to set
|
||||
*/
|
||||
public function setCacheClass($class)
|
||||
{
|
||||
$prev = $this->configuration['cache_class'];
|
||||
if (!isset($this->configuration['classes'][$class]) &&
|
||||
isset($this->configuration['classes'][$prev])) {
|
||||
$this->configuration['classes'][$class] =
|
||||
$this->configuration['classes'][$prev];
|
||||
}
|
||||
$this->configuration['cache_class'] = $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the logger class.
|
||||
*
|
||||
* @param $class string the class name to set
|
||||
*/
|
||||
public function setLoggerClass($class)
|
||||
{
|
||||
$prev = $this->configuration['logger_class'];
|
||||
if (!isset($this->configuration['classes'][$class]) &&
|
||||
isset($this->configuration['classes'][$prev])) {
|
||||
$this->configuration['classes'][$class] =
|
||||
$this->configuration['classes'][$prev];
|
||||
}
|
||||
$this->configuration['logger_class'] = $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured IO class.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getIoClass()
|
||||
{
|
||||
return $this->configuration['io_class'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the application name, this is included in the User-Agent HTTP header.
|
||||
* @param string $name
|
||||
*/
|
||||
public function setApplicationName($name)
|
||||
{
|
||||
$this->configuration['application_name'] = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string the name of the application
|
||||
*/
|
||||
public function getApplicationName()
|
||||
{
|
||||
return $this->configuration['application_name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the client ID for the auth class.
|
||||
* @param $clientId string - the API console client ID
|
||||
*/
|
||||
public function setClientId($clientId)
|
||||
{
|
||||
$this->setAuthConfig('client_id', $clientId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the client secret for the auth class.
|
||||
* @param $secret string - the API console client secret
|
||||
*/
|
||||
public function setClientSecret($secret)
|
||||
{
|
||||
$this->setAuthConfig('client_secret', $secret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the redirect uri for the auth class. Note that if using the
|
||||
* Javascript based sign in flow, this should be the string 'postmessage'.
|
||||
*
|
||||
* @param $uri string - the URI that users should be redirected to
|
||||
*/
|
||||
public function setRedirectUri($uri)
|
||||
{
|
||||
$this->setAuthConfig('redirect_uri', $uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the app activities for the auth class.
|
||||
* @param $rva string a space separated list of app activity types
|
||||
*/
|
||||
public function setRequestVisibleActions($rva)
|
||||
{
|
||||
$this->setAuthConfig('request_visible_actions', $rva);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the the access type requested (offline or online.)
|
||||
* @param $access string - the access type
|
||||
*/
|
||||
public function setAccessType($access)
|
||||
{
|
||||
$this->setAuthConfig('access_type', $access);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set when to show the approval prompt (auto or force)
|
||||
* @param $approval string - the approval request
|
||||
*/
|
||||
public function setApprovalPrompt($approval)
|
||||
{
|
||||
$this->setAuthConfig('approval_prompt', $approval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the login hint (email address or sub identifier)
|
||||
* @param $hint string
|
||||
*/
|
||||
public function setLoginHint($hint)
|
||||
{
|
||||
$this->setAuthConfig('login_hint', $hint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the developer key for the auth class. Note that this is separate value
|
||||
* from the client ID - if it looks like a URL, its a client ID!
|
||||
* @param $key string - the API console developer key
|
||||
*/
|
||||
public function setDeveloperKey($key)
|
||||
{
|
||||
$this->setAuthConfig('developer_key', $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the hd (hosted domain) parameter streamlines the login process for
|
||||
* Google Apps hosted accounts. By including the domain of the user, you
|
||||
* restrict sign-in to accounts at that domain.
|
||||
*
|
||||
* This should not be used to ensure security on your application - check
|
||||
* the hd values within an id token (@see Google_Auth_LoginTicket) after sign
|
||||
* in to ensure that the user is from the domain you were expecting.
|
||||
*
|
||||
* @param $hd string - the domain to use.
|
||||
*/
|
||||
public function setHostedDomain($hd)
|
||||
{
|
||||
$this->setAuthConfig('hd', $hd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the prompt hint. Valid values are none, consent and select_account.
|
||||
* If no value is specified and the user has not previously authorized
|
||||
* access, then the user is shown a consent screen.
|
||||
* @param $prompt string
|
||||
*/
|
||||
public function setPrompt($prompt)
|
||||
{
|
||||
$this->setAuthConfig('prompt', $prompt);
|
||||
}
|
||||
|
||||
/**
|
||||
* openid.realm is a parameter from the OpenID 2.0 protocol, not from OAuth
|
||||
* 2.0. It is used in OpenID 2.0 requests to signify the URL-space for which
|
||||
* an authentication request is valid.
|
||||
* @param $realm string - the URL-space to use.
|
||||
*/
|
||||
public function setOpenidRealm($realm)
|
||||
{
|
||||
$this->setAuthConfig('openid.realm', $realm);
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is provided with the value true, and the authorization request is
|
||||
* granted, the authorization will include any previous authorizations
|
||||
* granted to this user/application combination for other scopes.
|
||||
* @param $include boolean - the URL-space to use.
|
||||
*/
|
||||
public function setIncludeGrantedScopes($include)
|
||||
{
|
||||
$this->setAuthConfig(
|
||||
'include_granted_scopes',
|
||||
$include ? "true" : "false"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string the base URL to use for API calls
|
||||
*/
|
||||
public function getBasePath()
|
||||
{
|
||||
return $this->configuration['base_path'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the auth configuration for the current auth class.
|
||||
* @param $key - the key to set
|
||||
* @param $value - the parameter value
|
||||
*/
|
||||
private function setAuthConfig($key, $value)
|
||||
{
|
||||
if (!isset($this->configuration['classes'][$this->getAuthClass()])) {
|
||||
$this->configuration['classes'][$this->getAuthClass()] = array();
|
||||
}
|
||||
$this->configuration['classes'][$this->getAuthClass()][$key] = $value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
class Google_Exception extends Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to handle batched requests to the Google API service.
|
||||
*/
|
||||
class Google_Http_Batch
|
||||
{
|
||||
/** @var string Multipart Boundary. */
|
||||
private $boundary;
|
||||
|
||||
/** @var array service requests to be executed. */
|
||||
private $requests = array();
|
||||
|
||||
/** @var Google_Client */
|
||||
private $client;
|
||||
|
||||
private $expected_classes = array();
|
||||
|
||||
private $base_path;
|
||||
|
||||
public function __construct(UDP_Google_Client $client, $boundary = false)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->base_path = $this->client->getBasePath();
|
||||
$this->expected_classes = array();
|
||||
$boundary = (false == $boundary) ? mt_rand() : $boundary;
|
||||
$this->boundary = str_replace('"', '', $boundary);
|
||||
}
|
||||
|
||||
public function add(UDP_Google_Http_Request $request, $key = false)
|
||||
{
|
||||
if (false == $key) {
|
||||
$key = mt_rand();
|
||||
}
|
||||
|
||||
$this->requests[$key] = $request;
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$body = '';
|
||||
|
||||
/** @var Google_Http_Request $req */
|
||||
foreach ($this->requests as $key => $req) {
|
||||
$body .= "--{$this->boundary}\n";
|
||||
$body .= $req->toBatchString($key) . "\n";
|
||||
$this->expected_classes["response-" . $key] = $req->getExpectedClass();
|
||||
}
|
||||
|
||||
$body = rtrim($body);
|
||||
$body .= "\n--{$this->boundary}--";
|
||||
|
||||
$url = $this->base_path . '/batch';
|
||||
$httpRequest = new UDP_Google_Http_Request($url, 'POST');
|
||||
$httpRequest->setRequestHeaders(
|
||||
array('Content-Type' => 'multipart/mixed; boundary=' . $this->boundary)
|
||||
);
|
||||
|
||||
$httpRequest->setPostBody($body);
|
||||
$response = $this->client->getIo()->makeRequest($httpRequest);
|
||||
|
||||
return $this->parseResponse($response);
|
||||
}
|
||||
|
||||
public function parseResponse(UDP_Google_Http_Request $response)
|
||||
{
|
||||
$contentType = $response->getResponseHeader('content-type');
|
||||
$contentType = explode(';', $contentType);
|
||||
$boundary = false;
|
||||
foreach ($contentType as $part) {
|
||||
$part = (explode('=', $part, 2));
|
||||
if (isset($part[0]) && 'boundary' == trim($part[0])) {
|
||||
$boundary = $part[1];
|
||||
}
|
||||
}
|
||||
|
||||
$body = $response->getResponseBody();
|
||||
if ($body) {
|
||||
$body = str_replace("--$boundary--", "--$boundary", $body);
|
||||
$parts = explode("--$boundary", $body);
|
||||
$responses = array();
|
||||
|
||||
foreach ($parts as $part) {
|
||||
$part = trim($part);
|
||||
if (!empty($part)) {
|
||||
list($metaHeaders, $part) = explode("\r\n\r\n", $part, 2);
|
||||
$metaHeaders = $this->client->getIo()->getHttpResponseHeaders($metaHeaders);
|
||||
|
||||
$status = substr($part, 0, strpos($part, "\n"));
|
||||
$status = explode(" ", $status);
|
||||
$status = $status[1];
|
||||
|
||||
list($partHeaders, $partBody) = $this->client->getIo()->ParseHttpResponse($part, false);
|
||||
$response = new UDP_Google_Http_Request("");
|
||||
$response->setResponseHttpCode($status);
|
||||
$response->setResponseHeaders($partHeaders);
|
||||
$response->setResponseBody($partBody);
|
||||
|
||||
// Need content id.
|
||||
$key = $metaHeaders['content-id'];
|
||||
|
||||
if (isset($this->expected_classes[$key]) &&
|
||||
strlen($this->expected_classes[$key]) > 0) {
|
||||
$class = $this->expected_classes[$key];
|
||||
$response->setExpectedClass($class);
|
||||
}
|
||||
|
||||
try {
|
||||
$response = UDP_Google_Http_REST::decodeHttpResponse($response, $this->client);
|
||||
$responses[$key] = $response;
|
||||
} catch (UDP_Google_Service_Exception $e) {
|
||||
// Store the exception as the response, so successful responses
|
||||
// can be processed.
|
||||
$responses[$key] = $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $responses;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement the caching directives specified in rfc2616. This
|
||||
* implementation is guided by the guidance offered in rfc2616-sec13.
|
||||
*/
|
||||
class Google_Http_CacheParser
|
||||
{
|
||||
public static $CACHEABLE_HTTP_METHODS = array('GET', 'HEAD');
|
||||
public static $CACHEABLE_STATUS_CODES = array('200', '203', '300', '301');
|
||||
|
||||
/**
|
||||
* Check if an HTTP request can be cached by a private local cache.
|
||||
*
|
||||
* @static
|
||||
* @param Google_Http_Request $resp
|
||||
* @return bool True if the request is cacheable.
|
||||
* False if the request is uncacheable.
|
||||
*/
|
||||
public static function isRequestCacheable(UDP_Google_Http_Request $resp)
|
||||
{
|
||||
$method = $resp->getRequestMethod();
|
||||
if (! in_array($method, self::$CACHEABLE_HTTP_METHODS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't cache authorized requests/responses.
|
||||
// [rfc2616-14.8] When a shared cache receives a request containing an
|
||||
// Authorization field, it MUST NOT return the corresponding response
|
||||
// as a reply to any other request...
|
||||
if ($resp->getRequestHeader("authorization")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an HTTP response can be cached by a private local cache.
|
||||
*
|
||||
* @static
|
||||
* @param Google_Http_Request $resp
|
||||
* @return bool True if the response is cacheable.
|
||||
* False if the response is un-cacheable.
|
||||
*/
|
||||
public static function isResponseCacheable(UDP_Google_Http_Request $resp)
|
||||
{
|
||||
// First, check if the HTTP request was cacheable before inspecting the
|
||||
// HTTP response.
|
||||
if (false == self::isRequestCacheable($resp)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$code = $resp->getResponseHttpCode();
|
||||
if (! in_array($code, self::$CACHEABLE_STATUS_CODES)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The resource is uncacheable if the resource is already expired and
|
||||
// the resource doesn't have an ETag for revalidation.
|
||||
$etag = $resp->getResponseHeader("etag");
|
||||
if (self::isExpired($resp) && $etag == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// [rfc2616-14.9.2] If [no-store is] sent in a response, a cache MUST NOT
|
||||
// store any part of either this response or the request that elicited it.
|
||||
$cacheControl = $resp->getParsedCacheControl();
|
||||
if (isset($cacheControl['no-store'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Pragma: no-cache is an http request directive, but is occasionally
|
||||
// used as a response header incorrectly.
|
||||
$pragma = $resp->getResponseHeader('pragma');
|
||||
if ($pragma == 'no-cache' || strpos($pragma, 'no-cache') !== false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// [rfc2616-14.44] Vary: * is extremely difficult to cache. "It implies that
|
||||
// a cache cannot determine from the request headers of a subsequent request
|
||||
// whether this response is the appropriate representation."
|
||||
// Given this, we deem responses with the Vary header as uncacheable.
|
||||
$vary = $resp->getResponseHeader('vary');
|
||||
if ($vary) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @static
|
||||
* @param Google_Http_Request $resp
|
||||
* @return bool True if the HTTP response is considered to be expired.
|
||||
* False if it is considered to be fresh.
|
||||
*/
|
||||
public static function isExpired(UDP_Google_Http_Request $resp)
|
||||
{
|
||||
// HTTP/1.1 clients and caches MUST treat other invalid date formats,
|
||||
// especially including the value “0”, as in the past.
|
||||
$parsedExpires = false;
|
||||
$responseHeaders = $resp->getResponseHeaders();
|
||||
|
||||
if (isset($responseHeaders['expires'])) {
|
||||
$rawExpires = $responseHeaders['expires'];
|
||||
// Check for a malformed expires header first.
|
||||
if (empty($rawExpires) || (is_numeric($rawExpires) && $rawExpires <= 0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// See if we can parse the expires header.
|
||||
$parsedExpires = strtotime($rawExpires);
|
||||
if (false == $parsedExpires || $parsedExpires <= 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the freshness of an http response.
|
||||
$freshnessLifetime = false;
|
||||
$cacheControl = $resp->getParsedCacheControl();
|
||||
if (isset($cacheControl['max-age'])) {
|
||||
$freshnessLifetime = $cacheControl['max-age'];
|
||||
}
|
||||
|
||||
$rawDate = $resp->getResponseHeader('date');
|
||||
$parsedDate = strtotime($rawDate);
|
||||
|
||||
if (empty($rawDate) || false == $parsedDate) {
|
||||
// We can't default this to now, as that means future cache reads
|
||||
// will always pass with the logic below, so we will require a
|
||||
// date be injected if not supplied.
|
||||
throw new Google_Exception("All cacheable requests must have creation dates.");
|
||||
}
|
||||
|
||||
if (false == $freshnessLifetime && isset($responseHeaders['expires'])) {
|
||||
$freshnessLifetime = $parsedExpires - $parsedDate;
|
||||
}
|
||||
|
||||
if (false == $freshnessLifetime) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Calculate the age of an http response.
|
||||
$age = max(0, time() - $parsedDate);
|
||||
if (isset($responseHeaders['age'])) {
|
||||
$age = max($age, strtotime($responseHeaders['age']));
|
||||
}
|
||||
|
||||
return $freshnessLifetime <= $age;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a cache entry should be revalidated with by the origin.
|
||||
*
|
||||
* @param Google_Http_Request $response
|
||||
* @return bool True if the entry is expired, else return false.
|
||||
*/
|
||||
public static function mustRevalidate(UDP_Google_Http_Request $response)
|
||||
{
|
||||
// [13.3] When a cache has a stale entry that it would like to use as a
|
||||
// response to a client's request, it first has to check with the origin
|
||||
// server to see if its cached entry is still usable.
|
||||
return self::isExpired($response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage large file uploads, which may be media but can be any type
|
||||
* of sizable data.
|
||||
*/
|
||||
class Google_Http_MediaFileUpload
|
||||
{
|
||||
const UPLOAD_MEDIA_TYPE = 'media';
|
||||
const UPLOAD_MULTIPART_TYPE = 'multipart';
|
||||
const UPLOAD_RESUMABLE_TYPE = 'resumable';
|
||||
|
||||
/** @var string $mimeType */
|
||||
private $mimeType;
|
||||
|
||||
/** @var string $data */
|
||||
private $data;
|
||||
|
||||
/** @var bool $resumable */
|
||||
private $resumable;
|
||||
|
||||
/** @var int $chunkSize */
|
||||
private $chunkSize;
|
||||
|
||||
/** @var int $size */
|
||||
private $size;
|
||||
|
||||
/** @var string $resumeUri */
|
||||
// UpdraftPlus patch
|
||||
public $resumeUri;
|
||||
// private $resumeUri;
|
||||
|
||||
/** @var int $progress */
|
||||
// UpdraftPlus patch
|
||||
public $progress;
|
||||
// private $progress;
|
||||
|
||||
/** @var Google_Client */
|
||||
private $client;
|
||||
|
||||
/** @var Google_Http_Request */
|
||||
private $request;
|
||||
|
||||
/** @var string */
|
||||
private $boundary;
|
||||
|
||||
/**
|
||||
* Result code from last HTTP call
|
||||
* @var int
|
||||
*/
|
||||
private $httpResultCode;
|
||||
|
||||
/**
|
||||
* @param $mimeType string
|
||||
* @param $data string The bytes you want to upload.
|
||||
* @param $resumable bool
|
||||
* @param bool $chunkSize File will be uploaded in chunks of this many bytes.
|
||||
* only used if resumable=True
|
||||
*/
|
||||
public function __construct(
|
||||
UDP_Google_Client $client,
|
||||
UDP_Google_Http_Request $request,
|
||||
$mimeType,
|
||||
$data,
|
||||
$resumable = false,
|
||||
$chunkSize = false,
|
||||
$boundary = false
|
||||
) {
|
||||
$this->client = $client;
|
||||
$this->request = $request;
|
||||
$this->mimeType = $mimeType;
|
||||
$this->data = $data;
|
||||
$this->size = strlen($this->data);
|
||||
$this->resumable = $resumable;
|
||||
if (!$chunkSize) {
|
||||
$chunkSize = 256 * 1024;
|
||||
}
|
||||
$this->chunkSize = $chunkSize;
|
||||
$this->progress = 0;
|
||||
$this->boundary = $boundary;
|
||||
|
||||
// Process Media Request
|
||||
$this->process();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the size of the file that is being uploaded.
|
||||
* @param $size - int file size in bytes
|
||||
*/
|
||||
public function setFileSize($size)
|
||||
{
|
||||
$this->size = $size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the progress on the upload
|
||||
* @return int progress in bytes uploaded.
|
||||
*/
|
||||
public function getProgress()
|
||||
{
|
||||
return $this->progress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the HTTP result code from the last call made.
|
||||
* @return int code
|
||||
*/
|
||||
public function getHttpResultCode()
|
||||
{
|
||||
return $this->httpResultCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the next part of the file to upload.
|
||||
* @param [$chunk] the next set of bytes to send. If false will used $data passed
|
||||
* at construct time.
|
||||
*/
|
||||
public function nextChunk($chunk = false)
|
||||
{
|
||||
if (false == $this->resumeUri) {
|
||||
$this->resumeUri = $this->getResumeUri();
|
||||
}
|
||||
|
||||
if (false == $chunk) {
|
||||
$chunk = substr($this->data, $this->progress, $this->chunkSize);
|
||||
}
|
||||
|
||||
$lastBytePos = $this->progress + strlen($chunk) - 1;
|
||||
$headers = array(
|
||||
'content-range' => "bytes $this->progress-$lastBytePos/$this->size",
|
||||
'content-type' => $this->request->getRequestHeader('content-type'),
|
||||
'content-length' => $this->chunkSize,
|
||||
'expect' => '',
|
||||
);
|
||||
|
||||
$httpRequest = new UDP_Google_Http_Request(
|
||||
$this->resumeUri,
|
||||
'PUT',
|
||||
$headers,
|
||||
$chunk
|
||||
);
|
||||
|
||||
if ($this->client->getClassConfig("UDP_Google_Http_Request", "enable_gzip_for_uploads")) {
|
||||
$httpRequest->enableGzip();
|
||||
} else {
|
||||
$httpRequest->disableGzip();
|
||||
}
|
||||
|
||||
$response = $this->client->getIo()->makeRequest($httpRequest);
|
||||
$response->setExpectedClass($this->request->getExpectedClass());
|
||||
$code = $response->getResponseHttpCode();
|
||||
$this->httpResultCode = $code;
|
||||
|
||||
if (308 == $code) {
|
||||
// Track the amount uploaded.
|
||||
$range = explode('-', $response->getResponseHeader('range'));
|
||||
$this->progress = $range[1] + 1;
|
||||
|
||||
// Allow for changing upload URLs.
|
||||
$location = $response->getResponseHeader('location');
|
||||
if ($location) {
|
||||
$this->resumeUri = $location;
|
||||
}
|
||||
|
||||
// No problems, but upload not complete.
|
||||
return false;
|
||||
} else {
|
||||
return UDP_Google_Http_REST::decodeHttpResponse($response, $this->client);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $meta
|
||||
* @param $params
|
||||
* @return array|bool
|
||||
* @visible for testing
|
||||
*/
|
||||
private function process()
|
||||
{
|
||||
$postBody = false;
|
||||
$contentType = false;
|
||||
|
||||
$meta = $this->request->getPostBody();
|
||||
$meta = is_string($meta) ? json_decode($meta, true) : $meta;
|
||||
|
||||
$uploadType = $this->getUploadType($meta);
|
||||
$this->request->setQueryParam('uploadType', $uploadType);
|
||||
$this->transformToUploadUrl();
|
||||
$mimeType = $this->mimeType ?
|
||||
$this->mimeType :
|
||||
$this->request->getRequestHeader('content-type');
|
||||
|
||||
if (self::UPLOAD_RESUMABLE_TYPE == $uploadType) {
|
||||
$contentType = $mimeType;
|
||||
$postBody = is_string($meta) ? $meta : json_encode($meta);
|
||||
} else if (self::UPLOAD_MEDIA_TYPE == $uploadType) {
|
||||
$contentType = $mimeType;
|
||||
$postBody = $this->data;
|
||||
} else if (self::UPLOAD_MULTIPART_TYPE == $uploadType) {
|
||||
// This is a multipart/related upload.
|
||||
$boundary = $this->boundary ? $this->boundary : mt_rand();
|
||||
$boundary = str_replace('"', '', $boundary);
|
||||
$contentType = 'multipart/related; boundary=' . $boundary;
|
||||
$related = "--$boundary\r\n";
|
||||
$related .= "Content-Type: application/json; charset=UTF-8\r\n";
|
||||
$related .= "\r\n" . json_encode($meta) . "\r\n";
|
||||
$related .= "--$boundary\r\n";
|
||||
$related .= "Content-Type: $mimeType\r\n";
|
||||
$related .= "Content-Transfer-Encoding: base64\r\n";
|
||||
$related .= "\r\n" . base64_encode($this->data) . "\r\n";
|
||||
$related .= "--$boundary--";
|
||||
$postBody = $related;
|
||||
}
|
||||
|
||||
$this->request->setPostBody($postBody);
|
||||
|
||||
if (isset($contentType) && $contentType) {
|
||||
$contentTypeHeader['content-type'] = $contentType;
|
||||
$this->request->setRequestHeaders($contentTypeHeader);
|
||||
}
|
||||
}
|
||||
|
||||
private function transformToUploadUrl()
|
||||
{
|
||||
$base = $this->request->getBaseComponent();
|
||||
$this->request->setBaseComponent($base . '/upload');
|
||||
}
|
||||
|
||||
/**
|
||||
* Valid upload types:
|
||||
* - resumable (UPLOAD_RESUMABLE_TYPE)
|
||||
* - media (UPLOAD_MEDIA_TYPE)
|
||||
* - multipart (UPLOAD_MULTIPART_TYPE)
|
||||
* @param $meta
|
||||
* @return string
|
||||
* @visible for testing
|
||||
*/
|
||||
public function getUploadType($meta)
|
||||
{
|
||||
if ($this->resumable) {
|
||||
return self::UPLOAD_RESUMABLE_TYPE;
|
||||
}
|
||||
|
||||
if (false == $meta && $this->data) {
|
||||
return self::UPLOAD_MEDIA_TYPE;
|
||||
}
|
||||
|
||||
return self::UPLOAD_MULTIPART_TYPE;
|
||||
}
|
||||
|
||||
private function getResumeUri()
|
||||
{
|
||||
$result = null;
|
||||
$body = $this->request->getPostBody();
|
||||
if ($body) {
|
||||
$headers = array(
|
||||
'content-type' => 'application/json; charset=UTF-8',
|
||||
'content-length' => Google_Utils::getStrLen($body),
|
||||
'x-upload-content-type' => $this->mimeType,
|
||||
'x-upload-content-length' => $this->size,
|
||||
'expect' => '',
|
||||
);
|
||||
$this->request->setRequestHeaders($headers);
|
||||
}
|
||||
|
||||
$response = $this->client->getIo()->makeRequest($this->request);
|
||||
$location = $response->getResponseHeader('location');
|
||||
$code = $response->getResponseHttpCode();
|
||||
|
||||
if (200 == $code && true == $location) {
|
||||
return $location;
|
||||
}
|
||||
$message = $code;
|
||||
$body = @json_decode($response->getResponseBody());
|
||||
if (!empty( $body->error->errors ) ) {
|
||||
$message .= ': ';
|
||||
foreach ($body->error->errors as $error) {
|
||||
$message .= "{$error->domain}, {$error->message};";
|
||||
}
|
||||
$message = rtrim($message, ';');
|
||||
}
|
||||
|
||||
$error = "Failed to start the resumable upload (HTTP {$message})";
|
||||
$this->client->getLogger()->error($error);
|
||||
throw new Google_Exception($error); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* This class implements the RESTful transport of apiServiceRequest()'s
|
||||
*/
|
||||
class UDP_Google_Http_REST
|
||||
{
|
||||
/**
|
||||
* Executes a Google_Http_Request and (if applicable) automatically retries
|
||||
* when errors occur.
|
||||
*
|
||||
* @param UDP_Google_Client $client
|
||||
* @param Google_Http_Request $req
|
||||
* @return array decoded result
|
||||
* @throws UDP_Google_Service_Exception on server side error (ie: not authenticated,
|
||||
* invalid or malformed post body, invalid url)
|
||||
*/
|
||||
public static function execute(UDP_Google_Client $client, UDP_Google_Http_Request $req)
|
||||
{
|
||||
$runner = new UDP_Google_Task_Runner(
|
||||
$client,
|
||||
sprintf('%s %s', $req->getRequestMethod(), $req->getUrl()),
|
||||
array(__CLASS__, 'doExecute'),
|
||||
array($client, $req)
|
||||
);
|
||||
|
||||
return $runner->run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a Google_Http_Request
|
||||
*
|
||||
* @param UDP_Google_Client $client
|
||||
* @param Google_Http_Request $req
|
||||
* @return array decoded result
|
||||
* @throws UDP_Google_Service_Exception on server side error (ie: not authenticated,
|
||||
* invalid or malformed post body, invalid url)
|
||||
*/
|
||||
public static function doExecute(UDP_Google_Client $client, UDP_Google_Http_Request $req)
|
||||
{
|
||||
$httpRequest = $client->getIo()->makeRequest($req);
|
||||
$httpRequest->setExpectedClass($req->getExpectedClass());
|
||||
return self::decodeHttpResponse($httpRequest, $client);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode an HTTP Response.
|
||||
* @static
|
||||
* @throws UDP_Google_Service_Exception
|
||||
* @param Google_Http_Request $response The http response to be decoded.
|
||||
* @param UDP_Google_Client $client
|
||||
* @return mixed|null
|
||||
*/
|
||||
public static function decodeHttpResponse($response, UDP_Google_Client $client = null)
|
||||
{
|
||||
$code = $response->getResponseHttpCode();
|
||||
$body = $response->getResponseBody();
|
||||
$decoded = null;
|
||||
|
||||
if ((intVal($code)) >= 300) {
|
||||
$decoded = json_decode($body, true);
|
||||
$err = 'Error calling ' . $response->getRequestMethod() . ' ' . $response->getUrl();
|
||||
if (isset($decoded['error']) &&
|
||||
isset($decoded['error']['message']) &&
|
||||
isset($decoded['error']['code'])) {
|
||||
// if we're getting a json encoded error definition, use that instead of the raw response
|
||||
// body for improved readability
|
||||
$err .= ": ({$decoded['error']['code']}) {$decoded['error']['message']}";
|
||||
} else {
|
||||
$err .= ": ($code) $body";
|
||||
}
|
||||
|
||||
$errors = null;
|
||||
// Specific check for APIs which don't return error details, such as Blogger.
|
||||
if (isset($decoded['error']) && isset($decoded['error']['errors'])) {
|
||||
$errors = $decoded['error']['errors'];
|
||||
}
|
||||
|
||||
$map = null;
|
||||
if ($client) {
|
||||
$client->getLogger()->error(
|
||||
$err,
|
||||
array('code' => $code, 'errors' => $errors)
|
||||
);
|
||||
|
||||
$map = $client->getClassConfig(
|
||||
'UDP_Google_Service_Exception',
|
||||
'retry_map'
|
||||
);
|
||||
}
|
||||
throw new UDP_Google_Service_Exception($err, $code, null, $errors, $map); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
|
||||
}
|
||||
|
||||
// Only attempt to decode the response, if the response code wasn't (204) 'no content'
|
||||
if ($code != '204') {
|
||||
if ($response->getExpectedRaw()) {
|
||||
return $body;
|
||||
}
|
||||
|
||||
$decoded = json_decode($body, true);
|
||||
if ($decoded === null || $decoded === "") {
|
||||
# UpdraftPlus patch
|
||||
$error = "Invalid json in service response ($code): $body";
|
||||
if ($client) {
|
||||
$client->getLogger()->error($error);
|
||||
}
|
||||
throw new UDP_Google_Service_Exception($error); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
|
||||
}
|
||||
|
||||
if ($response->getExpectedClass()) {
|
||||
$class = $response->getExpectedClass();
|
||||
$decoded = new $class($decoded);
|
||||
}
|
||||
}
|
||||
return $decoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse/expand request parameters and create a fully qualified
|
||||
* request uri.
|
||||
* @static
|
||||
* @param string $servicePath
|
||||
* @param string $restPath
|
||||
* @param array $params
|
||||
* @return string $requestUrl
|
||||
*/
|
||||
public static function createRequestUri($servicePath, $restPath, $params)
|
||||
{
|
||||
$requestUrl = $servicePath . $restPath;
|
||||
$uriTemplateVars = array();
|
||||
$queryVars = array();
|
||||
foreach ($params as $paramName => $paramSpec) {
|
||||
if ($paramSpec['type'] == 'boolean') {
|
||||
$paramSpec['value'] = ($paramSpec['value']) ? 'true' : 'false';
|
||||
}
|
||||
if ($paramSpec['location'] == 'path') {
|
||||
$uriTemplateVars[$paramName] = $paramSpec['value'];
|
||||
} else if ($paramSpec['location'] == 'query') {
|
||||
if (isset($paramSpec['repeated']) && is_array($paramSpec['value'])) {
|
||||
foreach ($paramSpec['value'] as $value) {
|
||||
$queryVars[] = $paramName . '=' . rawurlencode(rawurldecode($value));
|
||||
}
|
||||
} else {
|
||||
$queryVars[] = $paramName . '=' . rawurlencode(rawurldecode($paramSpec['value']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($uriTemplateVars)) {
|
||||
$uriTemplateParser = new Google_Utils_URITemplate();
|
||||
$requestUrl = $uriTemplateParser->parse($requestUrl, $uriTemplateVars);
|
||||
}
|
||||
|
||||
if (count($queryVars)) {
|
||||
$requestUrl .= '?' . implode('&', $queryVars);
|
||||
}
|
||||
|
||||
return $requestUrl;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,505 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP Request to be executed by IO classes. Upon execution, the
|
||||
* responseHttpCode, responseHeaders and responseBody will be filled in.
|
||||
*
|
||||
* @author Chris Chabot <chabotc@google.com>
|
||||
* @author Chirag Shah <chirags@google.com>
|
||||
*
|
||||
*/
|
||||
class UDP_Google_Http_Request
|
||||
{
|
||||
const GZIP_UA = " (gzip)";
|
||||
|
||||
private $batchHeaders = array(
|
||||
'Content-Type' => 'application/http',
|
||||
'Content-Transfer-Encoding' => 'binary',
|
||||
'MIME-Version' => '1.0',
|
||||
);
|
||||
|
||||
protected $queryParams;
|
||||
protected $requestMethod;
|
||||
protected $requestHeaders;
|
||||
protected $baseComponent = null;
|
||||
protected $path;
|
||||
protected $postBody;
|
||||
// UpdraftPlus tweak: added default value to prevent type notices
|
||||
protected $userAgent = '';
|
||||
protected $canGzip = null;
|
||||
|
||||
protected $responseHttpCode;
|
||||
protected $responseHeaders;
|
||||
protected $responseBody;
|
||||
|
||||
protected $expectedClass;
|
||||
protected $expectedRaw = false;
|
||||
|
||||
public $accessKey;
|
||||
|
||||
public function __construct(
|
||||
$url,
|
||||
$method = 'GET',
|
||||
$headers = array(),
|
||||
$postBody = null
|
||||
) {
|
||||
$this->setUrl($url);
|
||||
$this->setRequestMethod($method);
|
||||
$this->setRequestHeaders($headers);
|
||||
$this->setPostBody($postBody);
|
||||
}
|
||||
|
||||
/**
|
||||
* Misc function that returns the base url component of the $url
|
||||
* used by the OAuth signing class to calculate the base string
|
||||
* @return string The base url component of the $url.
|
||||
*/
|
||||
public function getBaseComponent()
|
||||
{
|
||||
return $this->baseComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the base URL that path and query parameters will be added to.
|
||||
* @param $baseComponent string
|
||||
*/
|
||||
public function setBaseComponent($baseComponent)
|
||||
{
|
||||
$this->baseComponent = $baseComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable support for gzipped responses with this request.
|
||||
*/
|
||||
public function enableGzip()
|
||||
{
|
||||
$this->setRequestHeaders(array("Accept-Encoding" => "gzip"));
|
||||
$this->canGzip = true;
|
||||
$this->setUserAgent($this->userAgent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable support for gzip responses with this request.
|
||||
*/
|
||||
public function disableGzip()
|
||||
{
|
||||
if (
|
||||
isset($this->requestHeaders['accept-encoding']) &&
|
||||
$this->requestHeaders['accept-encoding'] == "gzip"
|
||||
) {
|
||||
unset($this->requestHeaders['accept-encoding']);
|
||||
}
|
||||
$this->canGzip = false;
|
||||
$this->userAgent = str_replace(self::GZIP_UA, "", $this->userAgent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can this request accept a gzip response?
|
||||
* @return bool
|
||||
*/
|
||||
public function canGzip()
|
||||
{
|
||||
return $this->canGzip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Misc function that returns an array of the query parameters of the current
|
||||
* url used by the OAuth signing class to calculate the signature
|
||||
* @return array Query parameters in the query string.
|
||||
*/
|
||||
public function getQueryParams()
|
||||
{
|
||||
return $this->queryParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new query parameter.
|
||||
* @param $key - string to set, does not need to be URL encoded
|
||||
* @param $value - string to set, does not need to be URL encoded
|
||||
*/
|
||||
public function setQueryParam($key, $value)
|
||||
{
|
||||
$this->queryParams[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string HTTP Response Code.
|
||||
*/
|
||||
public function getResponseHttpCode()
|
||||
{
|
||||
return (int) $this->responseHttpCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $responseHttpCode HTTP Response Code.
|
||||
*/
|
||||
public function setResponseHttpCode($responseHttpCode)
|
||||
{
|
||||
$this->responseHttpCode = $responseHttpCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $responseHeaders (array) HTTP Response Headers.
|
||||
*/
|
||||
public function getResponseHeaders()
|
||||
{
|
||||
return $this->responseHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string HTTP Response Body
|
||||
*/
|
||||
public function getResponseBody()
|
||||
{
|
||||
return $this->responseBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the class the response to this request should expect.
|
||||
*
|
||||
* @param $class string the class name
|
||||
*/
|
||||
public function setExpectedClass($class)
|
||||
{
|
||||
$this->expectedClass = $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the expected class the response should expect.
|
||||
* @return string class name
|
||||
*/
|
||||
public function getExpectedClass()
|
||||
{
|
||||
return $this->expectedClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable expected raw response
|
||||
*/
|
||||
public function enableExpectedRaw()
|
||||
{
|
||||
$this->expectedRaw = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable expected raw response
|
||||
*/
|
||||
public function disableExpectedRaw()
|
||||
{
|
||||
$this->expectedRaw = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expected raw response or not.
|
||||
* @return boolean expected raw response
|
||||
*/
|
||||
public function getExpectedRaw()
|
||||
{
|
||||
return $this->expectedRaw;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $headers The HTTP response headers
|
||||
* to be normalized.
|
||||
*/
|
||||
public function setResponseHeaders($headers)
|
||||
{
|
||||
$headers = Google_Utils::normalize($headers);
|
||||
if ($this->responseHeaders) {
|
||||
$headers = array_merge($this->responseHeaders, $headers);
|
||||
}
|
||||
|
||||
$this->responseHeaders = $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return array|boolean Returns the requested HTTP header or
|
||||
* false if unavailable.
|
||||
*/
|
||||
public function getResponseHeader($key)
|
||||
{
|
||||
return isset($this->responseHeaders[$key])
|
||||
? $this->responseHeaders[$key]
|
||||
: false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $responseBody The HTTP response body.
|
||||
*/
|
||||
public function setResponseBody($responseBody)
|
||||
{
|
||||
$this->responseBody = $responseBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string $url The request URL.
|
||||
*/
|
||||
public function getUrl()
|
||||
{
|
||||
return $this->baseComponent . $this->path .
|
||||
(count($this->queryParams) ?
|
||||
"?" . $this->buildQuery($this->queryParams) :
|
||||
'');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string $method HTTP Request Method.
|
||||
*/
|
||||
public function getRequestMethod()
|
||||
{
|
||||
return $this->requestMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array $headers HTTP Request Headers.
|
||||
*/
|
||||
public function getRequestHeaders()
|
||||
{
|
||||
return $this->requestHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return array|boolean Returns the requested HTTP header or
|
||||
* false if unavailable.
|
||||
*/
|
||||
public function getRequestHeader($key)
|
||||
{
|
||||
return isset($this->requestHeaders[$key])
|
||||
? $this->requestHeaders[$key]
|
||||
: false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string $postBody HTTP Request Body.
|
||||
*/
|
||||
public function getPostBody()
|
||||
{
|
||||
return $this->postBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url the url to set
|
||||
*/
|
||||
public function setUrl($url)
|
||||
{
|
||||
if (substr($url, 0, 4) != 'http') {
|
||||
// Force the path become relative.
|
||||
if (substr($url, 0, 1) !== '/') {
|
||||
$url = '/' . $url;
|
||||
}
|
||||
}
|
||||
$parts = parse_url($url);
|
||||
if (isset($parts['host'])) {
|
||||
$this->baseComponent = sprintf(
|
||||
"%s%s%s",
|
||||
isset($parts['scheme']) ? $parts['scheme'] . "://" : '',
|
||||
isset($parts['host']) ? $parts['host'] : '',
|
||||
isset($parts['port']) ? ":" . $parts['port'] : ''
|
||||
);
|
||||
}
|
||||
$this->path = isset($parts['path']) ? $parts['path'] : '';
|
||||
$this->queryParams = array();
|
||||
if (isset($parts['query'])) {
|
||||
$this->queryParams = $this->parseQuery($parts['query']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $method Set he HTTP Method and normalize
|
||||
* it to upper-case, as required by HTTP.
|
||||
*
|
||||
*/
|
||||
public function setRequestMethod($method)
|
||||
{
|
||||
$this->requestMethod = strtoupper($method);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $headers The HTTP request headers
|
||||
* to be set and normalized.
|
||||
*/
|
||||
public function setRequestHeaders($headers)
|
||||
{
|
||||
$headers = Google_Utils::normalize($headers);
|
||||
if ($this->requestHeaders) {
|
||||
$headers = array_merge($this->requestHeaders, $headers);
|
||||
}
|
||||
$this->requestHeaders = $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $postBody the postBody to set
|
||||
*/
|
||||
public function setPostBody($postBody)
|
||||
{
|
||||
$this->postBody = $postBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the User-Agent Header.
|
||||
* @param string $userAgent The User-Agent.
|
||||
*/
|
||||
public function setUserAgent($userAgent)
|
||||
{
|
||||
$this->userAgent = $userAgent;
|
||||
if ($this->canGzip) {
|
||||
$this->userAgent = $userAgent . self::GZIP_UA;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string The User-Agent.
|
||||
*/
|
||||
public function getUserAgent()
|
||||
{
|
||||
return $this->userAgent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a cache key depending on if this was an OAuth signed request
|
||||
* in which case it will use the non-signed url and access key to make this
|
||||
* cache key unique per authenticated user, else use the plain request url
|
||||
* @return string The md5 hash of the request cache key.
|
||||
*/
|
||||
public function getCacheKey()
|
||||
{
|
||||
$key = $this->getUrl();
|
||||
|
||||
if (isset($this->accessKey)) {
|
||||
$key .= $this->accessKey;
|
||||
}
|
||||
|
||||
if (isset($this->requestHeaders['authorization'])) {
|
||||
$key .= $this->requestHeaders['authorization'];
|
||||
}
|
||||
|
||||
return md5($key);
|
||||
}
|
||||
|
||||
public function getParsedCacheControl()
|
||||
{
|
||||
$parsed = array();
|
||||
$rawCacheControl = $this->getResponseHeader('cache-control');
|
||||
if ($rawCacheControl) {
|
||||
$rawCacheControl = str_replace(', ', '&', $rawCacheControl);
|
||||
parse_str($rawCacheControl, $parsed);
|
||||
}
|
||||
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @return string A string representation of the HTTP Request.
|
||||
*/
|
||||
public function toBatchString($id)
|
||||
{
|
||||
$str = '';
|
||||
$path = parse_url($this->getUrl(), PHP_URL_PATH) . "?" .
|
||||
http_build_query($this->queryParams);
|
||||
$str .= $this->getRequestMethod() . ' ' . $path . " HTTP/1.1\n";
|
||||
|
||||
foreach ($this->getRequestHeaders() as $key => $val) {
|
||||
$str .= $key . ': ' . $val . "\n";
|
||||
}
|
||||
|
||||
if ($this->getPostBody()) {
|
||||
$str .= "\n";
|
||||
$str .= $this->getPostBody();
|
||||
}
|
||||
|
||||
$headers = '';
|
||||
foreach ($this->batchHeaders as $key => $val) {
|
||||
$headers .= $key . ': ' . $val . "\n";
|
||||
}
|
||||
|
||||
$headers .= "Content-ID: $id\n";
|
||||
$str = $headers . "\n" . $str;
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Our own version of parse_str that allows for multiple variables
|
||||
* with the same name.
|
||||
* @param $string - the query string to parse
|
||||
*/
|
||||
private function parseQuery($string)
|
||||
{
|
||||
$return = array();
|
||||
$parts = explode("&", $string);
|
||||
foreach ($parts as $part) {
|
||||
list($key, $value) = explode('=', $part, 2);
|
||||
$value = urldecode($value);
|
||||
if (isset($return[$key])) {
|
||||
if (!is_array($return[$key])) {
|
||||
$return[$key] = array($return[$key]);
|
||||
}
|
||||
$return[$key][] = $value;
|
||||
} else {
|
||||
$return[$key] = $value;
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* A version of build query that allows for multiple
|
||||
* duplicate keys.
|
||||
* @param $parts array of key value pairs
|
||||
*/
|
||||
private function buildQuery($parts)
|
||||
{
|
||||
$return = array();
|
||||
foreach ($parts as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $v) {
|
||||
$return[] = urlencode($key) . "=" . urlencode($v);
|
||||
}
|
||||
} else {
|
||||
$return[] = urlencode($key) . "=" . urlencode($value);
|
||||
}
|
||||
}
|
||||
return implode('&', $return);
|
||||
}
|
||||
|
||||
/**
|
||||
* If we're POSTing and have no body to send, we can send the query
|
||||
* parameters in there, which avoids length issues with longer query
|
||||
* params.
|
||||
*/
|
||||
public function maybeMoveParametersToBody()
|
||||
{
|
||||
if ($this->getRequestMethod() == "POST" && empty($this->postBody)) {
|
||||
$this->setRequestHeaders(
|
||||
array(
|
||||
"content-type" =>
|
||||
"application/x-www-form-urlencoded; charset=UTF-8"
|
||||
)
|
||||
);
|
||||
$this->setPostBody($this->buildQuery($this->queryParams));
|
||||
$this->queryParams = array();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Abstract IO base class
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
abstract class UDP_Google_IO_Abstract
|
||||
{
|
||||
const UNKNOWN_CODE = 0;
|
||||
const FORM_URLENCODED = 'application/x-www-form-urlencoded';
|
||||
private static $CONNECTION_ESTABLISHED_HEADERS = array(
|
||||
"HTTP/1.0 200 Connection established\r\n\r\n",
|
||||
"HTTP/1.1 200 Connection established\r\n\r\n",
|
||||
);
|
||||
private static $ENTITY_HTTP_METHODS = array("POST" => null, "PUT" => null);
|
||||
private static $HOP_BY_HOP = array(
|
||||
'connection' => true,
|
||||
'keep-alive' => true,
|
||||
'proxy-authenticate' => true,
|
||||
'proxy-authorization' => true,
|
||||
'te' => true,
|
||||
'trailers' => true,
|
||||
'transfer-encoding' => true,
|
||||
'upgrade' => true
|
||||
);
|
||||
|
||||
|
||||
/** @var UDP_Google_Client */
|
||||
protected $client;
|
||||
|
||||
public function __construct(UDP_Google_Client $client)
|
||||
{
|
||||
$this->client = $client;
|
||||
$timeout = $client->getClassConfig('UDP_Google_IO_Abstract', 'request_timeout_seconds');
|
||||
if ($timeout > 0) {
|
||||
$this->setTimeout($timeout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a Google_Http_Request
|
||||
* @param Google_Http_Request $request the http request to be executed
|
||||
* @return array containing response headers, body, and http code
|
||||
* @throws UDP_Google_IO_Exception on curl or IO error
|
||||
*/
|
||||
abstract public function executeRequest(UDP_Google_Http_Request $request);
|
||||
|
||||
/**
|
||||
* Set options that update the transport implementation's behavior.
|
||||
* @param $options
|
||||
*/
|
||||
abstract public function setOptions($options);
|
||||
|
||||
/**
|
||||
* Set the maximum request time in seconds.
|
||||
* @param $timeout in seconds
|
||||
*/
|
||||
abstract public function setTimeout($timeout);
|
||||
|
||||
/**
|
||||
* Get the maximum request time in seconds.
|
||||
* @return timeout in seconds
|
||||
*/
|
||||
abstract public function getTimeout();
|
||||
|
||||
/**
|
||||
* Test for the presence of a cURL header processing bug
|
||||
*
|
||||
* The cURL bug was present in versions prior to 7.30.0 and caused the header
|
||||
* length to be miscalculated when a "Connection established" header added by
|
||||
* some proxies was present.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
abstract protected function needsQuirk();
|
||||
|
||||
/**
|
||||
* @visible for testing.
|
||||
* Cache the response to an HTTP request if it is cacheable.
|
||||
* @param Google_Http_Request $request
|
||||
* @return bool Returns true if the insertion was successful.
|
||||
* Otherwise, return false.
|
||||
*/
|
||||
public function setCachedRequest(UDP_Google_Http_Request $request)
|
||||
{
|
||||
// Determine if the request is cacheable.
|
||||
if (Google_Http_CacheParser::isResponseCacheable($request)) {
|
||||
$this->client->getCache()->set($request->getCacheKey(), $request);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an HTTP Request
|
||||
*
|
||||
* @param Google_Http_Request $request the http request to be executed
|
||||
* @return Google_Http_Request http request with the response http code,
|
||||
* response headers and response body filled in
|
||||
* @throws UDP_Google_IO_Exception on curl or IO error
|
||||
*/
|
||||
public function makeRequest(UDP_Google_Http_Request $request)
|
||||
{
|
||||
// First, check to see if we have a valid cached version.
|
||||
$cached = $this->getCachedRequest($request);
|
||||
if ($cached !== false && $cached instanceof UDP_Google_Http_Request) {
|
||||
if (!$this->checkMustRevalidateCachedRequest($cached, $request)) {
|
||||
return $cached;
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists($request->getRequestMethod(), self::$ENTITY_HTTP_METHODS)) {
|
||||
$request = $this->processEntityRequest($request);
|
||||
}
|
||||
|
||||
list($responseData, $responseHeaders, $respHttpCode) = $this->executeRequest($request);
|
||||
|
||||
if ($respHttpCode == 304 && $cached) {
|
||||
// If the server responded NOT_MODIFIED, return the cached request.
|
||||
$this->updateCachedRequest($cached, $responseHeaders);
|
||||
return $cached;
|
||||
}
|
||||
|
||||
if (!isset($responseHeaders['Date']) && !isset($responseHeaders['date'])) {
|
||||
$responseHeaders['date'] = date("r");
|
||||
}
|
||||
|
||||
$request->setResponseHttpCode($respHttpCode);
|
||||
$request->setResponseHeaders($responseHeaders);
|
||||
$request->setResponseBody($responseData);
|
||||
// Store the request in cache (the function checks to see if the request
|
||||
// can actually be cached)
|
||||
$this->setCachedRequest($request);
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @visible for testing.
|
||||
* @param Google_Http_Request $request
|
||||
* @return Google_Http_Request|bool Returns the cached object or
|
||||
* false if the operation was unsuccessful.
|
||||
*/
|
||||
public function getCachedRequest(UDP_Google_Http_Request $request)
|
||||
{
|
||||
if (false === Google_Http_CacheParser::isRequestCacheable($request)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->client->getCache()->get($request->getCacheKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* @visible for testing
|
||||
* Process an http request that contains an enclosed entity.
|
||||
* @param Google_Http_Request $request
|
||||
* @return Google_Http_Request Processed request with the enclosed entity.
|
||||
*/
|
||||
public function processEntityRequest(UDP_Google_Http_Request $request)
|
||||
{
|
||||
$postBody = $request->getPostBody();
|
||||
$contentType = $request->getRequestHeader("content-type");
|
||||
|
||||
// Set the default content-type as application/x-www-form-urlencoded.
|
||||
if (false == $contentType) {
|
||||
$contentType = self::FORM_URLENCODED;
|
||||
$request->setRequestHeaders(array('content-type' => $contentType));
|
||||
}
|
||||
|
||||
// Force the payload to match the content-type asserted in the header.
|
||||
if ($contentType == self::FORM_URLENCODED && is_array($postBody)) {
|
||||
$postBody = http_build_query($postBody, '', '&');
|
||||
$request->setPostBody($postBody);
|
||||
}
|
||||
|
||||
// Make sure the content-length header is set.
|
||||
if (!$postBody || is_string($postBody)) {
|
||||
$postsLength = strlen($postBody);
|
||||
$request->setRequestHeaders(array('content-length' => $postsLength));
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an already cached request must be revalidated, and if so update
|
||||
* the request with the correct ETag headers.
|
||||
* @param Google_Http_Request $cached A previously cached response.
|
||||
* @param Google_Http_Request $request The outbound request.
|
||||
* return bool If the cached object needs to be revalidated, false if it is
|
||||
* still current and can be re-used.
|
||||
*/
|
||||
protected function checkMustRevalidateCachedRequest($cached, $request)
|
||||
{
|
||||
if (Google_Http_CacheParser::mustRevalidate($cached)) {
|
||||
$addHeaders = array();
|
||||
if ($cached->getResponseHeader('etag')) {
|
||||
// [13.3.4] If an entity tag has been provided by the origin server,
|
||||
// we must use that entity tag in any cache-conditional request.
|
||||
$addHeaders['If-None-Match'] = $cached->getResponseHeader('etag');
|
||||
} elseif ($cached->getResponseHeader('date')) {
|
||||
$addHeaders['If-Modified-Since'] = $cached->getResponseHeader('date');
|
||||
}
|
||||
|
||||
$request->setRequestHeaders($addHeaders);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a cached request, using the headers from the last response.
|
||||
* @param Google_Http_Request $cached A previously cached response.
|
||||
* @param mixed Associative array of response headers from the last request.
|
||||
*/
|
||||
protected function updateCachedRequest($cached, $responseHeaders)
|
||||
{
|
||||
$hopByHop = self::$HOP_BY_HOP;
|
||||
if (!empty($responseHeaders['connection'])) {
|
||||
$connectionHeaders = array_map(
|
||||
'strtolower',
|
||||
array_filter(
|
||||
array_map('trim', explode(',', $responseHeaders['connection']))
|
||||
)
|
||||
);
|
||||
$hopByHop += array_fill_keys($connectionHeaders, true);
|
||||
}
|
||||
|
||||
$endToEnd = array_diff_key($responseHeaders, $hopByHop);
|
||||
$cached->setResponseHeaders($endToEnd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by the IO lib and also the batch processing.
|
||||
*
|
||||
* @param $respData
|
||||
* @param $headerSize
|
||||
* @return array
|
||||
*/
|
||||
public function parseHttpResponse($respData, $headerSize)
|
||||
{
|
||||
// check proxy header
|
||||
foreach (self::$CONNECTION_ESTABLISHED_HEADERS as $established_header) {
|
||||
if (stripos($respData, $established_header) !== false) {
|
||||
// existed, remove it
|
||||
$respData = str_ireplace($established_header, '', $respData);
|
||||
// Subtract the proxy header size unless the cURL bug prior to 7.30.0
|
||||
// is present which prevented the proxy header size from being taken into
|
||||
// account.
|
||||
if (!$this->needsQuirk()) {
|
||||
$headerSize -= strlen($established_header);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($headerSize) {
|
||||
$responseBody = substr($respData, $headerSize);
|
||||
$responseHeaders = substr($respData, 0, $headerSize);
|
||||
} else {
|
||||
$responseSegments = explode("\r\n\r\n", $respData, 2);
|
||||
$responseHeaders = $responseSegments[0];
|
||||
$responseBody = isset($responseSegments[1]) ? $responseSegments[1] :
|
||||
null;
|
||||
}
|
||||
|
||||
$responseHeaders = $this->getHttpResponseHeaders($responseHeaders);
|
||||
return array($responseHeaders, $responseBody);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse out headers from raw headers
|
||||
* @param rawHeaders array or string
|
||||
* @return array
|
||||
*/
|
||||
public function getHttpResponseHeaders($rawHeaders)
|
||||
{
|
||||
if (is_array($rawHeaders)) {
|
||||
return $this->parseArrayHeaders($rawHeaders);
|
||||
} else {
|
||||
return $this->parseStringHeaders($rawHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
private function parseStringHeaders($rawHeaders)
|
||||
{
|
||||
$headers = array();
|
||||
$responseHeaderLines = explode("\r\n", $rawHeaders);
|
||||
foreach ($responseHeaderLines as $headerLine) {
|
||||
if ($headerLine && strpos($headerLine, ':') !== false) {
|
||||
list($header, $value) = explode(': ', $headerLine, 2);
|
||||
$header = strtolower($header);
|
||||
if (isset($headers[$header])) {
|
||||
$headers[$header] .= "\n" . $value;
|
||||
} else {
|
||||
$headers[$header] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $headers;
|
||||
}
|
||||
|
||||
private function parseArrayHeaders($rawHeaders)
|
||||
{
|
||||
$header_count = count($rawHeaders);
|
||||
$headers = array();
|
||||
|
||||
for ($i = 0; $i < $header_count; $i++) {
|
||||
$header = $rawHeaders[$i];
|
||||
// Times will have colons in - so we just want the first match.
|
||||
$header_parts = explode(': ', $header, 2);
|
||||
if (count($header_parts) == 2) {
|
||||
$headers[strtolower($header_parts[0])] = $header_parts[1];
|
||||
}
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2014 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Curl based implementation of Google_IO.
|
||||
*
|
||||
* @author Stuart Langley <slangley@google.com>
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
class UDP_Google_IO_Curl extends UDP_Google_IO_Abstract
|
||||
{
|
||||
// cURL hex representation of version 7.30.0
|
||||
const NO_QUIRK_VERSION = 0x071E00;
|
||||
|
||||
private $options = array();
|
||||
|
||||
public function __construct(UDP_Google_Client $client)
|
||||
{
|
||||
if (!extension_loaded('curl')) {
|
||||
$error = 'The cURL IO handler requires the cURL extension to be enabled';
|
||||
$client->getLogger()->critical($error);
|
||||
throw new UDP_Google_IO_Exception($error); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
|
||||
}
|
||||
|
||||
parent::__construct($client);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an HTTP Request
|
||||
*
|
||||
* @param Google_Http_Request $request the http request to be executed
|
||||
* @return array containing response headers, body, and http code
|
||||
* @throws UDP_Google_IO_Exception on curl or IO error
|
||||
*/
|
||||
public function executeRequest(UDP_Google_Http_Request $request)
|
||||
{
|
||||
$curl = curl_init();
|
||||
|
||||
if ($request->getPostBody()) {
|
||||
curl_setopt($curl, CURLOPT_POSTFIELDS, $request->getPostBody());
|
||||
}
|
||||
|
||||
$requestHeaders = $request->getRequestHeaders();
|
||||
if ($requestHeaders && is_array($requestHeaders)) {
|
||||
$curlHeaders = array();
|
||||
foreach ($requestHeaders as $k => $v) {
|
||||
$curlHeaders[] = "$k: $v";
|
||||
}
|
||||
curl_setopt($curl, CURLOPT_HTTPHEADER, $curlHeaders);
|
||||
}
|
||||
curl_setopt($curl, CURLOPT_URL, $request->getUrl());
|
||||
|
||||
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $request->getRequestMethod());
|
||||
curl_setopt($curl, CURLOPT_USERAGENT, $request->getUserAgent());
|
||||
|
||||
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false);
|
||||
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
|
||||
// 1 is CURL_SSLVERSION_TLSv1, which is not always defined in PHP.
|
||||
// UpdraftPlus patch
|
||||
// The SDK leaves this on the default setting in later releases
|
||||
// curl_setopt($curl, CURLOPT_SSLVERSION, 1);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($curl, CURLOPT_HEADER, true);
|
||||
|
||||
if ($request->canGzip()) {
|
||||
curl_setopt($curl, CURLOPT_ENCODING, 'gzip,deflate');
|
||||
}
|
||||
|
||||
$options = $this->client->getClassConfig('UDP_Google_IO_Curl', 'options');
|
||||
if (is_array($options)) {
|
||||
$this->setOptions($options);
|
||||
}
|
||||
|
||||
foreach ($this->options as $key => $var) {
|
||||
curl_setopt($curl, $key, $var);
|
||||
}
|
||||
|
||||
if (!isset($this->options[CURLOPT_CAINFO])) {
|
||||
curl_setopt($curl, CURLOPT_CAINFO, dirname(__FILE__) . '/cacerts.pem');
|
||||
}
|
||||
|
||||
$this->client->getLogger()->debug(
|
||||
'cURL request',
|
||||
array(
|
||||
'url' => $request->getUrl(),
|
||||
'method' => $request->getRequestMethod(),
|
||||
'headers' => $requestHeaders,
|
||||
'body' => $request->getPostBody()
|
||||
)
|
||||
);
|
||||
|
||||
$response = curl_exec($curl);
|
||||
if ($response === false) {
|
||||
$error = curl_error($curl);
|
||||
$code = curl_errno($curl);
|
||||
$map = $this->client->getClassConfig('UDP_Google_IO_Exception', 'retry_map');
|
||||
|
||||
$this->client->getLogger()->error('cURL ' . $error);
|
||||
throw new UDP_Google_IO_Exception($error, $code, null, $map); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
|
||||
}
|
||||
$headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
|
||||
|
||||
list($responseHeaders, $responseBody) = $this->parseHttpResponse($response, $headerSize);
|
||||
$responseCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||
|
||||
$this->client->getLogger()->debug(
|
||||
'cURL response',
|
||||
array(
|
||||
'code' => $responseCode,
|
||||
'headers' => $responseHeaders,
|
||||
'body' => $responseBody,
|
||||
)
|
||||
);
|
||||
|
||||
return array($responseBody, $responseHeaders, $responseCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set options that update the transport implementation's behavior.
|
||||
* @param $options
|
||||
*/
|
||||
public function setOptions($options)
|
||||
{
|
||||
$this->options = $options + $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum request time in seconds.
|
||||
* @param $timeout in seconds
|
||||
*/
|
||||
public function setTimeout($timeout)
|
||||
{
|
||||
// Since this timeout is really for putting a bound on the time
|
||||
// we'll set them both to the same. If you need to specify a longer
|
||||
// CURLOPT_TIMEOUT, or a higher CONNECTTIMEOUT, the best thing to
|
||||
// do is use the setOptions method for the values individually.
|
||||
$this->options[CURLOPT_CONNECTTIMEOUT] = $timeout;
|
||||
$this->options[CURLOPT_TIMEOUT] = $timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum request time in seconds.
|
||||
* @return timeout in seconds
|
||||
*/
|
||||
public function getTimeout()
|
||||
{
|
||||
return $this->options[CURLOPT_TIMEOUT];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for the presence of a cURL header processing bug
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function needsQuirk()
|
||||
{
|
||||
$ver = curl_version();
|
||||
$versionNum = $ver['version_number'];
|
||||
return $versionNum < UDP_Google_IO_Curl::NO_QUIRK_VERSION;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
class UDP_Google_IO_Exception extends Google_Exception implements Google_Task_Retryable
|
||||
{
|
||||
/**
|
||||
* @var array $retryMap Map of errors with retry counts.
|
||||
*/
|
||||
private $retryMap = array();
|
||||
|
||||
/**
|
||||
* Creates a new IO exception with an optional retry map.
|
||||
*
|
||||
* @param string $message
|
||||
* @param int $code
|
||||
* @param Exception|null $previous
|
||||
* @param array|null $retryMap Map of errors with retry counts.
|
||||
*/
|
||||
public function __construct(
|
||||
$message,
|
||||
$code = 0,
|
||||
Exception $previous = null,
|
||||
array $retryMap = null
|
||||
) {
|
||||
if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
|
||||
parent::__construct($message, $code, $previous);
|
||||
} else {
|
||||
parent::__construct($message, $code);
|
||||
}
|
||||
|
||||
if (is_array($retryMap)) {
|
||||
$this->retryMap = $retryMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of times the associated task can be retried.
|
||||
*
|
||||
* NOTE: -1 is returned if the task can be retried indefinitely
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function allowedRetries()
|
||||
{
|
||||
if (isset($this->retryMap[$this->code])) {
|
||||
return $this->retryMap[$this->code];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Http Streams based implementation of Google_IO.
|
||||
*
|
||||
* @author Stuart Langley <slangley@google.com>
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
class UDP_Google_IO_Stream extends UDP_Google_IO_Abstract
|
||||
{
|
||||
const TIMEOUT = "timeout";
|
||||
const ZLIB = "compress.zlib://";
|
||||
private $options = array();
|
||||
private $trappedErrorNumber;
|
||||
private $trappedErrorString;
|
||||
|
||||
private static $DEFAULT_HTTP_CONTEXT = array(
|
||||
"follow_location" => 0,
|
||||
"ignore_errors" => 1,
|
||||
);
|
||||
|
||||
private static $DEFAULT_SSL_CONTEXT = array(
|
||||
"verify_peer" => true,
|
||||
);
|
||||
|
||||
public function __construct(UDP_Google_Client $client)
|
||||
{
|
||||
if (!ini_get('allow_url_fopen')) {
|
||||
$error = 'The stream IO handler requires the allow_url_fopen runtime ' .
|
||||
'configuration to be enabled';
|
||||
$client->getLogger()->critical($error);
|
||||
throw new UDP_Google_IO_Exception($error); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
|
||||
}
|
||||
|
||||
parent::__construct($client);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an HTTP Request
|
||||
*
|
||||
* @param Google_Http_Request $request the http request to be executed
|
||||
* @return array containing response headers, body, and http code
|
||||
* @throws UDP_Google_IO_Exception on curl or IO error
|
||||
*/
|
||||
public function executeRequest(UDP_Google_Http_Request $request)
|
||||
{
|
||||
$default_options = stream_context_get_options(stream_context_get_default());
|
||||
|
||||
$requestHttpContext = array_key_exists('http', $default_options) ?
|
||||
$default_options['http'] : array();
|
||||
|
||||
if ($request->getPostBody()) {
|
||||
$requestHttpContext["content"] = $request->getPostBody();
|
||||
}
|
||||
|
||||
$requestHeaders = $request->getRequestHeaders();
|
||||
if ($requestHeaders && is_array($requestHeaders)) {
|
||||
$headers = "";
|
||||
foreach ($requestHeaders as $k => $v) {
|
||||
$headers .= "$k: $v\r\n";
|
||||
}
|
||||
$requestHttpContext["header"] = $headers;
|
||||
}
|
||||
|
||||
$requestHttpContext["method"] = $request->getRequestMethod();
|
||||
$requestHttpContext["user_agent"] = $request->getUserAgent();
|
||||
|
||||
$requestSslContext = array_key_exists('ssl', $default_options) ?
|
||||
$default_options['ssl'] : array();
|
||||
|
||||
# UpdraftPlus patch
|
||||
// if (!array_key_exists("cafile", $requestSslContext)) {
|
||||
// $requestSslContext["cafile"] = dirname(__FILE__) . '/cacerts.pem';
|
||||
// }
|
||||
|
||||
$url = $request->getUrl();
|
||||
|
||||
if (preg_match('#^https?://([^/]+)/#', $url, $umatches)) { $cname = $umatches[1]; } else { $cname = false; }
|
||||
|
||||
# UpdraftPlus patch
|
||||
// Added
|
||||
if (empty($this->options['disable_verify_peer'])) {
|
||||
$requestSslContext['verify_peer'] = true;
|
||||
if (version_compare(PHP_VERSION, '5.6.0', '>=')) {
|
||||
if (!empty($cname)) $requestSslContext['peer_name'] = $cname;
|
||||
} else {
|
||||
if (!empty($cname)) {
|
||||
$requestSslContext['CN_match'] = $cname;
|
||||
$retry_on_fail = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$requestSslContext['allow_self_signed'] = true;
|
||||
}
|
||||
if (!empty($this->options['cafile'])) $requestSslContext['cafile'] = $this->options['cafile'];
|
||||
if (!empty($this->options['proxy'])) {
|
||||
$requestHttpContext['proxy'] = $this->options['proxy'];
|
||||
$requestHttpContext['request_fulluri'] = true;
|
||||
}
|
||||
$options = array(
|
||||
"http" => array_merge(
|
||||
self::$DEFAULT_HTTP_CONTEXT,
|
||||
$requestHttpContext
|
||||
),
|
||||
"ssl" => array_merge(
|
||||
# UpdraftPlus patch
|
||||
// self::$DEFAULT_SSL_CONTEXT,
|
||||
$requestSslContext
|
||||
)
|
||||
);
|
||||
|
||||
$context = stream_context_create($options);
|
||||
|
||||
# UpdraftPlus patch
|
||||
// $url = $request->getUrl();
|
||||
|
||||
if ($request->canGzip()) {
|
||||
$url = self::ZLIB . $url;
|
||||
}
|
||||
|
||||
$this->client->getLogger()->debug(
|
||||
'Stream request',
|
||||
array(
|
||||
'url' => $url,
|
||||
'method' => $request->getRequestMethod(),
|
||||
'headers' => $requestHeaders,
|
||||
'body' => $request->getPostBody()
|
||||
)
|
||||
);
|
||||
|
||||
// We are trapping any thrown errors in this method only and
|
||||
// throwing an exception.
|
||||
$this->trappedErrorNumber = null;
|
||||
$this->trappedErrorString = null;
|
||||
|
||||
// START - error trap.
|
||||
set_error_handler(array($this, 'trapError'));
|
||||
$fh = fopen($url, 'r', false, $context);
|
||||
|
||||
# UpdraftPLus patch
|
||||
if (!$fh && isset($retry_on_fail) && !empty($cname) && 'www.googleapis.com' == $cname) {
|
||||
// Reset
|
||||
$this->trappedErrorNumber = null;
|
||||
$this->trappedErrorString = null;
|
||||
global $updraftplus;
|
||||
$updraftplus->log("Using Stream, and fopen failed; retrying different CN match to try to overcome");
|
||||
// www.googleapis.com does not match the cert now being presented - *.storage.googleapis.com; presumably, PHP's stream handler isn't handling alternative names properly. Rather than turn off all verification, let's retry with a new name to match.
|
||||
$options['ssl']['CN_match'] = 'www.storage.googleapis.com';
|
||||
$context = stream_context_create($options);
|
||||
$fh = fopen($url, 'r', false, $context);
|
||||
}
|
||||
|
||||
restore_error_handler();
|
||||
// END - error trap.
|
||||
|
||||
if ($this->trappedErrorNumber) {
|
||||
$error = sprintf(
|
||||
"HTTP Error: Unable to connect: '%s'",
|
||||
$this->trappedErrorString
|
||||
);
|
||||
|
||||
$this->client->getLogger()->error('Stream ' . $error);
|
||||
throw new UDP_Google_IO_Exception($error, $this->trappedErrorNumber); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
|
||||
}
|
||||
|
||||
$response_data = false;
|
||||
$respHttpCode = self::UNKNOWN_CODE;
|
||||
if ($fh) {
|
||||
if (isset($this->options[self::TIMEOUT])) {
|
||||
stream_set_timeout($fh, $this->options[self::TIMEOUT]);
|
||||
}
|
||||
|
||||
$response_data = stream_get_contents($fh);
|
||||
fclose($fh);
|
||||
|
||||
$respHttpCode = $this->getHttpResponseCode($http_response_header);
|
||||
}
|
||||
|
||||
if (false === $response_data) {
|
||||
$error = sprintf(
|
||||
"HTTP Error: Unable to connect: '%s'",
|
||||
$respHttpCode
|
||||
);
|
||||
|
||||
$this->client->getLogger()->error('Stream ' . $error);
|
||||
throw new UDP_Google_IO_Exception($error, $respHttpCode); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
|
||||
}
|
||||
|
||||
$responseHeaders = $this->getHttpResponseHeaders($http_response_header);
|
||||
|
||||
$this->client->getLogger()->debug(
|
||||
'Stream response',
|
||||
array(
|
||||
'code' => $respHttpCode,
|
||||
'headers' => $responseHeaders,
|
||||
'body' => $response_data,
|
||||
)
|
||||
);
|
||||
|
||||
return array($response_data, $responseHeaders, $respHttpCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set options that update the transport implementation's behavior.
|
||||
* @param $options
|
||||
*/
|
||||
public function setOptions($options)
|
||||
{
|
||||
$this->options = $options + $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to handle errors, used for error handling around
|
||||
* stream connection methods.
|
||||
*/
|
||||
public function trapError($errno, $errstr)
|
||||
{
|
||||
$this->trappedErrorNumber = $errno;
|
||||
$this->trappedErrorString = $errstr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum request time in seconds.
|
||||
* @param $timeout in seconds
|
||||
*/
|
||||
public function setTimeout($timeout)
|
||||
{
|
||||
$this->options[self::TIMEOUT] = $timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum request time in seconds.
|
||||
* @return timeout in seconds
|
||||
*/
|
||||
public function getTimeout()
|
||||
{
|
||||
return $this->options[self::TIMEOUT];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for the presence of a cURL header processing bug
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function needsQuirk()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getHttpResponseCode($response_headers)
|
||||
{
|
||||
$header_count = count($response_headers);
|
||||
|
||||
for ($i = 0; $i < $header_count; $i++) {
|
||||
$header = $response_headers[$i];
|
||||
if (strncasecmp("HTTP", $header, strlen("HTTP")) == 0) {
|
||||
$response = explode(' ', $header);
|
||||
return $response[1];
|
||||
}
|
||||
}
|
||||
return self::UNKNOWN_CODE;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,410 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2014 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract logging class based on the PSR-3 standard.
|
||||
*
|
||||
* NOTE: We don't implement `Psr\Log\LoggerInterface` because we need to
|
||||
* maintain PHP 5.2 support.
|
||||
*
|
||||
* @see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
|
||||
*/
|
||||
abstract class Google_Logger_Abstract
|
||||
{
|
||||
/**
|
||||
* Default log format
|
||||
*/
|
||||
const DEFAULT_LOG_FORMAT = "[%datetime%] %level%: %message% %context%\n";
|
||||
/**
|
||||
* Default date format
|
||||
*
|
||||
* Example: 16/Nov/2014:03:26:16 -0500
|
||||
*/
|
||||
const DEFAULT_DATE_FORMAT = 'd/M/Y:H:i:s O';
|
||||
|
||||
/**
|
||||
* System is unusable
|
||||
*/
|
||||
const EMERGENCY = 'emergency';
|
||||
/**
|
||||
* Action must be taken immediately
|
||||
*
|
||||
* Example: Entire website down, database unavailable, etc. This should
|
||||
* trigger the SMS alerts and wake you up.
|
||||
*/
|
||||
const ALERT = 'alert';
|
||||
/**
|
||||
* Critical conditions
|
||||
*
|
||||
* Example: Application component unavailable, unexpected exception.
|
||||
*/
|
||||
const CRITICAL = 'critical';
|
||||
/**
|
||||
* Runtime errors that do not require immediate action but should typically
|
||||
* be logged and monitored.
|
||||
*/
|
||||
const ERROR = 'error';
|
||||
/**
|
||||
* Exceptional occurrences that are not errors.
|
||||
*
|
||||
* Example: Use of deprecated APIs, poor use of an API, undesirable things
|
||||
* that are not necessarily wrong.
|
||||
*/
|
||||
const WARNING = 'warning';
|
||||
/**
|
||||
* Normal but significant events.
|
||||
*/
|
||||
const NOTICE = 'notice';
|
||||
/**
|
||||
* Interesting events.
|
||||
*
|
||||
* Example: User logs in, SQL logs.
|
||||
*/
|
||||
const INFO = 'info';
|
||||
/**
|
||||
* Detailed debug information.
|
||||
*/
|
||||
const DEBUG = 'debug';
|
||||
|
||||
/**
|
||||
* @var array $levels Logging levels
|
||||
*/
|
||||
protected static $levels = array(
|
||||
self::EMERGENCY => 600,
|
||||
self::ALERT => 550,
|
||||
self::CRITICAL => 500,
|
||||
self::ERROR => 400,
|
||||
self::WARNING => 300,
|
||||
self::NOTICE => 250,
|
||||
self::INFO => 200,
|
||||
self::DEBUG => 100,
|
||||
);
|
||||
|
||||
/**
|
||||
* @var integer $level The minimum logging level
|
||||
*/
|
||||
protected $level = self::DEBUG;
|
||||
|
||||
/**
|
||||
* @var string $logFormat The current log format
|
||||
*/
|
||||
protected $logFormat = self::DEFAULT_LOG_FORMAT;
|
||||
/**
|
||||
* @var string $dateFormat The current date format
|
||||
*/
|
||||
protected $dateFormat = self::DEFAULT_DATE_FORMAT;
|
||||
|
||||
/**
|
||||
* @var boolean $allowNewLines If newlines are allowed
|
||||
*/
|
||||
protected $allowNewLines = false;
|
||||
|
||||
/**
|
||||
* @param UDP_Google_Client $client The current Google client
|
||||
*/
|
||||
public function __construct(UDP_Google_Client $client)
|
||||
{
|
||||
$this->setLevel(
|
||||
$client->getClassConfig('Google_Logger_Abstract', 'level')
|
||||
);
|
||||
|
||||
$format = $client->getClassConfig('Google_Logger_Abstract', 'log_format');
|
||||
$this->logFormat = $format ? $format : self::DEFAULT_LOG_FORMAT;
|
||||
|
||||
$format = $client->getClassConfig('Google_Logger_Abstract', 'date_format');
|
||||
$this->dateFormat = $format ? $format : self::DEFAULT_DATE_FORMAT;
|
||||
|
||||
$this->allowNewLines = (bool) $client->getClassConfig(
|
||||
'Google_Logger_Abstract',
|
||||
'allow_newlines'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum logging level that this logger handles.
|
||||
*
|
||||
* @param integer $level
|
||||
*/
|
||||
public function setLevel($level)
|
||||
{
|
||||
$this->level = $this->normalizeLevel($level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the logger should handle messages at the provided level.
|
||||
*
|
||||
* @param integer $level
|
||||
* @return boolean
|
||||
*/
|
||||
public function shouldHandle($level)
|
||||
{
|
||||
return $this->normalizeLevel($level) >= $this->level;
|
||||
}
|
||||
|
||||
/**
|
||||
* System is unusable.
|
||||
*
|
||||
* @param string $message The log message
|
||||
* @param array $context The log context
|
||||
*/
|
||||
public function emergency($message, array $context = array())
|
||||
{
|
||||
$this->log(self::EMERGENCY, $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Action must be taken immediately.
|
||||
*
|
||||
* Example: Entire website down, database unavailable, etc. This should
|
||||
* trigger the SMS alerts and wake you up.
|
||||
*
|
||||
* @param string $message The log message
|
||||
* @param array $context The log context
|
||||
*/
|
||||
public function alert($message, array $context = array())
|
||||
{
|
||||
$this->log(self::ALERT, $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Critical conditions.
|
||||
*
|
||||
* Example: Application component unavailable, unexpected exception.
|
||||
*
|
||||
* @param string $message The log message
|
||||
* @param array $context The log context
|
||||
*/
|
||||
public function critical($message, array $context = array())
|
||||
{
|
||||
$this->log(self::CRITICAL, $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime errors that do not require immediate action but should typically
|
||||
* be logged and monitored.
|
||||
*
|
||||
* @param string $message The log message
|
||||
* @param array $context The log context
|
||||
*/
|
||||
public function error($message, array $context = array())
|
||||
{
|
||||
$this->log(self::ERROR, $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exceptional occurrences that are not errors.
|
||||
*
|
||||
* Example: Use of deprecated APIs, poor use of an API, undesirable things
|
||||
* that are not necessarily wrong.
|
||||
*
|
||||
* @param string $message The log message
|
||||
* @param array $context The log context
|
||||
*/
|
||||
public function warning($message, array $context = array())
|
||||
{
|
||||
$this->log(self::WARNING, $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normal but significant events.
|
||||
*
|
||||
* @param string $message The log message
|
||||
* @param array $context The log context
|
||||
*/
|
||||
public function notice($message, array $context = array())
|
||||
{
|
||||
$this->log(self::NOTICE, $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interesting events.
|
||||
*
|
||||
* Example: User logs in, SQL logs.
|
||||
*
|
||||
* @param string $message The log message
|
||||
* @param array $context The log context
|
||||
*/
|
||||
public function info($message, array $context = array())
|
||||
{
|
||||
$this->log(self::INFO, $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detailed debug information.
|
||||
*
|
||||
* @param string $message The log message
|
||||
* @param array $context The log context
|
||||
*/
|
||||
public function debug($message, array $context = array())
|
||||
{
|
||||
$this->log(self::DEBUG, $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs with an arbitrary level.
|
||||
*
|
||||
* @param mixed $level The log level
|
||||
* @param string $message The log message
|
||||
* @param array $context The log context
|
||||
*/
|
||||
public function log($level, $message, array $context = array())
|
||||
{
|
||||
if (!$this->shouldHandle($level)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$levelName = is_int($level) ? array_search($level, self::$levels) : $level;
|
||||
$message = $this->interpolate(
|
||||
array(
|
||||
'message' => $message,
|
||||
'context' => $context,
|
||||
'level' => strtoupper($levelName),
|
||||
'datetime' => new DateTime(),
|
||||
)
|
||||
);
|
||||
|
||||
$this->write($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolates log variables into the defined log format.
|
||||
*
|
||||
* @param array $variables The log variables.
|
||||
* @return string
|
||||
*/
|
||||
protected function interpolate(array $variables = array())
|
||||
{
|
||||
$template = $this->logFormat;
|
||||
|
||||
if (!$variables['context']) {
|
||||
$template = str_replace('%context%', '', $template);
|
||||
unset($variables['context']);
|
||||
} else {
|
||||
$this->reverseJsonInContext($variables['context']);
|
||||
}
|
||||
|
||||
foreach ($variables as $key => $value) {
|
||||
if (strpos($template, '%'. $key .'%') !== false) {
|
||||
$template = str_replace(
|
||||
'%' . $key . '%',
|
||||
$this->export($value),
|
||||
$template
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverses JSON encoded PHP arrays and objects so that they log better.
|
||||
*
|
||||
* @param array $context The log context
|
||||
*/
|
||||
protected function reverseJsonInContext(array &$context)
|
||||
{
|
||||
if (!$context) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($context as $key => $val) {
|
||||
if (!$val || !is_string($val) || !($val[0] == '{' || $val[0] == '[')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$json = @json_decode($val);
|
||||
if (is_object($json) || is_array($json)) {
|
||||
$context[$key] = $json;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports a PHP value for logging to a string.
|
||||
*
|
||||
* @param mixed $value The value to
|
||||
*/
|
||||
protected function export($value)
|
||||
{
|
||||
if (is_string($value)) {
|
||||
if ($this->allowNewLines) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return preg_replace('/[\r\n]+/', ' ', $value);
|
||||
}
|
||||
|
||||
if (is_resource($value)) {
|
||||
return sprintf(
|
||||
'resource(%d) of type (%s)',
|
||||
$value,
|
||||
get_resource_type($value)
|
||||
);
|
||||
}
|
||||
|
||||
if ($value instanceof DateTime) {
|
||||
return $value->format($this->dateFormat);
|
||||
}
|
||||
|
||||
if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
|
||||
// @codingStandardsIgnoreLine
|
||||
$options = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
|
||||
|
||||
if ($this->allowNewLines) {
|
||||
// @codingStandardsIgnoreLine
|
||||
$options |= JSON_PRETTY_PRINT;
|
||||
}
|
||||
|
||||
return @json_encode($value, $options);
|
||||
}
|
||||
|
||||
return str_replace('\\/', '/', @json_encode($value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a given log level to the integer form.
|
||||
*
|
||||
* @param mixed $level The logging level
|
||||
* @return integer $level The normalized level
|
||||
* @throws Google_Logger_Exception If $level is invalid
|
||||
*/
|
||||
protected function normalizeLevel($level)
|
||||
{
|
||||
if (is_int($level) && array_search($level, self::$levels) !== false) {
|
||||
return $level;
|
||||
}
|
||||
|
||||
if (is_string($level) && isset(self::$levels[$level])) {
|
||||
return self::$levels[$level];
|
||||
}
|
||||
|
||||
throw new Google_Logger_Exception(
|
||||
sprintf("Unknown LogLevel: '%s'", $level) // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a message to the current log implementation.
|
||||
*
|
||||
* @param string $message The message
|
||||
*/
|
||||
abstract protected function write($message);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2014 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
class Google_Logger_Exception extends Google_Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2014 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* File logging class based on the PSR-3 standard.
|
||||
*
|
||||
* This logger writes to a PHP stream resource.
|
||||
*/
|
||||
class Google_Logger_File extends Google_Logger_Abstract
|
||||
{
|
||||
/**
|
||||
* @var string|resource $file Where logs are written
|
||||
*/
|
||||
private $file;
|
||||
/**
|
||||
* @var integer $mode The mode to use if the log file needs to be created
|
||||
*/
|
||||
private $mode = 0640;
|
||||
/**
|
||||
* @var boolean $lock If a lock should be attempted before writing to the log
|
||||
*/
|
||||
private $lock = false;
|
||||
|
||||
/**
|
||||
* @var integer $trappedErrorNumber Trapped error number
|
||||
*/
|
||||
private $trappedErrorNumber;
|
||||
/**
|
||||
* @var string $trappedErrorString Trapped error string
|
||||
*/
|
||||
private $trappedErrorString;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(UDP_Google_Client $client)
|
||||
{
|
||||
parent::__construct($client);
|
||||
|
||||
$file = $client->getClassConfig('Google_Logger_File', 'file');
|
||||
if (!is_string($file) && !is_resource($file)) {
|
||||
throw new Google_Logger_Exception(
|
||||
'File logger requires a filename or a valid file pointer'
|
||||
);
|
||||
}
|
||||
|
||||
$mode = $client->getClassConfig('Google_Logger_File', 'mode');
|
||||
if (!$mode) {
|
||||
$this->mode = $mode;
|
||||
}
|
||||
|
||||
$this->lock = (bool) $client->getClassConfig('Google_Logger_File', 'lock');
|
||||
$this->file = $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function write($message)
|
||||
{
|
||||
if (is_string($this->file)) {
|
||||
$this->open();
|
||||
} elseif (!is_resource($this->file)) {
|
||||
throw new Google_Logger_Exception('File pointer is no longer available');
|
||||
}
|
||||
|
||||
if ($this->lock) {
|
||||
flock($this->file, LOCK_EX);
|
||||
}
|
||||
|
||||
fwrite($this->file, (string) $message);
|
||||
|
||||
if ($this->lock) {
|
||||
flock($this->file, LOCK_UN);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the log for writing.
|
||||
*
|
||||
* @return resource
|
||||
*/
|
||||
private function open()
|
||||
{
|
||||
// Used for trapping `fopen()` errors.
|
||||
$this->trappedErrorNumber = null;
|
||||
$this->trappedErrorString = null;
|
||||
|
||||
$old = set_error_handler(array($this, 'trapError'));
|
||||
|
||||
$needsChmod = !file_exists($this->file);
|
||||
$fh = fopen($this->file, 'a');
|
||||
|
||||
restore_error_handler();
|
||||
|
||||
// Handles trapped `fopen()` errors.
|
||||
if ($this->trappedErrorNumber) {
|
||||
throw new Google_Logger_Exception(
|
||||
sprintf(
|
||||
"Logger Error: '%s'",
|
||||
$this->trappedErrorString // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
|
||||
),
|
||||
$this->trappedErrorNumber // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
|
||||
);
|
||||
}
|
||||
|
||||
if ($needsChmod) {
|
||||
@chmod($this->file, $this->mode & ~umask());
|
||||
}
|
||||
|
||||
return $this->file = $fh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the log stream resource.
|
||||
*/
|
||||
private function close()
|
||||
{
|
||||
if (is_resource($this->file)) {
|
||||
fclose($this->file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Traps `fopen()` errors.
|
||||
*
|
||||
* @param integer $errno The error number
|
||||
* @param string $errstr The error string
|
||||
*/
|
||||
private function trapError($errno, $errstr)
|
||||
{
|
||||
$this->trappedErrorNumber = $errno;
|
||||
$this->trappedErrorString = $errstr;
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2014 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Null logger based on the PSR-3 standard.
|
||||
*
|
||||
* This logger simply discards all messages.
|
||||
*/
|
||||
class Google_Logger_Null extends Google_Logger_Abstract
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function shouldHandle($level)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function write($message, array $context = array())
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2014 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Psr logging class based on the PSR-3 standard.
|
||||
*
|
||||
* This logger will delegate all logging to a PSR-3 compatible logger specified
|
||||
* with the `Google_Logger_Psr::setLogger()` method.
|
||||
*/
|
||||
class Google_Logger_Psr extends Google_Logger_Abstract
|
||||
{
|
||||
/**
|
||||
* @param Psr\Log\LoggerInterface $logger The PSR-3 logger
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* @param Google_Client $client The current Google client
|
||||
* @param Psr\Log\LoggerInterface $logger PSR-3 logger where logging will be delegated.
|
||||
*/
|
||||
public function __construct(UDP_Google_Client $client, /*Psr\Log\LoggerInterface*/ $logger = null)
|
||||
{
|
||||
parent::__construct($client);
|
||||
|
||||
if ($logger) {
|
||||
$this->setLogger($logger);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the PSR-3 logger where logging will be delegated.
|
||||
*
|
||||
* NOTE: The `$logger` should technically implement
|
||||
* `Psr\Log\LoggerInterface`, but we don't explicitly require this so that
|
||||
* we can be compatible with PHP 5.2.
|
||||
*
|
||||
* @param Psr\Log\LoggerInterface $logger The PSR-3 logger
|
||||
*/
|
||||
public function setLogger(/*Psr\Log\LoggerInterface*/ $logger)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function shouldHandle($level)
|
||||
{
|
||||
return isset($this->logger) && parent::shouldHandle($level);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function log($level, $message, array $context = array())
|
||||
{
|
||||
if (!$this->shouldHandle($level)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($context) {
|
||||
$this->reverseJsonInContext($context);
|
||||
}
|
||||
|
||||
$levelName = is_int($level) ? array_search($level, self::$levels) : $level;
|
||||
$this->logger->log($levelName, $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function write($message, array $context = array())
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class defines attributes, valid values, and usage which is generated
|
||||
* from a given json schema.
|
||||
* http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5
|
||||
*
|
||||
*/
|
||||
class Google_Model implements ArrayAccess
|
||||
{
|
||||
/**
|
||||
* If you need to specify a NULL JSON value, use Google_Model::NULL_VALUE
|
||||
* instead - it will be replaced when converting to JSON with a real null.
|
||||
*/
|
||||
const NULL_VALUE = "{}gapi-php-null";
|
||||
protected $internal_gapi_mappings = array();
|
||||
protected $modelData = array();
|
||||
protected $processed = array();
|
||||
|
||||
/**
|
||||
* Polymorphic - accepts a variable number of arguments dependent
|
||||
* on the type of the model subclass.
|
||||
*/
|
||||
final public function __construct()
|
||||
{
|
||||
if (func_num_args() == 1 && is_array(func_get_arg(0))) {
|
||||
// Initialize the model with the array's contents.
|
||||
$array = func_get_arg(0);
|
||||
$this->mapTypes($array);
|
||||
}
|
||||
$this->gapiInit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter that handles passthrough access to the data array, and lazy object creation.
|
||||
* @param string $key Property name.
|
||||
* @return mixed The value if any, or null.
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
$keyTypeName = $this->keyType($key);
|
||||
$keyDataType = $this->dataType($key);
|
||||
if (isset($this->$keyTypeName) && !isset($this->processed[$key])) {
|
||||
if (isset($this->modelData[$key])) {
|
||||
$val = $this->modelData[$key];
|
||||
} else if (isset($this->$keyDataType) &&
|
||||
($this->$keyDataType == 'array' || $this->$keyDataType == 'map')) {
|
||||
$val = array();
|
||||
} else {
|
||||
$val = null;
|
||||
}
|
||||
|
||||
if ($this->isAssociativeArray($val)) {
|
||||
if (isset($this->$keyDataType) && 'map' == $this->$keyDataType) {
|
||||
foreach ($val as $arrayKey => $arrayItem) {
|
||||
$this->modelData[$key][$arrayKey] =
|
||||
$this->createObjectFromName($keyTypeName, $arrayItem);
|
||||
}
|
||||
} else {
|
||||
$this->modelData[$key] = $this->createObjectFromName($keyTypeName, $val);
|
||||
}
|
||||
} else if (is_array($val)) {
|
||||
$arrayObject = array();
|
||||
foreach ($val as $arrayIndex => $arrayItem) {
|
||||
$arrayObject[$arrayIndex] =
|
||||
$this->createObjectFromName($keyTypeName, $arrayItem);
|
||||
}
|
||||
$this->modelData[$key] = $arrayObject;
|
||||
}
|
||||
$this->processed[$key] = true;
|
||||
}
|
||||
|
||||
return isset($this->modelData[$key]) ? $this->modelData[$key] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize this object's properties from an array.
|
||||
*
|
||||
* @param array $array Used to seed this object's properties.
|
||||
* @return void
|
||||
*/
|
||||
protected function mapTypes($array)
|
||||
{
|
||||
// Hard initialise simple types, lazy load more complex ones.
|
||||
foreach ($array as $key => $val) {
|
||||
if ( !property_exists($this, $this->keyType($key)) &&
|
||||
property_exists($this, $key)) {
|
||||
$this->$key = $val;
|
||||
unset($array[$key]);
|
||||
} elseif (property_exists($this, $camelKey = Google_Utils::camelCase($key))) {
|
||||
// This checks if property exists as camelCase, leaving it in array as snake_case
|
||||
// in case of backwards compatibility issues.
|
||||
$this->$camelKey = $val;
|
||||
}
|
||||
}
|
||||
$this->modelData = $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blank initialiser to be used in subclasses to do post-construction initialisation - this
|
||||
* avoids the need for subclasses to have to implement the variadics handling in their
|
||||
* constructors.
|
||||
*/
|
||||
protected function gapiInit()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a simplified object suitable for straightforward
|
||||
* conversion to JSON. This is relatively expensive
|
||||
* due to the usage of reflection, but shouldn't be called
|
||||
* a whole lot, and is the most straightforward way to filter.
|
||||
*/
|
||||
public function toSimpleObject()
|
||||
{
|
||||
$object = new stdClass();
|
||||
|
||||
// Process all other data.
|
||||
foreach ($this->modelData as $key => $val) {
|
||||
$result = $this->getSimpleValue($val);
|
||||
if ($result !== null) {
|
||||
$object->$key = $this->nullPlaceholderCheck($result);
|
||||
}
|
||||
}
|
||||
|
||||
// Process all public properties.
|
||||
$reflect = new ReflectionObject($this);
|
||||
$props = $reflect->getProperties(ReflectionProperty::IS_PUBLIC);
|
||||
foreach ($props as $member) {
|
||||
$name = $member->getName();
|
||||
$result = $this->getSimpleValue($this->$name);
|
||||
if ($result !== null) {
|
||||
$name = $this->getMappedName($name);
|
||||
$object->$name = $this->nullPlaceholderCheck($result);
|
||||
}
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle different types of values, primarily
|
||||
* other objects and map and array data types.
|
||||
*/
|
||||
private function getSimpleValue($value)
|
||||
{
|
||||
if ($value instanceof Google_Model) {
|
||||
return $value->toSimpleObject();
|
||||
} else if (is_array($value)) {
|
||||
$return = array();
|
||||
foreach ($value as $key => $a_value) {
|
||||
$a_value = $this->getSimpleValue($a_value);
|
||||
if ($a_value !== null) {
|
||||
$key = $this->getMappedName($key);
|
||||
$return[$key] = $this->nullPlaceholderCheck($a_value);
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the value is the null placeholder and return true null.
|
||||
*/
|
||||
private function nullPlaceholderCheck($value)
|
||||
{
|
||||
if ($value === self::NULL_VALUE) {
|
||||
return null;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is an internal name mapping, use that.
|
||||
*/
|
||||
private function getMappedName($key)
|
||||
{
|
||||
if (isset($this->internal_gapi_mappings) &&
|
||||
isset($this->internal_gapi_mappings[$key])) {
|
||||
$key = $this->internal_gapi_mappings[$key];
|
||||
}
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true only if the array is associative.
|
||||
* @param array $array
|
||||
* @return bool True if the array is associative.
|
||||
*/
|
||||
protected function isAssociativeArray($array)
|
||||
{
|
||||
if (!is_array($array)) {
|
||||
return false;
|
||||
}
|
||||
$keys = array_keys($array);
|
||||
foreach ($keys as $key) {
|
||||
if (is_string($key)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a variable name, discover its type.
|
||||
*
|
||||
* @param $name
|
||||
* @param $item
|
||||
* @return object The object from the item.
|
||||
*/
|
||||
private function createObjectFromName($name, $item)
|
||||
{
|
||||
$type = $this->$name;
|
||||
return new $type($item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if $obj is an array.
|
||||
* @throws Google_Exception Thrown if $obj isn't an array.
|
||||
* @param array $obj Items that should be validated.
|
||||
* @param string $method Method expecting an array as an argument.
|
||||
*/
|
||||
public function assertIsArray($obj, $method)
|
||||
{
|
||||
if ($obj && !is_array($obj)) {
|
||||
throw new Google_Exception(
|
||||
"Incorrect parameter type passed to $method(). Expected an array." // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return isset($this->$offset) || isset($this->modelData[$offset]);
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return isset($this->$offset) ?
|
||||
$this->$offset :
|
||||
$this->__get($offset);
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
if (property_exists($this, $offset)) {
|
||||
$this->$offset = $value;
|
||||
} else {
|
||||
$this->modelData[$offset] = $value;
|
||||
$this->processed[$offset] = true;
|
||||
}
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
unset($this->modelData[$offset]);
|
||||
}
|
||||
|
||||
protected function keyType($key)
|
||||
{
|
||||
return $key . "Type";
|
||||
}
|
||||
|
||||
protected function dataType($key)
|
||||
{
|
||||
return $key . "DataType";
|
||||
}
|
||||
|
||||
public function __isset($key)
|
||||
{
|
||||
return isset($this->modelData[$key]);
|
||||
}
|
||||
|
||||
public function __unset($key)
|
||||
{
|
||||
unset($this->modelData[$key]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
class UDP_Google_Service
|
||||
{
|
||||
public $rootUrl;
|
||||
public $version;
|
||||
public $servicePath;
|
||||
public $availableScopes;
|
||||
public $resource;
|
||||
private $client;
|
||||
|
||||
public function __construct(UDP_Google_Client $client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the associated UDP_Google_Client class.
|
||||
* @return UDP_Google_Client
|
||||
*/
|
||||
public function getClient()
|
||||
{
|
||||
return $this->client;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,916 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Service definition for Dns (v1).
|
||||
*
|
||||
* <p>
|
||||
* The Google Cloud DNS API provides services for configuring and serving
|
||||
* authoritative DNS records.</p>
|
||||
*
|
||||
* <p>
|
||||
* For more information about this service, see the API
|
||||
* <a href="https://developers.google.com/cloud-dns" target="_blank">Documentation</a>
|
||||
* </p>
|
||||
*
|
||||
* @author Google, Inc.
|
||||
*/
|
||||
class Google_Service_Dns extends UDP_Google_Service
|
||||
{
|
||||
/** View and manage your data across Google Cloud Platform services. */
|
||||
const CLOUD_PLATFORM =
|
||||
"https://www.googleapis.com/auth/cloud-platform";
|
||||
/** View your DNS records hosted by Google Cloud DNS. */
|
||||
const NDEV_CLOUDDNS_READONLY =
|
||||
"https://www.googleapis.com/auth/ndev.clouddns.readonly";
|
||||
/** View and manage your DNS records hosted by Google Cloud DNS. */
|
||||
const NDEV_CLOUDDNS_READWRITE =
|
||||
"https://www.googleapis.com/auth/ndev.clouddns.readwrite";
|
||||
|
||||
public $changes;
|
||||
public $managedZones;
|
||||
public $projects;
|
||||
public $resourceRecordSets;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs the internal representation of the Dns service.
|
||||
*
|
||||
* @param Google_Client $client
|
||||
*/
|
||||
public function __construct(UDP_Google_Client $client)
|
||||
{
|
||||
parent::__construct($client);
|
||||
$this->servicePath = 'dns/v1/projects/';
|
||||
$this->version = 'v1';
|
||||
$this->serviceName = 'dns';
|
||||
|
||||
$this->changes = new Google_Service_Dns_Changes_Resource(
|
||||
$this,
|
||||
$this->serviceName,
|
||||
'changes',
|
||||
array(
|
||||
'methods' => array(
|
||||
'create' => array(
|
||||
'path' => '{project}/managedZones/{managedZone}/changes',
|
||||
'httpMethod' => 'POST',
|
||||
'parameters' => array(
|
||||
'project' => array(
|
||||
'location' => 'path',
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
'managedZone' => array(
|
||||
'location' => 'path',
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
),
|
||||
),'get' => array(
|
||||
'path' => '{project}/managedZones/{managedZone}/changes/{changeId}',
|
||||
'httpMethod' => 'GET',
|
||||
'parameters' => array(
|
||||
'project' => array(
|
||||
'location' => 'path',
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
'managedZone' => array(
|
||||
'location' => 'path',
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
'changeId' => array(
|
||||
'location' => 'path',
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
),
|
||||
),'list' => array(
|
||||
'path' => '{project}/managedZones/{managedZone}/changes',
|
||||
'httpMethod' => 'GET',
|
||||
'parameters' => array(
|
||||
'project' => array(
|
||||
'location' => 'path',
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
'managedZone' => array(
|
||||
'location' => 'path',
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
'maxResults' => array(
|
||||
'location' => 'query',
|
||||
'type' => 'integer',
|
||||
),
|
||||
'pageToken' => array(
|
||||
'location' => 'query',
|
||||
'type' => 'string',
|
||||
),
|
||||
'sortBy' => array(
|
||||
'location' => 'query',
|
||||
'type' => 'string',
|
||||
),
|
||||
'sortOrder' => array(
|
||||
'location' => 'query',
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
$this->managedZones = new Google_Service_Dns_ManagedZones_Resource(
|
||||
$this,
|
||||
$this->serviceName,
|
||||
'managedZones',
|
||||
array(
|
||||
'methods' => array(
|
||||
'create' => array(
|
||||
'path' => '{project}/managedZones',
|
||||
'httpMethod' => 'POST',
|
||||
'parameters' => array(
|
||||
'project' => array(
|
||||
'location' => 'path',
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
),
|
||||
),'delete' => array(
|
||||
'path' => '{project}/managedZones/{managedZone}',
|
||||
'httpMethod' => 'DELETE',
|
||||
'parameters' => array(
|
||||
'project' => array(
|
||||
'location' => 'path',
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
'managedZone' => array(
|
||||
'location' => 'path',
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
),
|
||||
),'get' => array(
|
||||
'path' => '{project}/managedZones/{managedZone}',
|
||||
'httpMethod' => 'GET',
|
||||
'parameters' => array(
|
||||
'project' => array(
|
||||
'location' => 'path',
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
'managedZone' => array(
|
||||
'location' => 'path',
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
),
|
||||
),'list' => array(
|
||||
'path' => '{project}/managedZones',
|
||||
'httpMethod' => 'GET',
|
||||
'parameters' => array(
|
||||
'project' => array(
|
||||
'location' => 'path',
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
'pageToken' => array(
|
||||
'location' => 'query',
|
||||
'type' => 'string',
|
||||
),
|
||||
'maxResults' => array(
|
||||
'location' => 'query',
|
||||
'type' => 'integer',
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
$this->projects = new Google_Service_Dns_Projects_Resource(
|
||||
$this,
|
||||
$this->serviceName,
|
||||
'projects',
|
||||
array(
|
||||
'methods' => array(
|
||||
'get' => array(
|
||||
'path' => '{project}',
|
||||
'httpMethod' => 'GET',
|
||||
'parameters' => array(
|
||||
'project' => array(
|
||||
'location' => 'path',
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
$this->resourceRecordSets = new Google_Service_Dns_ResourceRecordSets_Resource(
|
||||
$this,
|
||||
$this->serviceName,
|
||||
'resourceRecordSets',
|
||||
array(
|
||||
'methods' => array(
|
||||
'list' => array(
|
||||
'path' => '{project}/managedZones/{managedZone}/rrsets',
|
||||
'httpMethod' => 'GET',
|
||||
'parameters' => array(
|
||||
'project' => array(
|
||||
'location' => 'path',
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
'managedZone' => array(
|
||||
'location' => 'path',
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
'name' => array(
|
||||
'location' => 'query',
|
||||
'type' => 'string',
|
||||
),
|
||||
'maxResults' => array(
|
||||
'location' => 'query',
|
||||
'type' => 'integer',
|
||||
),
|
||||
'pageToken' => array(
|
||||
'location' => 'query',
|
||||
'type' => 'string',
|
||||
),
|
||||
'type' => array(
|
||||
'location' => 'query',
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The "changes" collection of methods.
|
||||
* Typical usage is:
|
||||
* <code>
|
||||
* $dnsService = new Google_Service_Dns(...);
|
||||
* $changes = $dnsService->changes;
|
||||
* </code>
|
||||
*/
|
||||
class Google_Service_Dns_Changes_Resource extends UDP_Google_Service_Resource
|
||||
{
|
||||
|
||||
/**
|
||||
* Atomically update the ResourceRecordSet collection. (changes.create)
|
||||
*
|
||||
* @param string $project Identifies the project addressed by this request.
|
||||
* @param string $managedZone Identifies the managed zone addressed by this
|
||||
* request. Can be the managed zone name or id.
|
||||
* @param Google_Change $postBody
|
||||
* @param array $optParams Optional parameters.
|
||||
* @return Google_Service_Dns_Change
|
||||
*/
|
||||
public function create($project, $managedZone, Google_Service_Dns_Change $postBody, $optParams = array())
|
||||
{
|
||||
$params = array('project' => $project, 'managedZone' => $managedZone, 'postBody' => $postBody);
|
||||
$params = array_merge($params, $optParams);
|
||||
return $this->call('create', array($params), "Google_Service_Dns_Change");
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the representation of an existing Change. (changes.get)
|
||||
*
|
||||
* @param string $project Identifies the project addressed by this request.
|
||||
* @param string $managedZone Identifies the managed zone addressed by this
|
||||
* request. Can be the managed zone name or id.
|
||||
* @param string $changeId The identifier of the requested change, from a
|
||||
* previous ResourceRecordSetsChangeResponse.
|
||||
* @param array $optParams Optional parameters.
|
||||
* @return Google_Service_Dns_Change
|
||||
*/
|
||||
public function get($project, $managedZone, $changeId, $optParams = array())
|
||||
{
|
||||
$params = array('project' => $project, 'managedZone' => $managedZone, 'changeId' => $changeId);
|
||||
$params = array_merge($params, $optParams);
|
||||
return $this->call('get', array($params), "Google_Service_Dns_Change");
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate Changes to a ResourceRecordSet collection. (changes.listChanges)
|
||||
*
|
||||
* @param string $project Identifies the project addressed by this request.
|
||||
* @param string $managedZone Identifies the managed zone addressed by this
|
||||
* request. Can be the managed zone name or id.
|
||||
* @param array $optParams Optional parameters.
|
||||
*
|
||||
* @opt_param int maxResults Optional. Maximum number of results to be returned.
|
||||
* If unspecified, the server will decide how many results to return.
|
||||
* @opt_param string pageToken Optional. A tag returned by a previous list
|
||||
* request that was truncated. Use this parameter to continue a previous list
|
||||
* request.
|
||||
* @opt_param string sortBy Sorting criterion. The only supported value is
|
||||
* change sequence.
|
||||
* @opt_param string sortOrder Sorting order direction: 'ascending' or
|
||||
* 'descending'.
|
||||
* @return Google_Service_Dns_ChangesListResponse
|
||||
*/
|
||||
public function listChanges($project, $managedZone, $optParams = array())
|
||||
{
|
||||
$params = array('project' => $project, 'managedZone' => $managedZone);
|
||||
$params = array_merge($params, $optParams);
|
||||
return $this->call('list', array($params), "Google_Service_Dns_ChangesListResponse");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The "managedZones" collection of methods.
|
||||
* Typical usage is:
|
||||
* <code>
|
||||
* $dnsService = new Google_Service_Dns(...);
|
||||
* $managedZones = $dnsService->managedZones;
|
||||
* </code>
|
||||
*/
|
||||
class Google_Service_Dns_ManagedZones_Resource extends UDP_Google_Service_Resource
|
||||
{
|
||||
|
||||
/**
|
||||
* Create a new ManagedZone. (managedZones.create)
|
||||
*
|
||||
* @param string $project Identifies the project addressed by this request.
|
||||
* @param Google_ManagedZone $postBody
|
||||
* @param array $optParams Optional parameters.
|
||||
* @return Google_Service_Dns_ManagedZone
|
||||
*/
|
||||
public function create($project, Google_Service_Dns_ManagedZone $postBody, $optParams = array())
|
||||
{
|
||||
$params = array('project' => $project, 'postBody' => $postBody);
|
||||
$params = array_merge($params, $optParams);
|
||||
return $this->call('create', array($params), "Google_Service_Dns_ManagedZone");
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a previously created ManagedZone. (managedZones.delete)
|
||||
*
|
||||
* @param string $project Identifies the project addressed by this request.
|
||||
* @param string $managedZone Identifies the managed zone addressed by this
|
||||
* request. Can be the managed zone name or id.
|
||||
* @param array $optParams Optional parameters.
|
||||
*/
|
||||
public function delete($project, $managedZone, $optParams = array())
|
||||
{
|
||||
$params = array('project' => $project, 'managedZone' => $managedZone);
|
||||
$params = array_merge($params, $optParams);
|
||||
return $this->call('delete', array($params));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the representation of an existing ManagedZone. (managedZones.get)
|
||||
*
|
||||
* @param string $project Identifies the project addressed by this request.
|
||||
* @param string $managedZone Identifies the managed zone addressed by this
|
||||
* request. Can be the managed zone name or id.
|
||||
* @param array $optParams Optional parameters.
|
||||
* @return Google_Service_Dns_ManagedZone
|
||||
*/
|
||||
public function get($project, $managedZone, $optParams = array())
|
||||
{
|
||||
$params = array('project' => $project, 'managedZone' => $managedZone);
|
||||
$params = array_merge($params, $optParams);
|
||||
return $this->call('get', array($params), "Google_Service_Dns_ManagedZone");
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate ManagedZones that have been created but not yet deleted.
|
||||
* (managedZones.listManagedZones)
|
||||
*
|
||||
* @param string $project Identifies the project addressed by this request.
|
||||
* @param array $optParams Optional parameters.
|
||||
*
|
||||
* @opt_param string pageToken Optional. A tag returned by a previous list
|
||||
* request that was truncated. Use this parameter to continue a previous list
|
||||
* request.
|
||||
* @opt_param int maxResults Optional. Maximum number of results to be returned.
|
||||
* If unspecified, the server will decide how many results to return.
|
||||
* @return Google_Service_Dns_ManagedZonesListResponse
|
||||
*/
|
||||
public function listManagedZones($project, $optParams = array())
|
||||
{
|
||||
$params = array('project' => $project);
|
||||
$params = array_merge($params, $optParams);
|
||||
return $this->call('list', array($params), "Google_Service_Dns_ManagedZonesListResponse");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The "projects" collection of methods.
|
||||
* Typical usage is:
|
||||
* <code>
|
||||
* $dnsService = new Google_Service_Dns(...);
|
||||
* $projects = $dnsService->projects;
|
||||
* </code>
|
||||
*/
|
||||
class Google_Service_Dns_Projects_Resource extends UDP_Google_Service_Resource
|
||||
{
|
||||
|
||||
/**
|
||||
* Fetch the representation of an existing Project. (projects.get)
|
||||
*
|
||||
* @param string $project Identifies the project addressed by this request.
|
||||
* @param array $optParams Optional parameters.
|
||||
* @return Google_Service_Dns_Project
|
||||
*/
|
||||
public function get($project, $optParams = array())
|
||||
{
|
||||
$params = array('project' => $project);
|
||||
$params = array_merge($params, $optParams);
|
||||
return $this->call('get', array($params), "Google_Service_Dns_Project");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The "resourceRecordSets" collection of methods.
|
||||
* Typical usage is:
|
||||
* <code>
|
||||
* $dnsService = new Google_Service_Dns(...);
|
||||
* $resourceRecordSets = $dnsService->resourceRecordSets;
|
||||
* </code>
|
||||
*/
|
||||
class Google_Service_Dns_ResourceRecordSets_Resource extends UDP_Google_Service_Resource
|
||||
{
|
||||
|
||||
/**
|
||||
* Enumerate ResourceRecordSets that have been created but not yet deleted.
|
||||
* (resourceRecordSets.listResourceRecordSets)
|
||||
*
|
||||
* @param string $project Identifies the project addressed by this request.
|
||||
* @param string $managedZone Identifies the managed zone addressed by this
|
||||
* request. Can be the managed zone name or id.
|
||||
* @param array $optParams Optional parameters.
|
||||
*
|
||||
* @opt_param string name Restricts the list to return only records with this
|
||||
* fully qualified domain name.
|
||||
* @opt_param int maxResults Optional. Maximum number of results to be returned.
|
||||
* If unspecified, the server will decide how many results to return.
|
||||
* @opt_param string pageToken Optional. A tag returned by a previous list
|
||||
* request that was truncated. Use this parameter to continue a previous list
|
||||
* request.
|
||||
* @opt_param string type Restricts the list to return only records of this
|
||||
* type. If present, the "name" parameter must also be present.
|
||||
* @return Google_Service_Dns_ResourceRecordSetsListResponse
|
||||
*/
|
||||
public function listResourceRecordSets($project, $managedZone, $optParams = array())
|
||||
{
|
||||
$params = array('project' => $project, 'managedZone' => $managedZone);
|
||||
$params = array_merge($params, $optParams);
|
||||
return $this->call('list', array($params), "Google_Service_Dns_ResourceRecordSetsListResponse");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
class Google_Service_Dns_Change extends Google_Collection
|
||||
{
|
||||
protected $collection_key = 'deletions';
|
||||
protected $internal_gapi_mappings = array(
|
||||
);
|
||||
protected $additionsType = 'Google_Service_Dns_ResourceRecordSet';
|
||||
protected $additionsDataType = 'array';
|
||||
protected $deletionsType = 'Google_Service_Dns_ResourceRecordSet';
|
||||
protected $deletionsDataType = 'array';
|
||||
public $id;
|
||||
public $kind;
|
||||
public $startTime;
|
||||
public $status;
|
||||
|
||||
|
||||
public function setAdditions($additions)
|
||||
{
|
||||
$this->additions = $additions;
|
||||
}
|
||||
public function getAdditions()
|
||||
{
|
||||
return $this->additions;
|
||||
}
|
||||
public function setDeletions($deletions)
|
||||
{
|
||||
$this->deletions = $deletions;
|
||||
}
|
||||
public function getDeletions()
|
||||
{
|
||||
return $this->deletions;
|
||||
}
|
||||
public function setId($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
public function setKind($kind)
|
||||
{
|
||||
$this->kind = $kind;
|
||||
}
|
||||
public function getKind()
|
||||
{
|
||||
return $this->kind;
|
||||
}
|
||||
public function setStartTime($startTime)
|
||||
{
|
||||
$this->startTime = $startTime;
|
||||
}
|
||||
public function getStartTime()
|
||||
{
|
||||
return $this->startTime;
|
||||
}
|
||||
public function setStatus($status)
|
||||
{
|
||||
$this->status = $status;
|
||||
}
|
||||
public function getStatus()
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
}
|
||||
|
||||
class Google_Service_Dns_ChangesListResponse extends Google_Collection
|
||||
{
|
||||
protected $collection_key = 'changes';
|
||||
protected $internal_gapi_mappings = array(
|
||||
);
|
||||
protected $changesType = 'Google_Service_Dns_Change';
|
||||
protected $changesDataType = 'array';
|
||||
public $kind;
|
||||
public $nextPageToken;
|
||||
|
||||
|
||||
public function setChanges($changes)
|
||||
{
|
||||
$this->changes = $changes;
|
||||
}
|
||||
public function getChanges()
|
||||
{
|
||||
return $this->changes;
|
||||
}
|
||||
public function setKind($kind)
|
||||
{
|
||||
$this->kind = $kind;
|
||||
}
|
||||
public function getKind()
|
||||
{
|
||||
return $this->kind;
|
||||
}
|
||||
public function setNextPageToken($nextPageToken)
|
||||
{
|
||||
$this->nextPageToken = $nextPageToken;
|
||||
}
|
||||
public function getNextPageToken()
|
||||
{
|
||||
return $this->nextPageToken;
|
||||
}
|
||||
}
|
||||
|
||||
class Google_Service_Dns_ManagedZone extends Google_Collection
|
||||
{
|
||||
protected $collection_key = 'nameServers';
|
||||
protected $internal_gapi_mappings = array(
|
||||
);
|
||||
public $creationTime;
|
||||
public $description;
|
||||
public $dnsName;
|
||||
public $id;
|
||||
public $kind;
|
||||
public $name;
|
||||
public $nameServerSet;
|
||||
public $nameServers;
|
||||
|
||||
|
||||
public function setCreationTime($creationTime)
|
||||
{
|
||||
$this->creationTime = $creationTime;
|
||||
}
|
||||
public function getCreationTime()
|
||||
{
|
||||
return $this->creationTime;
|
||||
}
|
||||
public function setDescription($description)
|
||||
{
|
||||
$this->description = $description;
|
||||
}
|
||||
public function getDescription()
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
public function setDnsName($dnsName)
|
||||
{
|
||||
$this->dnsName = $dnsName;
|
||||
}
|
||||
public function getDnsName()
|
||||
{
|
||||
return $this->dnsName;
|
||||
}
|
||||
public function setId($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
public function setKind($kind)
|
||||
{
|
||||
$this->kind = $kind;
|
||||
}
|
||||
public function getKind()
|
||||
{
|
||||
return $this->kind;
|
||||
}
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
public function setNameServerSet($nameServerSet)
|
||||
{
|
||||
$this->nameServerSet = $nameServerSet;
|
||||
}
|
||||
public function getNameServerSet()
|
||||
{
|
||||
return $this->nameServerSet;
|
||||
}
|
||||
public function setNameServers($nameServers)
|
||||
{
|
||||
$this->nameServers = $nameServers;
|
||||
}
|
||||
public function getNameServers()
|
||||
{
|
||||
return $this->nameServers;
|
||||
}
|
||||
}
|
||||
|
||||
class Google_Service_Dns_ManagedZonesListResponse extends Google_Collection
|
||||
{
|
||||
protected $collection_key = 'managedZones';
|
||||
protected $internal_gapi_mappings = array(
|
||||
);
|
||||
public $kind;
|
||||
protected $managedZonesType = 'Google_Service_Dns_ManagedZone';
|
||||
protected $managedZonesDataType = 'array';
|
||||
public $nextPageToken;
|
||||
|
||||
|
||||
public function setKind($kind)
|
||||
{
|
||||
$this->kind = $kind;
|
||||
}
|
||||
public function getKind()
|
||||
{
|
||||
return $this->kind;
|
||||
}
|
||||
public function setManagedZones($managedZones)
|
||||
{
|
||||
$this->managedZones = $managedZones;
|
||||
}
|
||||
public function getManagedZones()
|
||||
{
|
||||
return $this->managedZones;
|
||||
}
|
||||
public function setNextPageToken($nextPageToken)
|
||||
{
|
||||
$this->nextPageToken = $nextPageToken;
|
||||
}
|
||||
public function getNextPageToken()
|
||||
{
|
||||
return $this->nextPageToken;
|
||||
}
|
||||
}
|
||||
|
||||
class Google_Service_Dns_Project extends Google_Model
|
||||
{
|
||||
protected $internal_gapi_mappings = array(
|
||||
);
|
||||
public $id;
|
||||
public $kind;
|
||||
public $number;
|
||||
protected $quotaType = 'Google_Service_Dns_Quota';
|
||||
protected $quotaDataType = '';
|
||||
|
||||
|
||||
public function setId($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
public function setKind($kind)
|
||||
{
|
||||
$this->kind = $kind;
|
||||
}
|
||||
public function getKind()
|
||||
{
|
||||
return $this->kind;
|
||||
}
|
||||
public function setNumber($number)
|
||||
{
|
||||
$this->number = $number;
|
||||
}
|
||||
public function getNumber()
|
||||
{
|
||||
return $this->number;
|
||||
}
|
||||
public function setQuota(Google_Service_Dns_Quota $quota)
|
||||
{
|
||||
$this->quota = $quota;
|
||||
}
|
||||
public function getQuota()
|
||||
{
|
||||
return $this->quota;
|
||||
}
|
||||
}
|
||||
|
||||
class Google_Service_Dns_Quota extends Google_Model
|
||||
{
|
||||
protected $internal_gapi_mappings = array(
|
||||
);
|
||||
public $kind;
|
||||
public $managedZones;
|
||||
public $resourceRecordsPerRrset;
|
||||
public $rrsetAdditionsPerChange;
|
||||
public $rrsetDeletionsPerChange;
|
||||
public $rrsetsPerManagedZone;
|
||||
public $totalRrdataSizePerChange;
|
||||
|
||||
|
||||
public function setKind($kind)
|
||||
{
|
||||
$this->kind = $kind;
|
||||
}
|
||||
public function getKind()
|
||||
{
|
||||
return $this->kind;
|
||||
}
|
||||
public function setManagedZones($managedZones)
|
||||
{
|
||||
$this->managedZones = $managedZones;
|
||||
}
|
||||
public function getManagedZones()
|
||||
{
|
||||
return $this->managedZones;
|
||||
}
|
||||
public function setResourceRecordsPerRrset($resourceRecordsPerRrset)
|
||||
{
|
||||
$this->resourceRecordsPerRrset = $resourceRecordsPerRrset;
|
||||
}
|
||||
public function getResourceRecordsPerRrset()
|
||||
{
|
||||
return $this->resourceRecordsPerRrset;
|
||||
}
|
||||
public function setRrsetAdditionsPerChange($rrsetAdditionsPerChange)
|
||||
{
|
||||
$this->rrsetAdditionsPerChange = $rrsetAdditionsPerChange;
|
||||
}
|
||||
public function getRrsetAdditionsPerChange()
|
||||
{
|
||||
return $this->rrsetAdditionsPerChange;
|
||||
}
|
||||
public function setRrsetDeletionsPerChange($rrsetDeletionsPerChange)
|
||||
{
|
||||
$this->rrsetDeletionsPerChange = $rrsetDeletionsPerChange;
|
||||
}
|
||||
public function getRrsetDeletionsPerChange()
|
||||
{
|
||||
return $this->rrsetDeletionsPerChange;
|
||||
}
|
||||
public function setRrsetsPerManagedZone($rrsetsPerManagedZone)
|
||||
{
|
||||
$this->rrsetsPerManagedZone = $rrsetsPerManagedZone;
|
||||
}
|
||||
public function getRrsetsPerManagedZone()
|
||||
{
|
||||
return $this->rrsetsPerManagedZone;
|
||||
}
|
||||
public function setTotalRrdataSizePerChange($totalRrdataSizePerChange)
|
||||
{
|
||||
$this->totalRrdataSizePerChange = $totalRrdataSizePerChange;
|
||||
}
|
||||
public function getTotalRrdataSizePerChange()
|
||||
{
|
||||
return $this->totalRrdataSizePerChange;
|
||||
}
|
||||
}
|
||||
|
||||
class Google_Service_Dns_ResourceRecordSet extends Google_Collection
|
||||
{
|
||||
protected $collection_key = 'rrdatas';
|
||||
protected $internal_gapi_mappings = array(
|
||||
);
|
||||
public $kind;
|
||||
public $name;
|
||||
public $rrdatas;
|
||||
public $ttl;
|
||||
public $type;
|
||||
|
||||
|
||||
public function setKind($kind)
|
||||
{
|
||||
$this->kind = $kind;
|
||||
}
|
||||
public function getKind()
|
||||
{
|
||||
return $this->kind;
|
||||
}
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
public function setRrdatas($rrdatas)
|
||||
{
|
||||
$this->rrdatas = $rrdatas;
|
||||
}
|
||||
public function getRrdatas()
|
||||
{
|
||||
return $this->rrdatas;
|
||||
}
|
||||
public function setTtl($ttl)
|
||||
{
|
||||
$this->ttl = $ttl;
|
||||
}
|
||||
public function getTtl()
|
||||
{
|
||||
return $this->ttl;
|
||||
}
|
||||
public function setType($type)
|
||||
{
|
||||
$this->type = $type;
|
||||
}
|
||||
public function getType()
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
}
|
||||
|
||||
class Google_Service_Dns_ResourceRecordSetsListResponse extends Google_Collection
|
||||
{
|
||||
protected $collection_key = 'rrsets';
|
||||
protected $internal_gapi_mappings = array(
|
||||
);
|
||||
public $kind;
|
||||
public $nextPageToken;
|
||||
protected $rrsetsType = 'Google_Service_Dns_ResourceRecordSet';
|
||||
protected $rrsetsDataType = 'array';
|
||||
|
||||
|
||||
public function setKind($kind)
|
||||
{
|
||||
$this->kind = $kind;
|
||||
}
|
||||
public function getKind()
|
||||
{
|
||||
return $this->kind;
|
||||
}
|
||||
public function setNextPageToken($nextPageToken)
|
||||
{
|
||||
$this->nextPageToken = $nextPageToken;
|
||||
}
|
||||
public function getNextPageToken()
|
||||
{
|
||||
return $this->nextPageToken;
|
||||
}
|
||||
public function setRrsets($rrsets)
|
||||
{
|
||||
$this->rrsets = $rrsets;
|
||||
}
|
||||
public function getRrsets()
|
||||
{
|
||||
return $this->rrsets;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2014 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
class UDP_Google_Service_Exception extends Google_Exception implements Google_Task_Retryable
|
||||
{
|
||||
/**
|
||||
* Optional list of errors returned in a JSON body of an HTTP error response.
|
||||
*/
|
||||
protected $errors = array();
|
||||
|
||||
/**
|
||||
* @var array $retryMap Map of errors with retry counts.
|
||||
*/
|
||||
private $retryMap = array();
|
||||
|
||||
/**
|
||||
* Override default constructor to add the ability to set $errors and a retry
|
||||
* map.
|
||||
*
|
||||
* @param string $message
|
||||
* @param int $code
|
||||
* @param Exception|null $previous
|
||||
* @param [{string, string}] errors List of errors returned in an HTTP
|
||||
* response. Defaults to [].
|
||||
* @param array|null $retryMap Map of errors with retry counts.
|
||||
*/
|
||||
public function __construct(
|
||||
$message,
|
||||
$code = 0,
|
||||
Exception $previous = null,
|
||||
$errors = array(),
|
||||
array $retryMap = null
|
||||
) {
|
||||
if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
|
||||
parent::__construct($message, $code, $previous);
|
||||
} else {
|
||||
parent::__construct($message, $code);
|
||||
}
|
||||
|
||||
$this->errors = $errors;
|
||||
|
||||
if (is_array($retryMap)) {
|
||||
$this->retryMap = $retryMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An example of the possible errors returned.
|
||||
*
|
||||
* {
|
||||
* "domain": "global",
|
||||
* "reason": "authError",
|
||||
* "message": "Invalid Credentials",
|
||||
* "locationType": "header",
|
||||
* "location": "Authorization",
|
||||
* }
|
||||
*
|
||||
* @return [{string, string}] List of errors return in an HTTP response or [].
|
||||
*/
|
||||
public function getErrors()
|
||||
{
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of times the associated task can be retried.
|
||||
*
|
||||
* NOTE: -1 is returned if the task can be retried indefinitely
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function allowedRetries()
|
||||
{
|
||||
if (isset($this->retryMap[$this->code])) {
|
||||
return $this->retryMap[$this->code];
|
||||
}
|
||||
|
||||
$errors = $this->getErrors();
|
||||
|
||||
if (!empty($errors) && isset($errors[0]['reason']) &&
|
||||
isset($this->retryMap[$errors[0]['reason']])) {
|
||||
return $this->retryMap[$errors[0]['reason']];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,504 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Service definition for Oauth2 (v2).
|
||||
*
|
||||
* <p>
|
||||
* Lets you access OAuth2 protocol related APIs.</p>
|
||||
*
|
||||
* <p>
|
||||
* For more information about this service, see the API
|
||||
* <a href="https://developers.google.com/accounts/docs/OAuth2" target="_blank">Documentation</a>
|
||||
* </p>
|
||||
*
|
||||
* @author Google, Inc.
|
||||
*/
|
||||
class UDP_Google_Service_Oauth2 extends UDP_Google_Service
|
||||
{
|
||||
/** Know your basic profile info and list of people in your circles.. */
|
||||
const PLUS_LOGIN =
|
||||
"https://www.googleapis.com/auth/plus.login";
|
||||
/** Know who you are on Google. */
|
||||
const PLUS_ME =
|
||||
"https://www.googleapis.com/auth/plus.me";
|
||||
/** View your email address. */
|
||||
const USERINFO_EMAIL =
|
||||
"https://www.googleapis.com/auth/userinfo.email";
|
||||
/** View your basic profile info. */
|
||||
const USERINFO_PROFILE =
|
||||
"https://www.googleapis.com/auth/userinfo.profile";
|
||||
|
||||
public $userinfo;
|
||||
public $userinfo_v2_me;
|
||||
|
||||
private $serviceName;
|
||||
private $base_methods;
|
||||
|
||||
/**
|
||||
* Constructs the internal representation of the Oauth2 service.
|
||||
*
|
||||
* @param UDP_Google_Client $client
|
||||
*/
|
||||
public function __construct(UDP_Google_Client $client)
|
||||
{
|
||||
parent::__construct($client);
|
||||
$this->servicePath = '';
|
||||
$this->version = 'v2';
|
||||
$this->serviceName = 'oauth2';
|
||||
|
||||
$this->userinfo = new Google_Service_Oauth2_Userinfo_Resource(
|
||||
$this,
|
||||
$this->serviceName,
|
||||
'userinfo',
|
||||
array(
|
||||
'methods' => array(
|
||||
'get' => array(
|
||||
'path' => 'oauth2/v2/userinfo',
|
||||
'httpMethod' => 'GET',
|
||||
'parameters' => array(),
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
$this->userinfo_v2_me = new Google_Service_Oauth2_UserinfoV2Me_Resource(
|
||||
$this,
|
||||
$this->serviceName,
|
||||
'me',
|
||||
array(
|
||||
'methods' => array(
|
||||
'get' => array(
|
||||
'path' => 'userinfo/v2/me',
|
||||
'httpMethod' => 'GET',
|
||||
'parameters' => array(),
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
$this->base_methods = new UDP_Google_Service_Resource(
|
||||
$this,
|
||||
$this->serviceName,
|
||||
'',
|
||||
array(
|
||||
'methods' => array(
|
||||
'getCertForOpenIdConnect' => array(
|
||||
'path' => 'oauth2/v2/certs',
|
||||
'httpMethod' => 'GET',
|
||||
'parameters' => array(),
|
||||
),'tokeninfo' => array(
|
||||
'path' => 'oauth2/v2/tokeninfo',
|
||||
'httpMethod' => 'POST',
|
||||
'parameters' => array(
|
||||
'access_token' => array(
|
||||
'location' => 'query',
|
||||
'type' => 'string',
|
||||
),
|
||||
'id_token' => array(
|
||||
'location' => 'query',
|
||||
'type' => 'string',
|
||||
),
|
||||
'token_handle' => array(
|
||||
'location' => 'query',
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
/**
|
||||
* (getCertForOpenIdConnect)
|
||||
*
|
||||
* @param array $optParams Optional parameters.
|
||||
* @return Google_Service_Oauth2_Jwk
|
||||
*/
|
||||
public function getCertForOpenIdConnect($optParams = array())
|
||||
{
|
||||
$params = array();
|
||||
$params = array_merge($params, $optParams);
|
||||
return $this->base_methods->call('getCertForOpenIdConnect', array($params), "Google_Service_Oauth2_Jwk");
|
||||
}
|
||||
/**
|
||||
* (tokeninfo)
|
||||
*
|
||||
* @param array $optParams Optional parameters.
|
||||
*
|
||||
* @opt_param string access_token
|
||||
* @opt_param string id_token
|
||||
* @opt_param string token_handle
|
||||
* @return Google_Service_Oauth2_Tokeninfo
|
||||
*/
|
||||
public function tokeninfo($optParams = array())
|
||||
{
|
||||
$params = array();
|
||||
$params = array_merge($params, $optParams);
|
||||
return $this->base_methods->call('tokeninfo', array($params), "Google_Service_Oauth2_Tokeninfo");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The "userinfo" collection of methods.
|
||||
* Typical usage is:
|
||||
* <code>
|
||||
* $oauth2Service = new UDP_Google_Service_Oauth2(...);
|
||||
* $userinfo = $oauth2Service->userinfo;
|
||||
* </code>
|
||||
*/
|
||||
class Google_Service_Oauth2_Userinfo_Resource extends UDP_Google_Service_Resource
|
||||
{
|
||||
|
||||
/**
|
||||
* (userinfo.get)
|
||||
*
|
||||
* @param array $optParams Optional parameters.
|
||||
* @return Google_Service_Oauth2_Userinfoplus
|
||||
*/
|
||||
public function get($optParams = array())
|
||||
{
|
||||
$params = array();
|
||||
$params = array_merge($params, $optParams);
|
||||
return $this->call('get', array($params), "Google_Service_Oauth2_Userinfoplus");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The "v2" collection of methods.
|
||||
* Typical usage is:
|
||||
* <code>
|
||||
* $oauth2Service = new UDP_Google_Service_Oauth2(...);
|
||||
* $v2 = $oauth2Service->v2;
|
||||
* </code>
|
||||
*/
|
||||
class Google_Service_Oauth2_UserinfoV2_Resource extends UDP_Google_Service_Resource
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* The "me" collection of methods.
|
||||
* Typical usage is:
|
||||
* <code>
|
||||
* $oauth2Service = new UDP_Google_Service_Oauth2(...);
|
||||
* $me = $oauth2Service->me;
|
||||
* </code>
|
||||
*/
|
||||
class Google_Service_Oauth2_UserinfoV2Me_Resource extends UDP_Google_Service_Resource
|
||||
{
|
||||
|
||||
/**
|
||||
* (me.get)
|
||||
*
|
||||
* @param array $optParams Optional parameters.
|
||||
* @return Google_Service_Oauth2_Userinfoplus
|
||||
*/
|
||||
public function get($optParams = array())
|
||||
{
|
||||
$params = array();
|
||||
$params = array_merge($params, $optParams);
|
||||
return $this->call('get', array($params), "Google_Service_Oauth2_Userinfoplus");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
class Google_Service_Oauth2_Jwk extends Google_Collection
|
||||
{
|
||||
protected $collection_key = 'keys';
|
||||
protected $internal_gapi_mappings = array(
|
||||
);
|
||||
protected $keysType = 'Google_Service_Oauth2_JwkKeys';
|
||||
protected $keysDataType = 'array';
|
||||
|
||||
|
||||
public function setKeys($keys)
|
||||
{
|
||||
$this->keys = $keys;
|
||||
}
|
||||
public function getKeys()
|
||||
{
|
||||
return $this->keys;
|
||||
}
|
||||
}
|
||||
|
||||
class Google_Service_Oauth2_JwkKeys extends Google_Model
|
||||
{
|
||||
protected $internal_gapi_mappings = array(
|
||||
);
|
||||
public $alg;
|
||||
public $e;
|
||||
public $kid;
|
||||
public $kty;
|
||||
public $n;
|
||||
public $use;
|
||||
|
||||
|
||||
public function setAlg($alg)
|
||||
{
|
||||
$this->alg = $alg;
|
||||
}
|
||||
public function getAlg()
|
||||
{
|
||||
return $this->alg;
|
||||
}
|
||||
public function setE($e)
|
||||
{
|
||||
$this->e = $e;
|
||||
}
|
||||
public function getE()
|
||||
{
|
||||
return $this->e;
|
||||
}
|
||||
public function setKid($kid)
|
||||
{
|
||||
$this->kid = $kid;
|
||||
}
|
||||
public function getKid()
|
||||
{
|
||||
return $this->kid;
|
||||
}
|
||||
public function setKty($kty)
|
||||
{
|
||||
$this->kty = $kty;
|
||||
}
|
||||
public function getKty()
|
||||
{
|
||||
return $this->kty;
|
||||
}
|
||||
public function setN($n)
|
||||
{
|
||||
$this->n = $n;
|
||||
}
|
||||
public function getN()
|
||||
{
|
||||
return $this->n;
|
||||
}
|
||||
public function setUse($use)
|
||||
{
|
||||
$this->use = $use;
|
||||
}
|
||||
public function getUse()
|
||||
{
|
||||
return $this->use;
|
||||
}
|
||||
}
|
||||
|
||||
class Google_Service_Oauth2_Tokeninfo extends Google_Model
|
||||
{
|
||||
protected $internal_gapi_mappings = array(
|
||||
"accessType" => "access_type",
|
||||
"expiresIn" => "expires_in",
|
||||
"issuedTo" => "issued_to",
|
||||
"tokenHandle" => "token_handle",
|
||||
"userId" => "user_id",
|
||||
"verifiedEmail" => "verified_email",
|
||||
);
|
||||
public $accessType;
|
||||
public $audience;
|
||||
public $email;
|
||||
public $expiresIn;
|
||||
public $issuedTo;
|
||||
public $scope;
|
||||
public $tokenHandle;
|
||||
public $userId;
|
||||
public $verifiedEmail;
|
||||
|
||||
|
||||
public function setAccessType($accessType)
|
||||
{
|
||||
$this->accessType = $accessType;
|
||||
}
|
||||
public function getAccessType()
|
||||
{
|
||||
return $this->accessType;
|
||||
}
|
||||
public function setAudience($audience)
|
||||
{
|
||||
$this->audience = $audience;
|
||||
}
|
||||
public function getAudience()
|
||||
{
|
||||
return $this->audience;
|
||||
}
|
||||
public function setEmail($email)
|
||||
{
|
||||
$this->email = $email;
|
||||
}
|
||||
public function getEmail()
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
public function setExpiresIn($expiresIn)
|
||||
{
|
||||
$this->expiresIn = $expiresIn;
|
||||
}
|
||||
public function getExpiresIn()
|
||||
{
|
||||
return $this->expiresIn;
|
||||
}
|
||||
public function setIssuedTo($issuedTo)
|
||||
{
|
||||
$this->issuedTo = $issuedTo;
|
||||
}
|
||||
public function getIssuedTo()
|
||||
{
|
||||
return $this->issuedTo;
|
||||
}
|
||||
public function setScope($scope)
|
||||
{
|
||||
$this->scope = $scope;
|
||||
}
|
||||
public function getScope()
|
||||
{
|
||||
return $this->scope;
|
||||
}
|
||||
public function setTokenHandle($tokenHandle)
|
||||
{
|
||||
$this->tokenHandle = $tokenHandle;
|
||||
}
|
||||
public function getTokenHandle()
|
||||
{
|
||||
return $this->tokenHandle;
|
||||
}
|
||||
public function setUserId($userId)
|
||||
{
|
||||
$this->userId = $userId;
|
||||
}
|
||||
public function getUserId()
|
||||
{
|
||||
return $this->userId;
|
||||
}
|
||||
public function setVerifiedEmail($verifiedEmail)
|
||||
{
|
||||
$this->verifiedEmail = $verifiedEmail;
|
||||
}
|
||||
public function getVerifiedEmail()
|
||||
{
|
||||
return $this->verifiedEmail;
|
||||
}
|
||||
}
|
||||
|
||||
class Google_Service_Oauth2_Userinfoplus extends Google_Model
|
||||
{
|
||||
protected $internal_gapi_mappings = array(
|
||||
"familyName" => "family_name",
|
||||
"givenName" => "given_name",
|
||||
"verifiedEmail" => "verified_email",
|
||||
);
|
||||
public $email;
|
||||
public $familyName;
|
||||
public $gender;
|
||||
public $givenName;
|
||||
public $hd;
|
||||
public $id;
|
||||
public $link;
|
||||
public $locale;
|
||||
public $name;
|
||||
public $picture;
|
||||
public $verifiedEmail;
|
||||
|
||||
|
||||
public function setEmail($email)
|
||||
{
|
||||
$this->email = $email;
|
||||
}
|
||||
public function getEmail()
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
public function setFamilyName($familyName)
|
||||
{
|
||||
$this->familyName = $familyName;
|
||||
}
|
||||
public function getFamilyName()
|
||||
{
|
||||
return $this->familyName;
|
||||
}
|
||||
public function setGender($gender)
|
||||
{
|
||||
$this->gender = $gender;
|
||||
}
|
||||
public function getGender()
|
||||
{
|
||||
return $this->gender;
|
||||
}
|
||||
public function setGivenName($givenName)
|
||||
{
|
||||
$this->givenName = $givenName;
|
||||
}
|
||||
public function getGivenName()
|
||||
{
|
||||
return $this->givenName;
|
||||
}
|
||||
public function setHd($hd)
|
||||
{
|
||||
$this->hd = $hd;
|
||||
}
|
||||
public function getHd()
|
||||
{
|
||||
return $this->hd;
|
||||
}
|
||||
public function setId($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
public function setLink($link)
|
||||
{
|
||||
$this->link = $link;
|
||||
}
|
||||
public function getLink()
|
||||
{
|
||||
return $this->link;
|
||||
}
|
||||
public function setLocale($locale)
|
||||
{
|
||||
$this->locale = $locale;
|
||||
}
|
||||
public function getLocale()
|
||||
{
|
||||
return $this->locale;
|
||||
}
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
public function setPicture($picture)
|
||||
{
|
||||
$this->picture = $picture;
|
||||
}
|
||||
public function getPicture()
|
||||
{
|
||||
return $this->picture;
|
||||
}
|
||||
public function setVerifiedEmail($verifiedEmail)
|
||||
{
|
||||
$this->verifiedEmail = $verifiedEmail;
|
||||
}
|
||||
public function getVerifiedEmail()
|
||||
{
|
||||
return $this->verifiedEmail;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the actual methods/resources of the discovered Google API using magic function
|
||||
* calling overloading (__call()), which on call will see if the method name (plus.activities.list)
|
||||
* is available in this service, and if so construct an apiHttpRequest representing it.
|
||||
*
|
||||
*/
|
||||
class UDP_Google_Service_Resource
|
||||
{
|
||||
// Valid query parameters that work, but don't appear in discovery.
|
||||
private $stackParameters = array(
|
||||
'alt' => array('type' => 'string', 'location' => 'query'),
|
||||
'fields' => array('type' => 'string', 'location' => 'query'),
|
||||
'trace' => array('type' => 'string', 'location' => 'query'),
|
||||
'userIp' => array('type' => 'string', 'location' => 'query'),
|
||||
'quotaUser' => array('type' => 'string', 'location' => 'query'),
|
||||
'data' => array('type' => 'string', 'location' => 'body'),
|
||||
'mimeType' => array('type' => 'string', 'location' => 'header'),
|
||||
'uploadType' => array('type' => 'string', 'location' => 'query'),
|
||||
'mediaUpload' => array('type' => 'complex', 'location' => 'query'),
|
||||
'prettyPrint' => array('type' => 'string', 'location' => 'query'),
|
||||
);
|
||||
|
||||
/** @var string $rootUrl */
|
||||
private $rootUrl;
|
||||
|
||||
/** @var Google_Client $client */
|
||||
private $client;
|
||||
|
||||
/** @var string $serviceName */
|
||||
private $serviceName;
|
||||
|
||||
/** @var string $servicePath */
|
||||
private $servicePath;
|
||||
|
||||
/** @var string $resourceName */
|
||||
private $resourceName;
|
||||
|
||||
/** @var array $methods */
|
||||
private $methods;
|
||||
|
||||
public function __construct($service, $serviceName, $resourceName, $resource)
|
||||
{
|
||||
$this->rootUrl = $service->rootUrl;
|
||||
$this->client = $service->getClient();
|
||||
$this->servicePath = $service->servicePath;
|
||||
$this->serviceName = $serviceName;
|
||||
$this->resourceName = $resourceName;
|
||||
$this->methods = is_array($resource) && isset($resource['methods']) ?
|
||||
$resource['methods'] :
|
||||
array($resourceName => $resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO(ianbarber): This function needs simplifying.
|
||||
* @param $name
|
||||
* @param $arguments
|
||||
* @param $expected_class - optional, the expected class name
|
||||
* @return Google_Http_Request|expected_class
|
||||
* @throws Google_Exception
|
||||
*/
|
||||
public function call($name, $arguments, $expected_class = null)
|
||||
{
|
||||
if (! isset($this->methods[$name])) {
|
||||
$this->client->getLogger()->error(
|
||||
'Service method unknown',
|
||||
array(
|
||||
'service' => $this->serviceName,
|
||||
'resource' => $this->resourceName,
|
||||
'method' => $name
|
||||
)
|
||||
);
|
||||
|
||||
throw new Google_Exception(
|
||||
"Unknown function: " .
|
||||
"{$this->serviceName}->{$this->resourceName}->{$name}()" // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
|
||||
);
|
||||
}
|
||||
$method = $this->methods[$name];
|
||||
$parameters = $arguments[0];
|
||||
|
||||
// postBody is a special case since it's not defined in the discovery
|
||||
// document as parameter, but we abuse the param entry for storing it.
|
||||
$postBody = null;
|
||||
if (isset($parameters['postBody'])) {
|
||||
if ($parameters['postBody'] instanceof Google_Model) {
|
||||
// In the cases the post body is an existing object, we want
|
||||
// to use the smart method to create a simple object for
|
||||
// for JSONification.
|
||||
$parameters['postBody'] = $parameters['postBody']->toSimpleObject();
|
||||
} else if (is_object($parameters['postBody'])) {
|
||||
// If the post body is another kind of object, we will try and
|
||||
// wrangle it into a sensible format.
|
||||
$parameters['postBody'] =
|
||||
$this->convertToArrayAndStripNulls($parameters['postBody']);
|
||||
}
|
||||
$postBody = json_encode($parameters['postBody']);
|
||||
unset($parameters['postBody']);
|
||||
}
|
||||
|
||||
// TODO(ianbarber): optParams here probably should have been
|
||||
// handled already - this may well be redundant code.
|
||||
if (isset($parameters['optParams'])) {
|
||||
$optParams = $parameters['optParams'];
|
||||
unset($parameters['optParams']);
|
||||
$parameters = array_merge($parameters, $optParams);
|
||||
}
|
||||
|
||||
if (!isset($method['parameters'])) {
|
||||
$method['parameters'] = array();
|
||||
}
|
||||
|
||||
$method['parameters'] = array_merge(
|
||||
$method['parameters'],
|
||||
$this->stackParameters
|
||||
);
|
||||
foreach ($parameters as $key => $val) {
|
||||
if ($key != 'postBody' && ! isset($method['parameters'][$key])) {
|
||||
$this->client->getLogger()->error(
|
||||
'Service parameter unknown',
|
||||
array(
|
||||
'service' => $this->serviceName,
|
||||
'resource' => $this->resourceName,
|
||||
'method' => $name,
|
||||
'parameter' => $key
|
||||
)
|
||||
);
|
||||
throw new Google_Exception("($name) unknown parameter: '$key'"); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($method['parameters'] as $paramName => $paramSpec) {
|
||||
if (isset($paramSpec['required']) &&
|
||||
$paramSpec['required'] &&
|
||||
! isset($parameters[$paramName])
|
||||
) {
|
||||
$this->client->getLogger()->error(
|
||||
'Service parameter missing',
|
||||
array(
|
||||
'service' => $this->serviceName,
|
||||
'resource' => $this->resourceName,
|
||||
'method' => $name,
|
||||
'parameter' => $paramName
|
||||
)
|
||||
);
|
||||
throw new Google_Exception("($name) missing required param: '$paramName'"); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
|
||||
}
|
||||
if (isset($parameters[$paramName])) {
|
||||
$value = $parameters[$paramName];
|
||||
$parameters[$paramName] = $paramSpec;
|
||||
$parameters[$paramName]['value'] = $value;
|
||||
unset($parameters[$paramName]['required']);
|
||||
} else {
|
||||
// Ensure we don't pass nulls.
|
||||
unset($parameters[$paramName]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->client->getLogger()->info(
|
||||
'Service Call',
|
||||
array(
|
||||
'service' => $this->serviceName,
|
||||
'resource' => $this->resourceName,
|
||||
'method' => $name,
|
||||
'arguments' => $parameters,
|
||||
)
|
||||
);
|
||||
|
||||
$url = UDP_Google_Http_REST::createRequestUri(
|
||||
$this->servicePath,
|
||||
$method['path'],
|
||||
$parameters
|
||||
);
|
||||
$httpRequest = new UDP_Google_Http_Request(
|
||||
$url,
|
||||
$method['httpMethod'],
|
||||
null,
|
||||
$postBody
|
||||
);
|
||||
|
||||
if ($this->rootUrl) {
|
||||
$httpRequest->setBaseComponent($this->rootUrl);
|
||||
} else {
|
||||
$httpRequest->setBaseComponent($this->client->getBasePath());
|
||||
}
|
||||
|
||||
if ($postBody) {
|
||||
$contentTypeHeader = array();
|
||||
$contentTypeHeader['content-type'] = 'application/json; charset=UTF-8';
|
||||
$httpRequest->setRequestHeaders($contentTypeHeader);
|
||||
$httpRequest->setPostBody($postBody);
|
||||
}
|
||||
|
||||
$httpRequest = $this->client->getAuth()->sign($httpRequest);
|
||||
$httpRequest->setExpectedClass($expected_class);
|
||||
|
||||
if (isset($parameters['data']) &&
|
||||
($parameters['uploadType']['value'] == 'media' || $parameters['uploadType']['value'] == 'multipart')) {
|
||||
// If we are doing a simple media upload, trigger that as a convenience.
|
||||
$mfu = new Google_Http_MediaFileUpload(
|
||||
$this->client,
|
||||
$httpRequest,
|
||||
isset($parameters['mimeType']) ? $parameters['mimeType']['value'] : 'application/octet-stream',
|
||||
$parameters['data']['value']
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($parameters['alt']) && $parameters['alt']['value'] == 'media') {
|
||||
$httpRequest->enableExpectedRaw();
|
||||
}
|
||||
|
||||
if ($this->client->shouldDefer()) {
|
||||
// If we are in batch or upload mode, return the raw request.
|
||||
return $httpRequest;
|
||||
}
|
||||
|
||||
return $this->client->execute($httpRequest);
|
||||
}
|
||||
|
||||
protected function convertToArrayAndStripNulls($o)
|
||||
{
|
||||
$o = (array) $o;
|
||||
foreach ($o as $k => $v) {
|
||||
if ($v === null) {
|
||||
unset($o[$k]);
|
||||
} elseif (is_object($v) || is_array($v)) {
|
||||
$o[$k] = $this->convertToArrayAndStripNulls($o[$k]);
|
||||
}
|
||||
}
|
||||
return $o;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Signs data.
|
||||
*
|
||||
* @author Brian Eaton <beaton@google.com>
|
||||
*/
|
||||
abstract class Google_Signer_Abstract
|
||||
{
|
||||
/**
|
||||
* Signs data, returns the signature as binary data.
|
||||
*/
|
||||
abstract public function sign($data);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs data.
|
||||
*
|
||||
* Only used for testing.
|
||||
*
|
||||
* @author Brian Eaton <beaton@google.com>
|
||||
*/
|
||||
class Google_Signer_P12 extends Google_Signer_Abstract
|
||||
{
|
||||
// OpenSSL private key resource
|
||||
private $privateKey;
|
||||
|
||||
// Creates a new signer from a .p12 file.
|
||||
public function __construct($p12, $password)
|
||||
{
|
||||
if (!function_exists('openssl_x509_read')) {
|
||||
throw new Google_Exception(
|
||||
'The Google PHP API library needs the openssl PHP extension'
|
||||
);
|
||||
}
|
||||
|
||||
// If the private key is provided directly, then this isn't in the p12
|
||||
// format. Different versions of openssl support different p12 formats
|
||||
// and the key from google wasn't being accepted by the version available
|
||||
// at the time.
|
||||
if (!$password && strpos($p12, "-----BEGIN RSA PRIVATE KEY-----") !== false) {
|
||||
$this->privateKey = openssl_pkey_get_private($p12);
|
||||
} elseif($password === 'notasecret' && strpos($p12, "-----BEGIN PRIVATE KEY-----") !== false) {
|
||||
$this->privateKey = openssl_pkey_get_private($p12);
|
||||
} else {
|
||||
// This throws on error
|
||||
$certs = array();
|
||||
if (!openssl_pkcs12_read($p12, $certs, $password)) {
|
||||
throw new Google_Auth_Exception(
|
||||
"Unable to parse the p12 file. " .
|
||||
"Is this a .p12 file? Is the password correct? OpenSSL error: " .
|
||||
openssl_error_string() // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
|
||||
);
|
||||
}
|
||||
// TODO(beaton): is this part of the contract for the openssl_pkcs12_read
|
||||
// method? What happens if there are multiple private keys? Do we care?
|
||||
if (!array_key_exists("pkey", $certs) || !$certs["pkey"]) {
|
||||
throw new Google_Auth_Exception("No private key found in p12 file.");
|
||||
}
|
||||
$this->privateKey = openssl_pkey_get_private($certs['pkey']);
|
||||
}
|
||||
|
||||
if (!$this->privateKey) {
|
||||
throw new Google_Auth_Exception("Unable to load private key");
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->privateKey) {
|
||||
openssl_pkey_free($this->privateKey);
|
||||
}
|
||||
}
|
||||
|
||||
public function sign($data)
|
||||
{
|
||||
if (version_compare(PHP_VERSION, '5.3.0') < 0) {
|
||||
throw new Google_Auth_Exception(
|
||||
"PHP 5.3.0 or higher is required to use service accounts."
|
||||
);
|
||||
}
|
||||
// @codingStandardsIgnoreLine
|
||||
$hash = defined("OPENSSL_ALGO_SHA256") ? OPENSSL_ALGO_SHA256 : "sha256";
|
||||
if (!openssl_sign($data, $signature, $this->privateKey, $hash)) {
|
||||
throw new Google_Auth_Exception("Unable to sign data");
|
||||
}
|
||||
return $signature;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2014 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
class Google_Task_Exception extends Google_Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2014 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for checking how many times a given task can be retried following
|
||||
* a failure.
|
||||
*/
|
||||
interface Google_Task_Retryable
|
||||
{
|
||||
/**
|
||||
* Gets the number of times the associated task can be retried.
|
||||
*
|
||||
* NOTE: -1 is returned if the task can be retried indefinitely
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function allowedRetries();
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2014 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* A task runner with exponential backoff support.
|
||||
*
|
||||
* @see https://developers.google.com/drive/web/handle-errors#implementing_exponential_backoff
|
||||
*/
|
||||
class UDP_Google_Task_Runner
|
||||
{
|
||||
/**
|
||||
* @var integer $maxDelay The max time (in seconds) to wait before a retry.
|
||||
*/
|
||||
private $maxDelay = 60;
|
||||
/**
|
||||
* @var integer $delay The previous delay from which the next is calculated.
|
||||
*/
|
||||
private $delay = 1;
|
||||
|
||||
/**
|
||||
* @var integer $factor The base number for the exponential back off.
|
||||
*/
|
||||
private $factor = 2;
|
||||
/**
|
||||
* @var float $jitter A random number between -$jitter and $jitter will be
|
||||
* added to $factor on each iteration to allow for a better distribution of
|
||||
* retries.
|
||||
*/
|
||||
private $jitter = 0.5;
|
||||
|
||||
/**
|
||||
* @var integer $attempts The number of attempts that have been tried so far.
|
||||
*/
|
||||
private $attempts = 0;
|
||||
/**
|
||||
* @var integer $maxAttempts The max number of attempts allowed.
|
||||
*/
|
||||
private $maxAttempts = 1;
|
||||
|
||||
/**
|
||||
* @var UDP_Google_Client $client The current API client.
|
||||
*/
|
||||
private $client;
|
||||
|
||||
/**
|
||||
* @var string $name The name of the current task (used for logging).
|
||||
*/
|
||||
private $name;
|
||||
/**
|
||||
* @var callable $action The task to run and possibly retry.
|
||||
*/
|
||||
private $action;
|
||||
/**
|
||||
* @var array $arguments The task arguments.
|
||||
*/
|
||||
private $arguments;
|
||||
|
||||
/**
|
||||
* Creates a new task runner with exponential backoff support.
|
||||
*
|
||||
* @param UDP_Google_Client $client The current API client
|
||||
* @param string $name The name of the current task (used for logging)
|
||||
* @param callable $action The task to run and possibly retry
|
||||
* @param array $arguments The task arguments
|
||||
* @throws Google_Task_Exception when misconfigured
|
||||
*/
|
||||
public function __construct(
|
||||
UDP_Google_Client $client,
|
||||
$name,
|
||||
$action,
|
||||
array $arguments = array()
|
||||
) {
|
||||
$config = (array) $client->getClassConfig('UDP_Google_Task_Runner');
|
||||
|
||||
if (isset($config['initial_delay'])) {
|
||||
if ($config['initial_delay'] < 0) {
|
||||
throw new Google_Task_Exception(
|
||||
'Task configuration `initial_delay` must not be negative.'
|
||||
);
|
||||
}
|
||||
|
||||
$this->delay = $config['initial_delay'];
|
||||
}
|
||||
|
||||
if (isset($config['max_delay'])) {
|
||||
if ($config['max_delay'] <= 0) {
|
||||
throw new Google_Task_Exception(
|
||||
'Task configuration `max_delay` must be greater than 0.'
|
||||
);
|
||||
}
|
||||
|
||||
$this->maxDelay = $config['max_delay'];
|
||||
}
|
||||
|
||||
if (isset($config['factor'])) {
|
||||
if ($config['factor'] <= 0) {
|
||||
throw new Google_Task_Exception(
|
||||
'Task configuration `factor` must be greater than 0.'
|
||||
);
|
||||
}
|
||||
|
||||
$this->factor = $config['factor'];
|
||||
}
|
||||
|
||||
if (isset($config['jitter'])) {
|
||||
if ($config['jitter'] <= 0) {
|
||||
throw new Google_Task_Exception(
|
||||
'Task configuration `jitter` must be greater than 0.'
|
||||
);
|
||||
}
|
||||
|
||||
$this->jitter = $config['jitter'];
|
||||
}
|
||||
|
||||
if (isset($config['retries'])) {
|
||||
if ($config['retries'] < 0) {
|
||||
throw new Google_Task_Exception(
|
||||
'Task configuration `retries` must not be negative.'
|
||||
);
|
||||
}
|
||||
$this->maxAttempts += $config['retries'];
|
||||
}
|
||||
|
||||
if (!is_callable($action)) {
|
||||
throw new Google_Task_Exception(
|
||||
'Task argument `$action` must be a valid callable.'
|
||||
);
|
||||
}
|
||||
|
||||
$this->name = $name;
|
||||
$this->client = $client;
|
||||
$this->action = $action;
|
||||
$this->arguments = $arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a retry can be attempted.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function canAttmpt()
|
||||
{
|
||||
return $this->attempts < $this->maxAttempts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the task and (if applicable) automatically retries when errors occur.
|
||||
*
|
||||
* @return mixed
|
||||
* @throws Google_Task_Retryable on failure when no retries are available.
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
while ($this->attempt()) {
|
||||
try {
|
||||
return call_user_func_array($this->action, $this->arguments);
|
||||
} catch (Google_Task_Retryable $exception) {
|
||||
$allowedRetries = $exception->allowedRetries();
|
||||
|
||||
if (!$this->canAttmpt() || !$allowedRetries) {
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
if ($allowedRetries > 0) {
|
||||
$this->maxAttempts = min(
|
||||
$this->maxAttempts,
|
||||
$this->attempts + $allowedRetries
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a task once, if possible. This is useful for bypassing the `run()`
|
||||
* loop.
|
||||
*
|
||||
* NOTE: If this is not the first attempt, this function will sleep in
|
||||
* accordance to the backoff configurations before running the task.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function attempt()
|
||||
{
|
||||
if (!$this->canAttmpt()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->attempts > 0) {
|
||||
$this->backOff();
|
||||
}
|
||||
|
||||
$this->attempts++;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleeps in accordance to the backoff configurations.
|
||||
*/
|
||||
private function backOff()
|
||||
{
|
||||
$delay = $this->getDelay();
|
||||
|
||||
$this->client->getLogger()->debug(
|
||||
'Retrying task with backoff',
|
||||
array(
|
||||
'request' => $this->name,
|
||||
'retry' => $this->attempts,
|
||||
'backoff_seconds' => $delay
|
||||
)
|
||||
);
|
||||
|
||||
usleep($delay * 1000000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the delay (in seconds) for the current backoff period.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
private function getDelay()
|
||||
{
|
||||
$jitter = $this->getJitter();
|
||||
$factor = $this->attempts > 1 ? $this->factor + $jitter : 1 + abs($jitter);
|
||||
|
||||
return $this->delay = min($this->maxDelay, $this->delay * $factor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current jitter (random number between -$this->jitter and
|
||||
* $this->jitter).
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
private function getJitter()
|
||||
{
|
||||
return $this->jitter * 2 * mt_rand() / mt_getrandmax() - $this->jitter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Collection of static utility methods used for convenience across
|
||||
* the client library.
|
||||
*/
|
||||
class Google_Utils
|
||||
{
|
||||
public static function urlSafeB64Encode($data)
|
||||
{
|
||||
$b64 = base64_encode($data);
|
||||
$b64 = str_replace(
|
||||
array('+', '/', '\r', '\n', '='),
|
||||
array('-', '_'),
|
||||
$b64
|
||||
);
|
||||
return $b64;
|
||||
}
|
||||
|
||||
public static function urlSafeB64Decode($b64)
|
||||
{
|
||||
$b64 = str_replace(
|
||||
array('-', '_'),
|
||||
array('+', '/'),
|
||||
$b64
|
||||
);
|
||||
return base64_decode($b64);
|
||||
}
|
||||
|
||||
/**
|
||||
* Misc function used to count the number of bytes in a post body, in the
|
||||
* world of multi-byte chars and the unpredictability of
|
||||
* strlen/mb_strlen/sizeof, this is the only way to do that in a sane
|
||||
* manner at the moment.
|
||||
*
|
||||
* This algorithm was originally developed for the
|
||||
* Solar Framework by Paul M. Jones
|
||||
*
|
||||
* @link http://solarphp.com/
|
||||
* @link http://svn.solarphp.com/core/trunk/Solar/Json.php
|
||||
* @link http://framework.zend.com/svn/framework/standard/trunk/library/Zend/Json/Decoder.php
|
||||
* @param string $str
|
||||
* @return int The number of bytes in a string.
|
||||
*/
|
||||
public static function getStrLen($str)
|
||||
{
|
||||
$strlenVar = strlen($str);
|
||||
$d = $ret = 0;
|
||||
for ($count = 0; $count < $strlenVar; ++ $count) {
|
||||
$ordinalValue = ord($str[$ret]);
|
||||
switch (true) {
|
||||
case (($ordinalValue >= 0x20) && ($ordinalValue <= 0x7F)):
|
||||
// characters U-00000000 - U-0000007F (same as ASCII)
|
||||
$ret ++;
|
||||
break;
|
||||
case (($ordinalValue & 0xE0) == 0xC0):
|
||||
// characters U-00000080 - U-000007FF, mask 110XXXXX
|
||||
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
||||
$ret += 2;
|
||||
break;
|
||||
case (($ordinalValue & 0xF0) == 0xE0):
|
||||
// characters U-00000800 - U-0000FFFF, mask 1110XXXX
|
||||
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
||||
$ret += 3;
|
||||
break;
|
||||
case (($ordinalValue & 0xF8) == 0xF0):
|
||||
// characters U-00010000 - U-001FFFFF, mask 11110XXX
|
||||
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
||||
$ret += 4;
|
||||
break;
|
||||
case (($ordinalValue & 0xFC) == 0xF8):
|
||||
// characters U-00200000 - U-03FFFFFF, mask 111110XX
|
||||
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
||||
$ret += 5;
|
||||
break;
|
||||
case (($ordinalValue & 0xFE) == 0xFC):
|
||||
// characters U-04000000 - U-7FFFFFFF, mask 1111110X
|
||||
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
||||
$ret += 6;
|
||||
break;
|
||||
default:
|
||||
$ret ++;
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize all keys in an array to lower-case.
|
||||
* @param array $arr
|
||||
* @return array Normalized array.
|
||||
*/
|
||||
public static function normalize($arr)
|
||||
{
|
||||
if (!is_array($arr)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$normalized = array();
|
||||
foreach ($arr as $key => $val) {
|
||||
$normalized[strtolower($key)] = $val;
|
||||
}
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string to camelCase
|
||||
* @param string $value
|
||||
* @return string
|
||||
*/
|
||||
public static function camelCase($value)
|
||||
{
|
||||
$value = ucwords(str_replace(array('-', '_'), ' ', $value));
|
||||
$value = str_replace(' ', '', $value);
|
||||
$value[0] = strtolower($value[0]);
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of levels 1-3 of the URI Template spec.
|
||||
* @see http://tools.ietf.org/html/rfc6570
|
||||
*/
|
||||
class Google_Utils_URITemplate
|
||||
{
|
||||
const TYPE_MAP = "1";
|
||||
const TYPE_LIST = "2";
|
||||
const TYPE_SCALAR = "4";
|
||||
|
||||
/**
|
||||
* @var $operators array
|
||||
* These are valid at the start of a template block to
|
||||
* modify the way in which the variables inside are
|
||||
* processed.
|
||||
*/
|
||||
private $operators = array(
|
||||
"+" => "reserved",
|
||||
"/" => "segments",
|
||||
"." => "dotprefix",
|
||||
"#" => "fragment",
|
||||
";" => "semicolon",
|
||||
"?" => "form",
|
||||
"&" => "continuation"
|
||||
);
|
||||
|
||||
/**
|
||||
* @var reserved array
|
||||
* These are the characters which should not be URL encoded in reserved
|
||||
* strings.
|
||||
*/
|
||||
private $reserved = array(
|
||||
"=", ",", "!", "@", "|", ":", "/", "?", "#",
|
||||
"[", "]",'$', "&", "'", "(", ")", "*", "+", ";"
|
||||
);
|
||||
private $reservedEncoded = array(
|
||||
"%3D", "%2C", "%21", "%40", "%7C", "%3A", "%2F", "%3F",
|
||||
"%23", "%5B", "%5D", "%24", "%26", "%27", "%28", "%29",
|
||||
"%2A", "%2B", "%3B"
|
||||
);
|
||||
|
||||
public function parse($string, array $parameters)
|
||||
{
|
||||
return $this->resolveNextSection($string, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function finds the first matching {...} block and
|
||||
* executes the replacement. It then calls itself to find
|
||||
* subsequent blocks, if any.
|
||||
*/
|
||||
private function resolveNextSection($string, $parameters)
|
||||
{
|
||||
$start = strpos($string, "{");
|
||||
if ($start === false) {
|
||||
return $string;
|
||||
}
|
||||
$end = strpos($string, "}");
|
||||
if ($end === false) {
|
||||
return $string;
|
||||
}
|
||||
$string = $this->replace($string, $start, $end, $parameters);
|
||||
return $this->resolveNextSection($string, $parameters);
|
||||
}
|
||||
|
||||
private function replace($string, $start, $end, $parameters)
|
||||
{
|
||||
// We know a data block will have {} round it, so we can strip that.
|
||||
$data = substr($string, $start + 1, $end - $start - 1);
|
||||
|
||||
// If the first character is one of the reserved operators, it effects
|
||||
// the processing of the stream.
|
||||
if (isset($this->operators[$data[0]])) {
|
||||
$op = $this->operators[$data[0]];
|
||||
$data = substr($data, 1);
|
||||
$prefix = "";
|
||||
$prefix_on_missing = false;
|
||||
|
||||
switch ($op) {
|
||||
case "reserved":
|
||||
// Reserved means certain characters should not be URL encoded
|
||||
$data = $this->replaceVars($data, $parameters, ",", null, true);
|
||||
break;
|
||||
case "fragment":
|
||||
// Comma separated with fragment prefix. Bare values only.
|
||||
$prefix = "#";
|
||||
$prefix_on_missing = true;
|
||||
$data = $this->replaceVars($data, $parameters, ",", null, true);
|
||||
break;
|
||||
case "segments":
|
||||
// Slash separated data. Bare values only.
|
||||
$prefix = "/";
|
||||
$data =$this->replaceVars($data, $parameters, "/");
|
||||
break;
|
||||
case "dotprefix":
|
||||
// Dot separated data. Bare values only.
|
||||
$prefix = ".";
|
||||
$prefix_on_missing = true;
|
||||
$data = $this->replaceVars($data, $parameters, ".");
|
||||
break;
|
||||
case "semicolon":
|
||||
// Semicolon prefixed and separated. Uses the key name
|
||||
$prefix = ";";
|
||||
$data = $this->replaceVars($data, $parameters, ";", "=", false, true, false);
|
||||
break;
|
||||
case "form":
|
||||
// Standard URL format. Uses the key name
|
||||
$prefix = "?";
|
||||
$data = $this->replaceVars($data, $parameters, "&", "=");
|
||||
break;
|
||||
case "continuation":
|
||||
// Standard URL, but with leading ampersand. Uses key name.
|
||||
$prefix = "&";
|
||||
$data = $this->replaceVars($data, $parameters, "&", "=");
|
||||
break;
|
||||
}
|
||||
|
||||
// Add the initial prefix character if data is valid.
|
||||
if ($data || ($data !== false && $prefix_on_missing)) {
|
||||
$data = $prefix . $data;
|
||||
}
|
||||
|
||||
} else {
|
||||
// If no operator we replace with the defaults.
|
||||
$data = $this->replaceVars($data, $parameters);
|
||||
}
|
||||
// This is chops out the {...} and replaces with the new section.
|
||||
return substr($string, 0, $start) . $data . substr($string, $end + 1);
|
||||
}
|
||||
|
||||
private function replaceVars(
|
||||
$section,
|
||||
$parameters,
|
||||
$sep = ",",
|
||||
$combine = null,
|
||||
$reserved = false,
|
||||
$tag_empty = false,
|
||||
$combine_on_empty = true
|
||||
) {
|
||||
if (strpos($section, ",") === false) {
|
||||
// If we only have a single value, we can immediately process.
|
||||
return $this->combine(
|
||||
$section,
|
||||
$parameters,
|
||||
$sep,
|
||||
$combine,
|
||||
$reserved,
|
||||
$tag_empty,
|
||||
$combine_on_empty
|
||||
);
|
||||
} else {
|
||||
// If we have multiple values, we need to split and loop over them.
|
||||
// Each is treated individually, then glued together with the
|
||||
// separator character.
|
||||
$vars = explode(",", $section);
|
||||
return $this->combineList(
|
||||
$vars,
|
||||
$sep,
|
||||
$parameters,
|
||||
$combine,
|
||||
$reserved,
|
||||
false, // Never emit empty strings in multi-param replacements
|
||||
$combine_on_empty
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function combine(
|
||||
$key,
|
||||
$parameters,
|
||||
$sep,
|
||||
$combine,
|
||||
$reserved,
|
||||
$tag_empty,
|
||||
$combine_on_empty
|
||||
) {
|
||||
$length = false;
|
||||
$explode = false;
|
||||
$skip_final_combine = false;
|
||||
$value = false;
|
||||
|
||||
// Check for length restriction.
|
||||
if (strpos($key, ":") !== false) {
|
||||
list($key, $length) = explode(":", $key);
|
||||
}
|
||||
|
||||
// Check for explode parameter.
|
||||
if ($key[strlen($key) - 1] == "*") {
|
||||
$explode = true;
|
||||
$key = substr($key, 0, -1);
|
||||
$skip_final_combine = true;
|
||||
}
|
||||
|
||||
// Define the list separator.
|
||||
$list_sep = $explode ? $sep : ",";
|
||||
|
||||
if (isset($parameters[$key])) {
|
||||
$data_type = $this->getDataType($parameters[$key]);
|
||||
switch($data_type) {
|
||||
case self::TYPE_SCALAR:
|
||||
$value = $this->getValue($parameters[$key], $length);
|
||||
break;
|
||||
case self::TYPE_LIST:
|
||||
$values = array();
|
||||
foreach ($parameters[$key] as $pkey => $pvalue) {
|
||||
$pvalue = $this->getValue($pvalue, $length);
|
||||
if ($combine && $explode) {
|
||||
$values[$pkey] = $key . $combine . $pvalue;
|
||||
} else {
|
||||
$values[$pkey] = $pvalue;
|
||||
}
|
||||
}
|
||||
$value = implode($list_sep, $values);
|
||||
if ($value == '') {
|
||||
return '';
|
||||
}
|
||||
break;
|
||||
case self::TYPE_MAP:
|
||||
$values = array();
|
||||
foreach ($parameters[$key] as $pkey => $pvalue) {
|
||||
$pvalue = $this->getValue($pvalue, $length);
|
||||
if ($explode) {
|
||||
$pkey = $this->getValue($pkey, $length);
|
||||
$values[] = $pkey . "=" . $pvalue; // Explode triggers = combine.
|
||||
} else {
|
||||
$values[] = $pkey;
|
||||
$values[] = $pvalue;
|
||||
}
|
||||
}
|
||||
$value = implode($list_sep, $values);
|
||||
if ($value == '') {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if ($tag_empty) {
|
||||
// If we are just indicating empty values with their key name, return that.
|
||||
return $key;
|
||||
} else {
|
||||
// Otherwise we can skip this variable due to not being defined.
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($reserved) {
|
||||
$value = str_replace($this->reservedEncoded, $this->reserved, $value);
|
||||
}
|
||||
|
||||
// If we do not need to include the key name, we just return the raw
|
||||
// value.
|
||||
if (!$combine || $skip_final_combine) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
// Else we combine the key name: foo=bar, if value is not the empty string.
|
||||
return $key . ($value != '' || $combine_on_empty ? $combine . $value : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type of a passed in value
|
||||
*/
|
||||
private function getDataType($data)
|
||||
{
|
||||
if (is_array($data)) {
|
||||
reset($data);
|
||||
if (key($data) !== 0) {
|
||||
return self::TYPE_MAP;
|
||||
}
|
||||
return self::TYPE_LIST;
|
||||
}
|
||||
return self::TYPE_SCALAR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function that merges multiple combine calls
|
||||
* for multi-key templates.
|
||||
*/
|
||||
private function combineList(
|
||||
$vars,
|
||||
$sep,
|
||||
$parameters,
|
||||
$combine,
|
||||
$reserved,
|
||||
$tag_empty,
|
||||
$combine_on_empty
|
||||
) {
|
||||
$ret = array();
|
||||
foreach ($vars as $var) {
|
||||
$response = $this->combine(
|
||||
$var,
|
||||
$parameters,
|
||||
$sep,
|
||||
$combine,
|
||||
$reserved,
|
||||
$tag_empty,
|
||||
$combine_on_empty
|
||||
);
|
||||
if ($response === false) {
|
||||
continue;
|
||||
}
|
||||
$ret[] = $response;
|
||||
}
|
||||
return implode($sep, $ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to encode and trim values
|
||||
*/
|
||||
private function getValue($value, $length)
|
||||
{
|
||||
if ($length) {
|
||||
$value = substr($value, 0, $length);
|
||||
}
|
||||
$value = rawurlencode($value);
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Verifies signatures.
|
||||
*
|
||||
* @author Brian Eaton <beaton@google.com>
|
||||
*/
|
||||
abstract class Google_Verifier_Abstract
|
||||
{
|
||||
/**
|
||||
* Checks a signature, returns true if the signature is correct,
|
||||
* false otherwise.
|
||||
*/
|
||||
abstract public function verify($data, $signature);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
if (!class_exists('UDP_Google_Client')) {
|
||||
require_once dirname(__FILE__) . '/../autoload.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies signatures using PEM encoded certificates.
|
||||
*
|
||||
* @author Brian Eaton <beaton@google.com>
|
||||
*/
|
||||
class Google_Verifier_Pem extends Google_Verifier_Abstract
|
||||
{
|
||||
private $publicKey;
|
||||
|
||||
/**
|
||||
* Constructs a verifier from the supplied PEM-encoded certificate.
|
||||
*
|
||||
* $pem: a PEM encoded certificate (not a file).
|
||||
* @param $pem
|
||||
* @throws Google_Auth_Exception
|
||||
* @throws Google_Exception
|
||||
*/
|
||||
public function __construct($pem)
|
||||
{
|
||||
if (!function_exists('openssl_x509_read')) {
|
||||
throw new Google_Exception('Google API PHP client needs the openssl PHP extension');
|
||||
}
|
||||
$this->publicKey = openssl_x509_read($pem);
|
||||
if (!$this->publicKey) {
|
||||
throw new Google_Auth_Exception("Unable to parse PEM: $pem"); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->publicKey) {
|
||||
openssl_x509_free($this->publicKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the signature on data.
|
||||
*
|
||||
* Returns true if the signature is valid, false otherwise.
|
||||
* @param $data
|
||||
* @param $signature
|
||||
* @throws Google_Auth_Exception
|
||||
* @return bool
|
||||
*/
|
||||
public function verify($data, $signature)
|
||||
{
|
||||
// @codingStandardsIgnoreLine
|
||||
$hash = defined("OPENSSL_ALGO_SHA256") ? OPENSSL_ALGO_SHA256 : "sha256";
|
||||
$status = openssl_verify($data, $signature, $this->publicKey, $hash);
|
||||
if ($status === -1) {
|
||||
throw new Google_Auth_Exception('Signature verification error: ' . openssl_error_string()); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
|
||||
}
|
||||
return $status === 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2014 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
function google_api_php_client_autoload_updraftplus($className) {
|
||||
|
||||
$className = str_replace('UDP_', '', $className);
|
||||
$classPath = explode('_', $className);
|
||||
if ($classPath[0] != 'Google') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Drop 'Google', and maximum class file path depth in this project is 3.
|
||||
$classPath = array_slice($classPath, 1, 2);
|
||||
|
||||
$filePath = dirname(__FILE__) . '/' . implode('/', $classPath) . '.php';
|
||||
if (file_exists($filePath)) {
|
||||
require_once($filePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
|
||||
// Use the 'prepend' parameter; if other tools have registered autoloaders for incompatible versions of the Google SDK, ours should still get loaded first (since we only register our autoloader late, immediately before using it).
|
||||
spl_autoload_register('google_api_php_client_autoload_updraftplus', true, true);
|
||||
} else {
|
||||
spl_autoload_register('google_api_php_client_autoload_updraftplus');
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,959 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreStart
|
||||
// This is a compatibility library, using Amazon's official PHP SDK (PHP 5.3.3+), but providing the methods of Donovan Schönknecht's S3.php library (which we used to always use) - but we've only cared about making code-paths in UpdraftPlus work, so be careful if re-deploying this in another project. And, we have a few bits of UpdraftPlus-specific code below, for logging.
|
||||
|
||||
/**
|
||||
*
|
||||
* Copyright (c) 2012-22, David Anderson (https://www.simbahosting.co.uk). All rights reserved.
|
||||
* Portions copyright (c) 2011, Donovan Schönknecht. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* Amazon S3 is a trademark of Amazon.com, Inc. or its affiliates.
|
||||
*/
|
||||
// @codingStandardsIgnoreEnd
|
||||
|
||||
// SDK requires PHP 5.5+
|
||||
use Aws\Common\RulesEndpointProvider;
|
||||
use Aws\Credentials\Credentials;
|
||||
use Aws\S3\Exception\NoSuchBucketException;
|
||||
use Aws\S3\S3MultiRegionClient;
|
||||
|
||||
global $updraftplus;
|
||||
$updraftplus->potentially_remove_composer_autoloaders(array('GuzzleHttp\\', 'Aws\\'));
|
||||
updraft_try_include_file('vendor/autoload.php', 'include');
|
||||
$updraftplus->mitigate_guzzle_autoloader_conflicts();
|
||||
|
||||
/**
|
||||
* Amazon S3 PHP class
|
||||
* http://undesigned.org.za/2007/10/22/amazon-s3-php-cla
|
||||
*
|
||||
* @version Release: 0.5.0-dev
|
||||
*/
|
||||
class UpdraftPlus_S3_Compat {
|
||||
|
||||
// ACL flags
|
||||
const ACL_PRIVATE = 'private';
|
||||
const ACL_PUBLIC_READ = 'public-read';
|
||||
const ACL_PUBLIC_READ_WRITE = 'public-read-write';
|
||||
const ACL_AUTHENTICATED_READ = 'authenticated-read';
|
||||
|
||||
const STORAGE_CLASS_STANDARD = 'STANDARD';
|
||||
|
||||
// AWS S3 client
|
||||
private $client;
|
||||
|
||||
private $config;
|
||||
|
||||
private $__access_key = null; // AWS Access key
|
||||
|
||||
private $__secret_key = null; // AWS Secret key
|
||||
|
||||
private $__session_token = null;
|
||||
|
||||
private $__ssl_key = null;
|
||||
|
||||
public $endpoint = 's3.amazonaws.com';
|
||||
|
||||
public $proxy = null;
|
||||
|
||||
private $region = 'us-east-1';
|
||||
|
||||
// Added to cope with a particular situation where the user had no pernmission to check the bucket location, which necessitated using DNS-based endpoints.
|
||||
public $use_dns_bucket_name = false;
|
||||
|
||||
public $use_ssl = false;
|
||||
|
||||
public $use_ssl_validation = true;
|
||||
|
||||
public $useExceptions = false;
|
||||
|
||||
private $_serverSideEncryption = null;
|
||||
|
||||
// SSL CURL SSL options - only needed if you are experiencing problems with your OpenSSL configuration
|
||||
public $ssl_key = null;
|
||||
|
||||
public $ssl_cert = null;
|
||||
|
||||
public $ssl_ca_cert = null;
|
||||
|
||||
public $iam = null;
|
||||
|
||||
// Added at request of a user using a non-default port.
|
||||
public static $port = false;
|
||||
|
||||
/**
|
||||
* Constructor - if you're not using the class statically
|
||||
*
|
||||
* @param string $access_key Access key
|
||||
* @param string $secret_key Secret key
|
||||
* @param boolean $use_ssl Enable SSL
|
||||
* @param string|boolean $ssl_ca_cert Certificate authority (true = bundled Guzzle version; false = no verify, 'system' = system version; otherwise, path)
|
||||
* @param Null|String $endpoint Endpoint (if omitted, it will be set by the SDK using the region)
|
||||
* @param Null|String $session_token The session token returned by AWS for temporary credentials access
|
||||
* @param Null|String $region Region. Currently unused, but harmonised with UpdraftPlus_S3 class
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($access_key = null, $secret_key = null, $use_ssl = true, $ssl_ca_cert = true, $endpoint = null, $session_token = null, $region = null) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $region is unused
|
||||
do_action('updraftplus_load_aws_sdk');
|
||||
|
||||
global $updraftplus;
|
||||
$updraftplus->mitigate_guzzle_autoloader_conflicts();
|
||||
|
||||
if (null !== $access_key && null !== $secret_key)
|
||||
$this->setAuth($access_key, $secret_key, $session_token);
|
||||
|
||||
$this->use_ssl = $use_ssl;
|
||||
$this->ssl_ca_cert = $ssl_ca_cert;
|
||||
|
||||
if ($session_token) {
|
||||
$credentials = new Credentials($access_key, $secret_key, $session_token);
|
||||
} else {
|
||||
$credentials = new Credentials($access_key, $secret_key);
|
||||
}
|
||||
|
||||
global $updraftplus;
|
||||
|
||||
// AWS SDK V3 requires we specify a version. String 'latest' can be used but not recommended, a full list of versions for each API found here: https://docs.aws.amazon.com/aws-sdk-php/v3/api/index.html
|
||||
// latest S3Client version as of 16/09/21 is version 2006-03-01
|
||||
$this->config = array(
|
||||
'credentials' => $credentials,
|
||||
'version' => '2006-03-01',
|
||||
'scheme' => ($use_ssl) ? 'https' : 'http',
|
||||
'ua_append' => 'UpdraftPlus/'.$updraftplus->version,
|
||||
// Using signature v4 requires a region (but see the note below)
|
||||
// 'signature_version' => 'v4',
|
||||
// 'region' => $this->region
|
||||
// 'endpoint' => 'somethingorother.s3.amazonaws.com'
|
||||
);
|
||||
|
||||
if ($endpoint) {
|
||||
// Can't specify signature v4, as that requires stating the region - which we don't necessarily yet know.
|
||||
// Later comment: however, it looks to me like in current UD (Sep 2017), $endpoint is never used for Amazon S3/Vault, and there may be cases (e.g. DigitalOcean Spaces) where we might prefer v4 (DO support v2 too, currently) without knowing a region.
|
||||
$this->endpoint = $endpoint;
|
||||
$this->config['endpoint'] = $endpoint;
|
||||
} else {
|
||||
// Using signature v4 requires a region. Also, some regions (EU Central 1, China) require signature v4 - and all support it, so we may as well use it if we can.
|
||||
$this->config['signature_version'] = 'v4';
|
||||
$this->config['region'] = $this->region;
|
||||
}
|
||||
|
||||
if ($use_ssl) $this->config['http']['verify'] = $ssl_ca_cert;
|
||||
|
||||
$this->client = new S3MultiRegionClient($this->config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set AWS access key and secret key
|
||||
*
|
||||
* @param string $access_key Access key
|
||||
* @param string $secret_key Secret key
|
||||
* @param null|string $session_token The session token returned by AWS for temporary credentials access
|
||||
* @return void
|
||||
*/
|
||||
public function setAuth($access_key, $secret_key, $session_token = null) {
|
||||
$this->__access_key = $access_key;
|
||||
$this->__secret_key = $secret_key;
|
||||
$this->__session_token = $session_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Example value: 'AES256'. See: https://docs.aws.amazon.com/AmazonS3/latest/dev/SSEUsingPHPSDK.html
|
||||
* Or, false to turn off.
|
||||
*
|
||||
* @param boolean $value Set if Value
|
||||
*/
|
||||
public function setServerSideEncryption($value) {
|
||||
$this->_serverSideEncryption = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the service region
|
||||
*
|
||||
* @param string $region Region
|
||||
* @return void
|
||||
*/
|
||||
public function setRegion($region) {
|
||||
$this->region = $region;
|
||||
if ('eu-central-1' == $region || 'cn-north-1' == $region) {
|
||||
// $this->config['signature'] = new Aws\S3\S3SignatureV4('s3');
|
||||
// $this->client->setConfig($this->config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the service endpoint
|
||||
*
|
||||
* @param string $host Hostname
|
||||
* @param string $region Region
|
||||
* @return void
|
||||
*/
|
||||
public function setEndpoint($host, $region) {
|
||||
$this->endpoint = $host;
|
||||
$this->region = $region;
|
||||
$this->config['endpoint_provider'] = $this->return_provider();
|
||||
$this->client->setConfig($this->config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the service port
|
||||
*
|
||||
* @param Integer $port Port number
|
||||
*/
|
||||
public function setPort($port) {
|
||||
// Not used with AWS (which is the only thing using this class)
|
||||
self::$port = $port;
|
||||
}
|
||||
|
||||
public function return_provider() {
|
||||
$our_endpoints = array(
|
||||
'endpoint' => $this->endpoint
|
||||
);
|
||||
if ('eu-central-1' == $this->region || 'cn-north-1' == $this->region) $our_endpoints['signatureVersion'] = 'v4';
|
||||
$endpoints = array(
|
||||
'version' => 2,
|
||||
'endpoints' => array(
|
||||
"*/s3" => $our_endpoints
|
||||
)
|
||||
);
|
||||
return new RulesEndpointProvider($endpoints);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SSL on or off
|
||||
* This code relies upon the particular pattern of SSL options-setting in s3.php in UpdraftPlus
|
||||
*
|
||||
* @param boolean $enabled SSL enabled
|
||||
* @param boolean $validate SSL certificate validation
|
||||
* @return void
|
||||
*/
|
||||
public function setSSL($enabled, $validate = true) {
|
||||
$this->use_ssl = $enabled;
|
||||
$this->use_ssl_validation = $validate;
|
||||
|
||||
$scheme = ($enabled) ? 'https' : 'http';
|
||||
|
||||
// The AWS SDK V3 client doesn't have the 'setConfig' method, so we must recreate the client instance to update the configs. However, we only do it when the '$config' variable is changed.
|
||||
if ($this->config['scheme'] != $scheme) {
|
||||
$this->config['scheme'] = $scheme;
|
||||
$this->client = new S3MultiRegionClient($this->config);
|
||||
}
|
||||
}
|
||||
|
||||
public function getuseSSL() {
|
||||
return $this->use_ssl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SSL validation value.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getUseSSLValidation() {
|
||||
return $this->use_ssl_validation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SSL client certificates (experimental)
|
||||
*
|
||||
* @param string $ssl_cert SSL client certificate
|
||||
* @param string $ssl_key SSL client key
|
||||
* @param string $ssl_ca_cert SSL CA cert (only required if you are having problems with your system CA cert)
|
||||
* @return void
|
||||
*/
|
||||
public function setSSLAuth($ssl_cert = null, $ssl_key = null, $ssl_ca_cert = null) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Unused parameters are for future use.
|
||||
|
||||
if (!$this->use_ssl) return;
|
||||
|
||||
if (!$this->use_ssl_validation || !$ssl_ca_cert) {
|
||||
$verify = false;
|
||||
} else {
|
||||
$verify = ('system' === $ssl_ca_cert) ? true : realpath($ssl_ca_cert);
|
||||
}
|
||||
|
||||
// The AWS SDK V3 client doesn't have the 'setConfig' method, so we must recreate the client instance to update the configs. However, we only do it when the '$config' variable is changed.
|
||||
if ($this->config['http']['verify'] != $verify) {
|
||||
$this->config['http']['verify'] = $verify;
|
||||
$this->client = new S3MultiRegionClient($this->config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set proxy information
|
||||
*
|
||||
* @param string $host Proxy hostname and port (localhost:1234)
|
||||
* @param string $user Proxy username
|
||||
* @param string $pass Proxy password
|
||||
* @param constant $type CURL proxy type
|
||||
* @param integer $port Port number
|
||||
* @return void
|
||||
*/
|
||||
public function setProxy($host, $user = null, $pass = null, $type = CURLPROXY_SOCKS5, $port = null) {
|
||||
|
||||
$this->proxy = array('host' => $host, 'type' => $type, 'user' => $user, 'pass' => $pass, 'port' => $port);
|
||||
|
||||
if (!$host) return;
|
||||
|
||||
$wp_proxy = new WP_HTTP_Proxy();
|
||||
if ($wp_proxy->send_through_proxy('https://s3.amazonaws.com')) {
|
||||
|
||||
global $updraftplus;
|
||||
$updraftplus->log("setProxy: host=$host, user=$user, port=$port");
|
||||
|
||||
// N.B. Currently (02-Feb-15), only support for HTTP proxies has ever been requested for S3 in UpdraftPlus
|
||||
$proxy_url = 'http://';
|
||||
if ($user) {
|
||||
$proxy_url .= $user;
|
||||
if ($pass) $proxy_url .= ":$pass";
|
||||
$proxy_url .= "@";
|
||||
}
|
||||
|
||||
$proxy_url .= $host;
|
||||
|
||||
if ($port) $proxy_url .= ":$port";
|
||||
|
||||
$this->client->setDefaultOption('proxy', $proxy_url);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the error mode to exceptions
|
||||
*
|
||||
* @param boolean $enabled Enable exceptions
|
||||
* @return void
|
||||
*/
|
||||
public function setExceptions($enabled = true) {
|
||||
$this->useExceptions = $enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* A no-op in this compatibility layer (for now - not yet found a use)...
|
||||
*
|
||||
* @param boolean $use Bucket use
|
||||
* @param string $bucket Bucket name
|
||||
* @return boolean
|
||||
*/
|
||||
public function useDNSBucketName($use = true, $bucket = '') {
|
||||
$this->use_dns_bucket_name = $use;
|
||||
if ($use && $bucket) {
|
||||
$this->setEndpoint($bucket.'.s3.amazonaws.com', $this->region);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get contents for a bucket
|
||||
* If max_keys is null this method will loop through truncated result sets
|
||||
* N.B. UpdraftPlus does not use the $delimiter or $return_common_prefixes parameters (nor set $prefix or $marker to anything other than null)
|
||||
* $return_common_prefixes is not implemented below
|
||||
*
|
||||
* @param string $bucket Bucket name
|
||||
* @param string $prefix Prefix
|
||||
* @param string $marker Marker (last file listed)
|
||||
* @param string $max_keys Max keys (maximum number of keys to return)
|
||||
* @param string $delimiter Delimiter
|
||||
* @param boolean $return_common_prefixes Set to true to return CommonPrefixes
|
||||
* @return array
|
||||
*/
|
||||
public function getBucket($bucket, $prefix = null, $marker = null, $max_keys = null, $delimiter = null, $return_common_prefixes = false) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $return_common_prefixes is unused (commented out) so kept in just incase it is reused again
|
||||
try {
|
||||
if (0 == $max_keys) $max_keys = null;
|
||||
|
||||
$vars = array(
|
||||
'Bucket' => $bucket,
|
||||
'@region' => $this->region
|
||||
);
|
||||
if (null !== $prefix && '' !== $prefix) $vars['Prefix'] = $prefix;
|
||||
if (null !== $marker && '' !== $marker) $vars['Marker'] = $marker;
|
||||
if (null !== $max_keys && '' !== $max_keys) $vars['MaxKeys'] = $max_keys;
|
||||
if (null !== $delimiter && '' !== $delimiter) $vars['Delimiter'] = $delimiter;
|
||||
$result = $this->client->listObjects($vars);
|
||||
|
||||
if (!is_a($result, 'Aws\Result')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$results = array();
|
||||
$next_marker = null;
|
||||
// http://docs.aws.amazon.com/AmazonS3/latest/dev/ListingObjectKeysUsingPHP.html
|
||||
// UpdraftPlus does not use the 'hash' result
|
||||
if (empty($result['Contents'])) $result['Contents'] = array();
|
||||
foreach ($result['Contents'] as $c) {
|
||||
$results[(string) $c['Key']] = array(
|
||||
'name' => (string) $c['Key'],
|
||||
'time' => strtotime((string) $c['LastModified']),
|
||||
'size' => (int) $c['Size'],
|
||||
// 'hash' => trim((string)$c['ETag'])
|
||||
// 'hash' => substr((string)$c['ETag'], 1, -1)
|
||||
);
|
||||
$next_marker = (string) $c['Key'];
|
||||
}
|
||||
|
||||
if (isset($result['IsTruncated']) && empty($result['IsTruncated'])) return $results;
|
||||
|
||||
if (isset($result['NextMarker'])) $next_marker = (string) $result['NextMarker'];
|
||||
|
||||
// Loop through truncated results if max_keys isn't specified
|
||||
if (null == $max_keys && null !== $next_marker && !empty($result['IsTruncated']))
|
||||
do {
|
||||
$vars['Marker'] = $next_marker;
|
||||
$result = $this->client->listObjects($vars);
|
||||
|
||||
if (!is_a($result, 'Aws\Result') || empty($result['Contents'])) break;
|
||||
|
||||
foreach ($result['Contents'] as $c) {
|
||||
$results[(string) $c['Key']] = array(
|
||||
'name' => (string) $c['Key'],
|
||||
'time' => strtotime((string) $c['LastModified']),
|
||||
'size' => (int) $c['Size'],
|
||||
// 'hash' => trim((string)$c['ETag'])
|
||||
// 'hash' => substr((string)$c['ETag'], 1, -1)
|
||||
);
|
||||
$next_marker = (string) $c['Key'];
|
||||
}
|
||||
|
||||
// if ($return_common_prefixes && isset($response->body, $response->body->CommonPrefixes))
|
||||
// foreach ($response->body->CommonPrefixes as $c)
|
||||
// $results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix);
|
||||
|
||||
if (isset($result['NextMarker']))
|
||||
$next_marker = (string) $result['NextMarker'];
|
||||
|
||||
} while (is_a($result, 'Aws\Result') && !empty($result['Contents']) && !empty($result['IsTruncated']));
|
||||
|
||||
return $results;
|
||||
|
||||
} catch (Exception $e) {
|
||||
if ($this->useExceptions) {
|
||||
throw $e;
|
||||
} else {
|
||||
return $this->trigger_from_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is crude - nothing is returned
|
||||
*
|
||||
* @param string $bucket Name of the Bucket
|
||||
* @return array Returns an array of results if bucket exists
|
||||
*/
|
||||
public function waitForBucket($bucket) {
|
||||
try {
|
||||
$this->client->waitUntil('BucketExists', array(
|
||||
'Bucket' => $bucket,
|
||||
'@region' => $this->region
|
||||
));
|
||||
} catch (Exception $e) {
|
||||
if ($this->useExceptions) {
|
||||
throw $e;
|
||||
} else {
|
||||
return $this->trigger_from_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a bucket
|
||||
*
|
||||
* @param string $bucket Bucket name
|
||||
* @param constant $acl ACL flag
|
||||
* @param string $location Set as "EU" to create buckets hosted in Europe
|
||||
* @return boolean Returns true or false; or may throw an exception
|
||||
*/
|
||||
public function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = false) {
|
||||
if (!$location) {
|
||||
$location = $this->region;
|
||||
} else {
|
||||
$this->setRegion($location);
|
||||
}
|
||||
$bucket_vars = array(
|
||||
'Bucket' => $bucket,
|
||||
'ACL' => $acl,
|
||||
'@region' => $this->region
|
||||
);
|
||||
// http://docs.aws.amazon.com/aws-sdk-php/latest/class-Aws.S3.S3Client.html#_createBucket
|
||||
$location_constraint = apply_filters('updraftplus_s3_putbucket_defaultlocation', $location);
|
||||
if ('us-east-1' != $location_constraint) $bucket_vars['LocationConstraint'] = $location_constraint;
|
||||
try {
|
||||
$result = $this->client->createBucket($bucket_vars);
|
||||
if (is_object($result) && method_exists($result, 'get')) {
|
||||
$metadata = $result->get('@metadata');
|
||||
if (!empty($metadata) && in_array($metadata['statusCode'], array(200, 204))) {
|
||||
$this->client->waitUntil('BucketExists', array(
|
||||
'Bucket' => $bucket,
|
||||
'@region' => $this->region
|
||||
));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
if ($this->useExceptions) {
|
||||
throw $e;
|
||||
} else {
|
||||
return $this->trigger_from_exception($e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate a multi-part upload (http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadInitiate.html)
|
||||
*
|
||||
* @param string $bucket Bucket name
|
||||
* @param string $uri Object URI
|
||||
* @param constant $acl ACL constant
|
||||
* @param array $meta_headers Array of x-amz-meta-* headers
|
||||
* @param array $request_headers Array of request headers or content type as a string
|
||||
* @param constant $storage_class Storage class constant
|
||||
* @return string | false
|
||||
*/
|
||||
public function initiateMultipartUpload($bucket, $uri, $acl = self::ACL_PRIVATE, $meta_headers = array(), $request_headers = array(), $storage_class = self::STORAGE_CLASS_STANDARD) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Unused parameter is present because the caller from UpdraftPlus_BackupModule_s3 class uses 6 arguments.
|
||||
$vars = array(
|
||||
'ACL' => $acl,
|
||||
'Bucket' => $bucket,
|
||||
'Key' => $uri,
|
||||
'Metadata' => $meta_headers,
|
||||
'StorageClass' => $storage_class,
|
||||
'@region' => $this->region
|
||||
);
|
||||
|
||||
$vars['ContentType'] = ('.gz' == strtolower(substr($uri, -3, 3))) ? 'application/octet-stream' : 'application/zip';
|
||||
|
||||
if (!empty($this->_serverSideEncryption)) $vars['ServerSideEncryption'] = $this->_serverSideEncryption;
|
||||
|
||||
try {
|
||||
$result = $this->client->createMultipartUpload($vars);
|
||||
if (is_object($result) && method_exists($result, 'get') && '' != $result->get('UploadId')) return $result->get('UploadId');
|
||||
} catch (Exception $e) {
|
||||
if ($this->useExceptions) {
|
||||
throw $e;
|
||||
} else {
|
||||
return $this->trigger_from_exception($e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a part of a multi-part set (http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadUploadPart.html)
|
||||
* The chunk is read into memory, so make sure that you have enough (or patch this function to work another way!)
|
||||
*
|
||||
* @param string $bucket Bucket name
|
||||
* @param string $uri Object URI
|
||||
* @param string $upload_id upload_id returned previously from initiateMultipartUpload
|
||||
* @param string $file_path file to upload content from
|
||||
* @param integer $part_number sequential part number to upload
|
||||
* @param integer $part_size number of bytes in each part (though final part may have fewer) - pass the same value each time (for this particular upload) - default 5Mb (which is Amazon's minimum)
|
||||
* @return string (ETag) | false]
|
||||
*/
|
||||
public function uploadPart($bucket, $uri, $upload_id, $file_path, $part_number, $part_size = 5242880) {
|
||||
$vars = array(
|
||||
'Bucket' => $bucket,
|
||||
'Key' => $uri,
|
||||
'PartNumber' => $part_number,
|
||||
'UploadId' => $upload_id,
|
||||
'@region' => $this->region
|
||||
);
|
||||
|
||||
// Where to begin
|
||||
$file_offset = ($part_number - 1) * $part_size;
|
||||
|
||||
// Download the smallest of the remaining bytes and the part size
|
||||
$file_bytes = min(filesize($file_path) - $file_offset, $part_size);
|
||||
if ($file_bytes < 0) $file_bytes = 0;
|
||||
|
||||
// $rest->setHeader('Content-Type', 'application/octet-stream');
|
||||
$data = "";
|
||||
|
||||
if ($handle = fopen($file_path, "rb")) {
|
||||
if ($file_offset > 0) fseek($handle, $file_offset);
|
||||
// $bytes_read = 0;
|
||||
while ($file_bytes > 0 && $read = fread($handle, max($file_bytes, 131072))) {
|
||||
$file_bytes = $file_bytes - strlen($read);
|
||||
// $bytes_read += strlen($read);
|
||||
$data .= $read;
|
||||
}
|
||||
fclose($handle);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
$vars['Body'] = $data;
|
||||
|
||||
try {
|
||||
$result = $this->client->uploadPart($vars);
|
||||
if (is_object($result) && method_exists($result, 'get') && '' != $result->get('ETag')) return $result->get('ETag');
|
||||
} catch (Exception $e) {
|
||||
if ($this->useExceptions) {
|
||||
throw $e;
|
||||
} else {
|
||||
return $this->trigger_from_exception($e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete a multi-part upload (http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadComplete.html)
|
||||
*
|
||||
* @param string $bucket Bucket name
|
||||
* @param string $uri Object URI
|
||||
* @param string $upload_id upload_id returned previously from initiateMultipartUpload
|
||||
* @param array $parts an ordered list of eTags of previously uploaded parts from uploadPart
|
||||
* @return boolean Returns either true of false
|
||||
*/
|
||||
public function completeMultipartUpload($bucket, $uri, $upload_id, $parts) {
|
||||
$vars = array(
|
||||
'Bucket' => $bucket,
|
||||
'Key' => $uri,
|
||||
'UploadId' => $upload_id,
|
||||
'@region' => $this->region
|
||||
);
|
||||
|
||||
$partno = 1;
|
||||
$send_parts = array();
|
||||
foreach ($parts as $etag) {
|
||||
$send_parts[] = array('ETag' => $etag, 'PartNumber' => $partno);
|
||||
$partno++;
|
||||
}
|
||||
|
||||
$vars['MultipartUpload'] = array('Parts' => $send_parts);
|
||||
|
||||
try {
|
||||
$result = $this->client->completeMultipartUpload($vars);
|
||||
if (is_object($result) && method_exists($result, 'get') && '' != $result->get('ETag')) return true;
|
||||
} catch (Exception $e) {
|
||||
if ($this->useExceptions) {
|
||||
throw $e;
|
||||
} else {
|
||||
return $this->trigger_from_exception($e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put an object from a file (legacy function)
|
||||
*
|
||||
* @param string $file Input file path
|
||||
* @param string $bucket Bucket name
|
||||
* @param string $uri Object URI
|
||||
* @param constant $acl ACL constant
|
||||
* @param array $meta_headers Array of x-amz-meta-* headers
|
||||
* @param string $content_type Content type
|
||||
* @param string $storage_class STORAGE_CLASS_STANDARD constant
|
||||
* @return boolean returns either true of false
|
||||
*/
|
||||
public function putObjectFile($file, $bucket, $uri, $acl = self::ACL_PRIVATE, $meta_headers = array(), $content_type = null, $storage_class = self::STORAGE_CLASS_STANDARD) {
|
||||
try {
|
||||
$options = array(
|
||||
'Bucket' => $bucket,
|
||||
'Key' => $uri,
|
||||
'SourceFile' => $file,
|
||||
'StorageClass' => $storage_class,
|
||||
'ACL' => $acl,
|
||||
'@region' => $this->region
|
||||
);
|
||||
if ($content_type) $options['ContentType'] = $content_type;
|
||||
if (!empty($this->_serverSideEncryption)) $options['ServerSideEncryption'] = $this->_serverSideEncryption;
|
||||
if (!empty($meta_headers)) $options['Metadata'] = $meta_headers;
|
||||
$result = $this->client->putObject($options);
|
||||
if (is_object($result) && method_exists($result, 'get')) {
|
||||
$metadata = $result->get('@metadata');
|
||||
if (!empty($metadata) && in_array($metadata['statusCode'], array(200, 204))) return true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
if ($this->useExceptions) {
|
||||
throw $e;
|
||||
} else {
|
||||
return $this->trigger_from_exception($e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Put an object from a string (legacy function)
|
||||
* Only the first 3 parameters vary in UpdraftPlus
|
||||
*
|
||||
* @param string $string Input data
|
||||
* @param string $bucket Bucket name
|
||||
* @param string $uri Object URI
|
||||
* @param constant $acl ACL constant
|
||||
* @param array $meta_headers Array of x-amz-meta-* headers
|
||||
* @param string $content_type Content type
|
||||
* @return boolean returns either true of false
|
||||
*/
|
||||
public function putObjectString($string, $bucket, $uri, $acl = self::ACL_PRIVATE, $meta_headers = array(), $content_type = 'text/plain') { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- legacy function
|
||||
try {
|
||||
$result = $this->client->putObject(array(
|
||||
'Bucket' => $bucket,
|
||||
'Key' => $uri,
|
||||
'Body' => $string,
|
||||
'ContentType' => $content_type,
|
||||
'@region' => $this->region
|
||||
));
|
||||
if (is_object($result) && method_exists($result, 'get')) {
|
||||
$metadata = $result->get('@metadata');
|
||||
if (!empty($metadata) && in_array($metadata['statusCode'], array(200, 204))) return true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
if ($this->useExceptions) {
|
||||
throw $e;
|
||||
} else {
|
||||
return $this->trigger_from_exception($e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get an object
|
||||
*
|
||||
* @param string $bucket Bucket name
|
||||
* @param string $uri Object URI
|
||||
* @param mixed $save_to Filename or resource to write to
|
||||
* @param mixed $resume - if $save_to is a resource, then this is either false or the value for a Range: header; otherwise, a boolean, indicating whether to resume if possible.
|
||||
* @return mixed
|
||||
*/
|
||||
public function getObject($bucket, $uri, $save_to = false, $resume = false) {
|
||||
try {
|
||||
// SaveAs: "Specify where the contents of the object should be downloaded. Can be the path to a file, a resource returned by fopen, or a Guzzle\Http\EntityBodyInterface object." - http://docs.aws.amazon.com/aws-sdk-php/latest/class-Aws.S3.S3Client.html#_getObject
|
||||
|
||||
$range_header = false;
|
||||
if (is_resource($save_to)) {
|
||||
$fp = $save_to;
|
||||
if (!is_bool($resume)) $range_header = $resume;
|
||||
} elseif (file_exists($save_to)) {
|
||||
if ($resume && ($fp = @fopen($save_to, 'ab')) !== false) {// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
|
||||
$range_header = "bytes=".filesize($save_to).'-';
|
||||
} else {
|
||||
throw new Exception('Unable to open save file for writing: '.$save_to);
|
||||
}
|
||||
} else {
|
||||
if (($fp = @fopen($save_to, 'wb')) !== false) {// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
|
||||
$range_header = false;
|
||||
} else {
|
||||
throw new Exception('Unable to open save file for writing: '.$save_to);
|
||||
}
|
||||
}
|
||||
|
||||
$vars = array(
|
||||
'Bucket' => $bucket,
|
||||
'Key' => $uri,
|
||||
'SaveAs' => $fp,
|
||||
'@region' => $this->region
|
||||
);
|
||||
if (!empty($range_header)) $vars['Range'] = $range_header;
|
||||
|
||||
$result = $this->client->getObject($vars);
|
||||
if (is_object($result) && method_exists($result, 'get')) {
|
||||
$metadata = $result->get('@metadata');
|
||||
if (!empty($metadata) && in_array($metadata['statusCode'], array(200, 204, 206))) return true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
if ($this->useExceptions) {
|
||||
throw $e;
|
||||
} else {
|
||||
return $this->trigger_from_exception($e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a bucket's location
|
||||
*
|
||||
* @param string $bucket Bucket name
|
||||
*
|
||||
* @return String | Boolean - A boolean result will be false, indicating failure.
|
||||
*/
|
||||
public function getBucketLocation($bucket) {
|
||||
try {
|
||||
$result = $this->client->getBucketLocation(array(
|
||||
'Bucket' => $bucket,
|
||||
'@region' => $this->region
|
||||
));
|
||||
$location = $result->get('LocationConstraint');
|
||||
if ($location) return $location;
|
||||
} catch (NoSuchBucketException $e) {
|
||||
return false;
|
||||
} catch (Exception $e) {
|
||||
if ($this->useExceptions) {
|
||||
throw $e;
|
||||
} else {
|
||||
return $this->trigger_from_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function trigger_from_exception($e) {
|
||||
trigger_error(esc_html($e->getMessage().' ('.get_class($e).') (line: '.$e->getLine().', file: '.$e->getFile().')'), E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an object
|
||||
*
|
||||
* @param string $bucket Bucket name
|
||||
* @param string $uri Object URI
|
||||
* @return boolean
|
||||
*/
|
||||
public function deleteObject($bucket, $uri) {
|
||||
try {
|
||||
$result = $this->client->deleteObject(array(
|
||||
'Bucket' => $bucket,
|
||||
'Key' => $uri,
|
||||
'@region' => $this->region
|
||||
));
|
||||
if (is_object($result) && method_exists($result, 'get')) {
|
||||
$metadata = $result->get('@metadata');
|
||||
if (!empty($metadata) && in_array($metadata['statusCode'], array(200, 204))) return true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
if ($this->useExceptions) {
|
||||
throw $e;
|
||||
} else {
|
||||
return $this->trigger_from_exception($e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function setCORS($policy) {
|
||||
try {
|
||||
$cors = $this->client->putBucketCors($policy);
|
||||
if (is_object($cors) && method_exists($cors, 'get')) {
|
||||
$metadata = $cors->get('@metadata');
|
||||
if (!empty($metadata) && in_array($metadata['statusCode'], array(200, 204))) return true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
if ($this->useExceptions) {
|
||||
throw $e;
|
||||
} else {
|
||||
return $this->trigger_from_exception($e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create IAM instance
|
||||
*
|
||||
* @return object $this->iam
|
||||
*/
|
||||
private function getIAM() {
|
||||
if (!$this->iam) {
|
||||
$opts = array(
|
||||
'credentials' => array(
|
||||
'key' => $this->__access_key,
|
||||
'secret' => $this->__secret_key
|
||||
),
|
||||
'version' => '2010-05-08',
|
||||
'region' => $this->region
|
||||
);
|
||||
$this->iam = new Aws\Iam\IamClient($opts);
|
||||
}
|
||||
|
||||
return $this->iam;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create IAM user
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* @return array $response
|
||||
*/
|
||||
public function createUser($options) {
|
||||
$iam = $this->getIAM();
|
||||
|
||||
try {
|
||||
$response = $iam->createUser(array(
|
||||
'Path' => $options['Path'],
|
||||
'UserName' => $options['UserName']
|
||||
));
|
||||
} catch (Guzzle\Http\Exception\ClientErrorResponseException $e) {
|
||||
$response = $e->getResponse();
|
||||
$code = $response->getStatusCode();
|
||||
return array(
|
||||
'code' => $code,
|
||||
'error' => $e->getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create access key for IAM user
|
||||
*
|
||||
* @param string $username
|
||||
*
|
||||
* @return array $response
|
||||
*/
|
||||
public function createAccessKey($username) {
|
||||
$iam = $this->getIAM();
|
||||
|
||||
try {
|
||||
$response = $iam->createAccessKey(array('UserName' => $username));
|
||||
} catch (Guzzle\Http\Exception\ClientErrorResponseException $e) {
|
||||
return array(
|
||||
'error' => $e->getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put user policy IAM user
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* @return array $response
|
||||
*/
|
||||
public function putUserPolicy($options) {
|
||||
$iam = $this->getIAM();
|
||||
|
||||
try {
|
||||
$response = $iam->putUserPolicy(array(
|
||||
'UserName' => $options['UserName'],
|
||||
'PolicyName' => $options['PolicyName'],
|
||||
'PolicyDocument' => $options['PolicyDocument']
|
||||
));
|
||||
} catch (Guzzle\Http\Exception\ClientErrorResponseException $e) {
|
||||
return array(
|
||||
'error' => $e->getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,623 @@
|
||||
/*!
|
||||
* jQuery blockUI plugin
|
||||
* Version 2.71.0-2020.12.08
|
||||
* Requires jQuery v1.12 or later
|
||||
*
|
||||
* Examples at: http://malsup.com/jquery/block/
|
||||
* Copyright (c) 2007-2013 M. Alsup
|
||||
* Dual licensed under the MIT and GPL licenses:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*
|
||||
* Thanks to Amir-Hossein Sobhi for some excellent contributions!
|
||||
*/
|
||||
|
||||
;(function() {
|
||||
/*jshint eqeqeq:false curly:false latedef:false */
|
||||
"use strict";
|
||||
|
||||
function setup($) {
|
||||
var migrateDeduplicateWarnings = jQuery.migrateDeduplicateWarnings || false;
|
||||
jQuery.migrateDeduplicateWarnings = false;
|
||||
|
||||
$.fn._fadeIn = $.fn.fadeIn;
|
||||
|
||||
var noOp = $.noop || function() {};
|
||||
|
||||
// this bit is to ensure we don't call setExpression when we shouldn't (with extra muscle to handle
|
||||
// confusing userAgent strings on Vista)
|
||||
var msie = /MSIE/.test(navigator.userAgent);
|
||||
var ie6 = /MSIE 6.0/.test(navigator.userAgent) && ! /MSIE 8.0/.test(navigator.userAgent);
|
||||
var mode = document.documentMode || 0;
|
||||
var setExpr = "function" === typeof document.createElement('div').style.setExpression;
|
||||
|
||||
// global $ methods for blocking/unblocking the entire page
|
||||
$.blockUI = function(opts) { install(window, opts); };
|
||||
$.unblockUI = function(opts) { remove(window, opts); };
|
||||
|
||||
// convenience method for quick growl-like notifications (http://www.google.com/search?q=growl)
|
||||
$.growlUI = function(title, message, timeout, onClose) {
|
||||
var $m = $('<div class="growlUI"></div>');
|
||||
if (title) $m.append('<h1>'+title+'</h1>');
|
||||
if (message) $m.append('<h2>'+message+'</h2>');
|
||||
if (timeout === undefined) timeout = 3000;
|
||||
|
||||
// Added by konapun: Set timeout to 30 seconds if this growl is moused over, like normal toast notifications
|
||||
var callBlock = function(opts) {
|
||||
opts = opts || {};
|
||||
|
||||
$.blockUI({
|
||||
message: $m,
|
||||
fadeIn : typeof opts.fadeIn !== 'undefined' ? opts.fadeIn : 700,
|
||||
fadeOut: typeof opts.fadeOut !== 'undefined' ? opts.fadeOut : 1000,
|
||||
timeout: typeof opts.timeout !== 'undefined' ? opts.timeout : timeout,
|
||||
centerY: false,
|
||||
showOverlay: false,
|
||||
onUnblock: onClose,
|
||||
css: $.blockUI.defaults.growlCSS
|
||||
});
|
||||
};
|
||||
|
||||
callBlock();
|
||||
var nonmousedOpacity = $m.css('opacity');
|
||||
$m.on('mouseover', function() {
|
||||
callBlock({
|
||||
fadeIn: 0,
|
||||
timeout: 30000
|
||||
});
|
||||
|
||||
var displayBlock = $('.blockMsg');
|
||||
displayBlock.stop(); // cancel fadeout if it has started
|
||||
displayBlock.fadeTo(300, 1); // make it easier to read the message by removing transparency
|
||||
}).on('mouseout', function() {
|
||||
$('.blockMsg').fadeOut(1000);
|
||||
});
|
||||
// End konapun additions
|
||||
};
|
||||
|
||||
// plugin method for blocking element content
|
||||
$.fn.block = function(opts) {
|
||||
if ( this[0] === window ) {
|
||||
$.blockUI( opts );
|
||||
return this;
|
||||
}
|
||||
var fullOpts = $.extend({}, $.blockUI.defaults, opts || {});
|
||||
this.each(function() {
|
||||
var $el = $(this);
|
||||
if (fullOpts.ignoreIfBlocked && $el.data('blockUI.isBlocked'))
|
||||
return;
|
||||
$el.unblock({ fadeOut: 0 });
|
||||
});
|
||||
|
||||
return this.each(function() {
|
||||
if ($.css(this,'position') == 'static') {
|
||||
this.style.position = 'relative';
|
||||
$(this).data('blockUI.static', true);
|
||||
}
|
||||
this.style.zoom = 1; // force 'hasLayout' in ie
|
||||
install(this, opts);
|
||||
});
|
||||
};
|
||||
|
||||
// plugin method for unblocking element content
|
||||
$.fn.unblock = function(opts) {
|
||||
if ( this[0] === window ) {
|
||||
$.unblockUI( opts );
|
||||
return this;
|
||||
}
|
||||
return this.each(function() {
|
||||
remove(this, opts);
|
||||
});
|
||||
};
|
||||
|
||||
$.blockUI.version = 2.70; // 2nd generation blocking at no extra cost!
|
||||
|
||||
// override these in your code to change the default behavior and style
|
||||
$.blockUI.defaults = {
|
||||
// message displayed when blocking (use null for no message)
|
||||
message: '<h1>Please wait...</h1>',
|
||||
|
||||
title: null, // title string; only used when theme == true
|
||||
draggable: true, // only used when theme == true (requires jquery-ui.js to be loaded)
|
||||
|
||||
theme: false, // set to true to use with jQuery UI themes
|
||||
|
||||
// styles for the message when blocking; if you wish to disable
|
||||
// these and use an external stylesheet then do this in your code:
|
||||
// $.blockUI.defaults.css = {};
|
||||
css: {
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
width: '30%',
|
||||
top: '40%',
|
||||
left: '35%',
|
||||
textAlign: 'center',
|
||||
color: '#000',
|
||||
border: '3px solid #aaa',
|
||||
backgroundColor:'#fff',
|
||||
cursor: 'wait'
|
||||
},
|
||||
|
||||
// minimal style set used when themes are used
|
||||
themedCSS: {
|
||||
width: '30%',
|
||||
top: '40%',
|
||||
left: '35%'
|
||||
},
|
||||
|
||||
// styles for the overlay
|
||||
overlayCSS: {
|
||||
backgroundColor: '#000',
|
||||
opacity: 0.6,
|
||||
cursor: 'wait'
|
||||
},
|
||||
|
||||
// style to replace wait cursor before unblocking to correct issue
|
||||
// of lingering wait cursor
|
||||
cursorReset: 'default',
|
||||
|
||||
// styles applied when using $.growlUI
|
||||
growlCSS: {
|
||||
width: '350px',
|
||||
top: '10px',
|
||||
left: '',
|
||||
right: '10px',
|
||||
border: 'none',
|
||||
padding: '5px',
|
||||
opacity: 0.6,
|
||||
cursor: 'default',
|
||||
color: '#fff',
|
||||
backgroundColor: '#000',
|
||||
'-webkit-border-radius':'10px',
|
||||
'-moz-border-radius': '10px',
|
||||
'border-radius': '10px'
|
||||
},
|
||||
|
||||
// IE issues: 'about:blank' fails on HTTPS and javascript:false is s-l-o-w
|
||||
// (hat tip to Jorge H. N. de Vasconcelos)
|
||||
/*jshint scripturl:true */
|
||||
iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank',
|
||||
|
||||
// force usage of iframe in non-IE browsers (handy for blocking applets)
|
||||
forceIframe: false,
|
||||
|
||||
// z-index for the blocking overlay
|
||||
baseZ: 1000,
|
||||
|
||||
// set these to true to have the message automatically centered
|
||||
centerX: true, // <-- only effects element blocking (page block controlled via css above)
|
||||
centerY: true,
|
||||
|
||||
// allow body element to be stetched in ie6; this makes blocking look better
|
||||
// on "short" pages. disable if you wish to prevent changes to the body height
|
||||
allowBodyStretch: true,
|
||||
|
||||
// enable if you want key and mouse events to be disabled for content that is blocked
|
||||
bindEvents: true,
|
||||
|
||||
// be default blockUI will suppress tab navigation from leaving blocking content
|
||||
// (if bindEvents is true)
|
||||
constrainTabKey: true,
|
||||
|
||||
// fadeIn time in millis; set to 0 to disable fadeIn on block
|
||||
fadeIn: 200,
|
||||
|
||||
// fadeOut time in millis; set to 0 to disable fadeOut on unblock
|
||||
fadeOut: 400,
|
||||
|
||||
// time in millis to wait before auto-unblocking; set to 0 to disable auto-unblock
|
||||
timeout: 0,
|
||||
|
||||
// disable if you don't want to show the overlay
|
||||
showOverlay: true,
|
||||
|
||||
// if true, focus will be placed in the first available input field when
|
||||
// page blocking
|
||||
focusInput: true,
|
||||
|
||||
// elements that can receive focus
|
||||
focusableElements: ':input:enabled:visible',
|
||||
|
||||
// suppresses the use of overlay styles on FF/Linux (due to performance issues with opacity)
|
||||
// no longer needed in 2012
|
||||
// applyPlatformOpacityRules: true,
|
||||
|
||||
// callback method invoked when fadeIn has completed and blocking message is visible
|
||||
onBlock: null,
|
||||
|
||||
// callback method invoked when unblocking has completed; the callback is
|
||||
// passed the element that has been unblocked (which is the window object for page
|
||||
// blocks) and the options that were passed to the unblock call:
|
||||
// onUnblock(element, options)
|
||||
onUnblock: null,
|
||||
|
||||
// callback method invoked when the overlay area is clicked.
|
||||
// setting this will turn the cursor to a pointer, otherwise cursor defined in overlayCss will be used.
|
||||
onOverlayClick: null,
|
||||
|
||||
// don't ask; if you really must know: http://groups.google.com/group/jquery-en/browse_thread/thread/36640a8730503595/2f6a79a77a78e493#2f6a79a77a78e493
|
||||
quirksmodeOffsetHack: 4,
|
||||
|
||||
// class name of the message block
|
||||
blockMsgClass: 'blockMsg',
|
||||
|
||||
// if it is already blocked, then ignore it (don't unblock and reblock)
|
||||
ignoreIfBlocked: false
|
||||
};
|
||||
|
||||
// private data and functions follow...
|
||||
|
||||
var pageBlock = null;
|
||||
var pageBlockEls = [];
|
||||
|
||||
function install(el, opts) {
|
||||
var css, themedCSS;
|
||||
var full = (el == window);
|
||||
var msg = (opts && opts.message !== undefined ? opts.message : undefined);
|
||||
opts = $.extend({}, $.blockUI.defaults, opts || {});
|
||||
|
||||
if (opts.ignoreIfBlocked && $(el).data('blockUI.isBlocked'))
|
||||
return;
|
||||
|
||||
opts.overlayCSS = $.extend({}, $.blockUI.defaults.overlayCSS, opts.overlayCSS || {});
|
||||
css = $.extend({}, $.blockUI.defaults.css, opts.css || {});
|
||||
if (opts.onOverlayClick)
|
||||
opts.overlayCSS.cursor = 'pointer';
|
||||
|
||||
themedCSS = $.extend({}, $.blockUI.defaults.themedCSS, opts.themedCSS || {});
|
||||
msg = msg === undefined ? opts.message : msg;
|
||||
|
||||
// remove the current block (if there is one)
|
||||
if (full && pageBlock)
|
||||
remove(window, {fadeOut:0});
|
||||
|
||||
// if an existing element is being used as the blocking content then we capture
|
||||
// its current place in the DOM (and current display style) so we can restore
|
||||
// it when we unblock
|
||||
if (msg && typeof msg != 'string' && (msg.parentNode || msg.jquery)) {
|
||||
var node = msg.jquery ? msg[0] : msg;
|
||||
var data = {};
|
||||
$(el).data('blockUI.history', data);
|
||||
data.el = node;
|
||||
data.parent = node.parentNode;
|
||||
data.display = node.style.display;
|
||||
data.position = node.style.position;
|
||||
if (data.parent)
|
||||
data.parent.removeChild(node);
|
||||
}
|
||||
|
||||
$(el).data('blockUI.onUnblock', opts.onUnblock);
|
||||
var z = opts.baseZ;
|
||||
|
||||
// blockUI uses 3 layers for blocking, for simplicity they are all used on every platform;
|
||||
// layer1 is the iframe layer which is used to suppress bleed through of underlying content
|
||||
// layer2 is the overlay layer which has opacity and a wait cursor (by default)
|
||||
// layer3 is the message content that is displayed while blocking
|
||||
var lyr1, lyr2, lyr3, s;
|
||||
if (msie || opts.forceIframe)
|
||||
lyr1 = $('<iframe class="blockUI" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;position:absolute;width:100%;height:100%;top:0;left:0" src="'+opts.iframeSrc+'"></iframe>');
|
||||
else
|
||||
lyr1 = $('<div class="blockUI" style="display:none"></div>');
|
||||
|
||||
if (opts.theme)
|
||||
lyr2 = $('<div class="blockUI blockOverlay ui-widget-overlay" style="z-index:'+ (z++) +';display:none"></div>');
|
||||
else
|
||||
lyr2 = $('<div class="blockUI blockOverlay" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;width:100%;height:100%;top:0;left:0"></div>');
|
||||
|
||||
if (opts.theme && full) {
|
||||
s = '<div class="blockUI ' + opts.blockMsgClass + ' blockPage ui-dialog ui-widget ui-corner-all" style="z-index:'+(z+10)+';display:none;position:fixed">';
|
||||
if ( opts.title ) {
|
||||
s += '<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(opts.title || ' ')+'</div>';
|
||||
}
|
||||
s += '<div class="ui-widget-content ui-dialog-content"></div>';
|
||||
s += '</div>';
|
||||
}
|
||||
else if (opts.theme) {
|
||||
s = '<div class="blockUI ' + opts.blockMsgClass + ' blockElement ui-dialog ui-widget ui-corner-all" style="z-index:'+(z+10)+';display:none;position:absolute">';
|
||||
if ( opts.title ) {
|
||||
s += '<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(opts.title || ' ')+'</div>';
|
||||
}
|
||||
s += '<div class="ui-widget-content ui-dialog-content"></div>';
|
||||
s += '</div>';
|
||||
}
|
||||
else if (full) {
|
||||
s = '<div class="blockUI ' + opts.blockMsgClass + ' blockPage" style="z-index:'+(z+10)+';display:none;position:fixed"></div>';
|
||||
}
|
||||
else {
|
||||
s = '<div class="blockUI ' + opts.blockMsgClass + ' blockElement" style="z-index:'+(z+10)+';display:none;position:absolute"></div>';
|
||||
}
|
||||
lyr3 = $(s);
|
||||
|
||||
// if we have a message, style it
|
||||
if (msg) {
|
||||
if (opts.theme) {
|
||||
lyr3.css(themedCSS);
|
||||
lyr3.addClass('ui-widget-content');
|
||||
}
|
||||
else
|
||||
lyr3.css(css);
|
||||
}
|
||||
|
||||
// style the overlay
|
||||
if (!opts.theme /*&& (!opts.applyPlatformOpacityRules)*/)
|
||||
lyr2.css(opts.overlayCSS);
|
||||
lyr2.css('position', full ? 'fixed' : 'absolute');
|
||||
|
||||
// make iframe layer transparent in IE
|
||||
if (msie || opts.forceIframe)
|
||||
lyr1.css('opacity',0.0);
|
||||
|
||||
//$([lyr1[0],lyr2[0],lyr3[0]]).appendTo(full ? 'body' : el);
|
||||
var layers = [lyr1,lyr2,lyr3], $par = full ? $('body') : $(el);
|
||||
$.each(layers, function() {
|
||||
this.appendTo($par);
|
||||
});
|
||||
|
||||
if (opts.theme && opts.draggable && $.fn.draggable) {
|
||||
lyr3.draggable({
|
||||
handle: '.ui-dialog-titlebar',
|
||||
cancel: 'li'
|
||||
});
|
||||
}
|
||||
|
||||
// ie7 must use absolute positioning in quirks mode and to account for activex issues (when scrolling)
|
||||
var expr = setExpr && ( "CSS1Compat" !== document.compatMode || $('object,embed', full ? null : el).length > 0);
|
||||
if (ie6 || expr) {
|
||||
// give body 100% height
|
||||
if (full && opts.allowBodyStretch && "CSS1Compat" === document.compatMode)
|
||||
$('html,body').css('height','100%');
|
||||
|
||||
// fix ie6 issue when blocked element has a border width
|
||||
if ((ie6 || "CSS1Compat" !== document.compatMode) && !full) {
|
||||
var t = sz(el,'borderTopWidth'), l = sz(el,'borderLeftWidth');
|
||||
var fixT = t ? '(0 - '+t+')' : 0;
|
||||
var fixL = l ? '(0 - '+l+')' : 0;
|
||||
}
|
||||
|
||||
// simulate fixed position
|
||||
$.each(layers, function(i,o) {
|
||||
var s = o[0].style;
|
||||
s.position = 'absolute';
|
||||
if (i < 2) {
|
||||
if (full)
|
||||
s.setExpression('height','Math.max(document.body.scrollHeight, document.body.offsetHeight) - ("CSS1Compat" === document.compatMode?0:'+opts.quirksmodeOffsetHack+') + "px"');
|
||||
else
|
||||
s.setExpression('height','this.parentNode.offsetHeight + "px"');
|
||||
if (full)
|
||||
s.setExpression('width','"CSS1Compat" === document.compatMode && document.documentElement.clientWidth || document.body.clientWidth + "px"');
|
||||
else
|
||||
s.setExpression('width','this.parentNode.offsetWidth + "px"');
|
||||
if (fixL) s.setExpression('left', fixL);
|
||||
if (fixT) s.setExpression('top', fixT);
|
||||
}
|
||||
else if (opts.centerY) {
|
||||
if (full) s.setExpression('top','(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"');
|
||||
s.marginTop = 0;
|
||||
}
|
||||
else if (!opts.centerY && full) {
|
||||
var top = (opts.css && opts.css.top) ? parseInt(opts.css.top, 10) : 0;
|
||||
var expression = '((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + '+top+') + "px"';
|
||||
s.setExpression('top',expression);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// show the message
|
||||
if (msg) {
|
||||
if (opts.theme)
|
||||
lyr3.find('.ui-widget-content').append(msg);
|
||||
else
|
||||
lyr3.append(msg);
|
||||
if (msg.jquery || msg.nodeType)
|
||||
$(msg).show();
|
||||
}
|
||||
|
||||
if ((msie || opts.forceIframe) && opts.showOverlay)
|
||||
lyr1.show(); // opacity is zero
|
||||
if (opts.fadeIn) {
|
||||
var cb = opts.onBlock ? opts.onBlock : noOp;
|
||||
var cb1 = (opts.showOverlay && !msg) ? cb : noOp;
|
||||
var cb2 = msg ? cb : noOp;
|
||||
if (opts.showOverlay)
|
||||
lyr2._fadeIn(opts.fadeIn, cb1);
|
||||
if (msg)
|
||||
lyr3._fadeIn(opts.fadeIn, cb2);
|
||||
}
|
||||
else {
|
||||
if (opts.showOverlay)
|
||||
lyr2.show();
|
||||
if (msg)
|
||||
lyr3.show();
|
||||
if (opts.onBlock)
|
||||
opts.onBlock.bind(lyr3)();
|
||||
}
|
||||
|
||||
// bind key and mouse events
|
||||
bind(1, el, opts);
|
||||
|
||||
if (full) {
|
||||
pageBlock = lyr3[0];
|
||||
pageBlockEls = $(opts.focusableElements,pageBlock);
|
||||
if (opts.focusInput)
|
||||
setTimeout(focus, 20);
|
||||
}
|
||||
else
|
||||
center(lyr3[0], opts.centerX, opts.centerY);
|
||||
|
||||
if (opts.timeout) {
|
||||
// auto-unblock
|
||||
var to = setTimeout(function() {
|
||||
if (full)
|
||||
$.unblockUI(opts);
|
||||
else
|
||||
$(el).unblock(opts);
|
||||
}, opts.timeout);
|
||||
$(el).data('blockUI.timeout', to);
|
||||
}
|
||||
}
|
||||
|
||||
// remove the block
|
||||
function remove(el, opts) {
|
||||
var count;
|
||||
var full = (el == window);
|
||||
var $el = $(el);
|
||||
var data = $el.data('blockUI.history');
|
||||
var to = $el.data('blockUI.timeout');
|
||||
if (to) {
|
||||
clearTimeout(to);
|
||||
$el.removeData('blockUI.timeout');
|
||||
}
|
||||
opts = $.extend({}, $.blockUI.defaults, opts || {});
|
||||
bind(0, el, opts); // unbind events
|
||||
|
||||
if (opts.onUnblock === null) {
|
||||
opts.onUnblock = $el.data('blockUI.onUnblock');
|
||||
$el.removeData('blockUI.onUnblock');
|
||||
}
|
||||
|
||||
var els;
|
||||
if (full) // crazy selector to handle odd field errors in ie6/7
|
||||
els = $('body').children().filter('.blockUI').add('body > .blockUI');
|
||||
else
|
||||
els = $el.find('>.blockUI');
|
||||
|
||||
// fix cursor issue
|
||||
if ( opts.cursorReset ) {
|
||||
if ( els.length > 1 )
|
||||
els[1].style.cursor = opts.cursorReset;
|
||||
if ( els.length > 2 )
|
||||
els[2].style.cursor = opts.cursorReset;
|
||||
}
|
||||
|
||||
if (full)
|
||||
pageBlock = pageBlockEls = null;
|
||||
|
||||
if (opts.fadeOut) {
|
||||
count = els.length;
|
||||
els.stop().fadeOut(opts.fadeOut, function() {
|
||||
if ( --count === 0)
|
||||
reset(els,data,opts,el);
|
||||
});
|
||||
}
|
||||
else
|
||||
reset(els, data, opts, el);
|
||||
}
|
||||
|
||||
// move blocking element back into the DOM where it started
|
||||
function reset(els,data,opts,el) {
|
||||
var $el = $(el);
|
||||
if ( $el.data('blockUI.isBlocked') )
|
||||
return;
|
||||
|
||||
els.each(function(i,o) {
|
||||
// remove via DOM calls so we don't lose event handlers
|
||||
if (this.parentNode)
|
||||
this.parentNode.removeChild(this);
|
||||
});
|
||||
|
||||
if (data && data.el) {
|
||||
data.el.style.display = data.display;
|
||||
data.el.style.position = data.position;
|
||||
data.el.style.cursor = 'default'; // #59
|
||||
if (data.parent)
|
||||
data.parent.appendChild(data.el);
|
||||
$el.removeData('blockUI.history');
|
||||
}
|
||||
|
||||
if ($el.data('blockUI.static')) {
|
||||
$el.css('position', 'static'); // #22
|
||||
}
|
||||
|
||||
if (typeof opts.onUnblock == 'function')
|
||||
opts.onUnblock(el,opts);
|
||||
|
||||
// fix issue in Safari 6 where block artifacts remain until reflow
|
||||
var body = $(document.body), w = body.width(), cssW = body[0].style.width;
|
||||
body.width(w-1).width(w);
|
||||
body[0].style.width = cssW;
|
||||
}
|
||||
|
||||
// bind/unbind the handler
|
||||
function bind(b, el, opts) {
|
||||
var full = el == window, $el = $(el);
|
||||
|
||||
// don't bother unbinding if there is nothing to unbind
|
||||
if (!b && (full && !pageBlock || !full && !$el.data('blockUI.isBlocked')))
|
||||
return;
|
||||
|
||||
$el.data('blockUI.isBlocked', b);
|
||||
|
||||
// don't bind events when overlay is not in use or if bindEvents is false
|
||||
if (!full || !opts.bindEvents || (b && !opts.showOverlay))
|
||||
return;
|
||||
|
||||
// bind anchors and inputs for mouse and key events
|
||||
var events = 'mousedown mouseup keydown keypress keyup touchstart touchend touchmove';
|
||||
if (b)
|
||||
$(document).on(events, opts, handler);
|
||||
else
|
||||
$(document).off(events, handler);
|
||||
|
||||
// former impl...
|
||||
// var $e = $('a,:input');
|
||||
// b ? $e.bind(events, opts, handler) : $e.unbind(events, handler);
|
||||
}
|
||||
|
||||
// event handler to suppress keyboard/mouse events when blocking
|
||||
function handler(e) {
|
||||
// allow tab navigation (conditionally)
|
||||
if (e.type === 'keydown' && e.keyCode && e.keyCode == 9) {
|
||||
if (pageBlock && e.data.constrainTabKey) {
|
||||
var els = pageBlockEls;
|
||||
var fwd = !e.shiftKey && e.target === els[els.length-1];
|
||||
var back = e.shiftKey && e.target === els[0];
|
||||
if (fwd || back) {
|
||||
setTimeout(function(){focus(back);},10);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
var opts = e.data;
|
||||
var target = $(e.target);
|
||||
if (target.hasClass('blockOverlay') && opts.onOverlayClick)
|
||||
opts.onOverlayClick(e);
|
||||
|
||||
// allow events within the message content
|
||||
if (target.parents('div.' + opts.blockMsgClass).length > 0)
|
||||
return true;
|
||||
|
||||
// allow events for content that is not being blocked
|
||||
return target.parents().children().filter('div.blockUI').length === 0;
|
||||
}
|
||||
|
||||
function focus(back) {
|
||||
if (!pageBlockEls)
|
||||
return;
|
||||
var e = pageBlockEls[back===true ? pageBlockEls.length-1 : 0];
|
||||
if (e)
|
||||
e.focus();
|
||||
}
|
||||
|
||||
function center(el, x, y) {
|
||||
var p = el.parentNode, s = el.style;
|
||||
var l = ((p.offsetWidth - el.offsetWidth)/2) - sz(p,'borderLeftWidth');
|
||||
var t = ((p.offsetHeight - el.offsetHeight)/2) - sz(p,'borderTopWidth');
|
||||
if (x) s.left = l > 0 ? (l+'px') : '0';
|
||||
if (y) s.top = t > 0 ? (t+'px') : '0';
|
||||
}
|
||||
|
||||
function sz(el, p) {
|
||||
return parseInt($.css(el,p),10)||0;
|
||||
}
|
||||
jQuery.migrateDeduplicateWarnings = migrateDeduplicateWarnings;
|
||||
}
|
||||
|
||||
|
||||
/*global define:true */
|
||||
if (typeof define === 'function' && define.amd && define.amd.jQuery) {
|
||||
define(['jquery'], setup);
|
||||
} else {
|
||||
setup(jQuery);
|
||||
}
|
||||
|
||||
})();
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 91.3 91.1" enable-background="new 0 0 91.3 91.1" xml:space="preserve">
|
||||
<circle cx="45.7" cy="45.7" r="45.7"/>
|
||||
<circle fill="#FFFFFF" cx="45.7" cy="24.4" r="12.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 450 B |
@@ -0,0 +1,2 @@
|
||||
@-webkit-keyframes spin{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spin{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}body.udp-modal-is-opened{overflow:hidden}.udp-modal{position:fixed;top:0;left:0;bottom:0;right:0;z-index:20000}.udp-modal__overlay{width:100%;height:100%;position:absolute;background:#000;opacity:.8;z-index:1}.udp-modal__modal{position:absolute;z-index:2;left:0;top:32px;bottom:0;right:0;background:#FFF;-webkit-box-shadow:0 4px 10px rgba(0,0,0,0.45882);box-shadow:0 4px 10px rgba(0,0,0,0.45882);overflow:auto}.udp-modal__content{position:relative;overflow:auto}.udp-modal__content .img{padding:0 20px;-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center}.udp-modal__content .img img{max-width:100%}.udp-modal__content .text{padding:40px}.udp-modal.loading{background-image:url()}.udp-modal.loading::before{height:1em;width:1em;display:block;position:absolute;top:50%;left:50%;margin-left:-0.5em;margin-top:-0.5em;content:'';-webkit-animation:spin 1s linear infinite;animation:spin 1s linear infinite;background:url('loader.svg') center center;background-size:cover;line-height:1;text-align:center;font-size:2em;color:#000;z-index:3;opacity:.5}.udp-modal.loading .udp-modal__content,.udp-modal.loading .udp-modal__sidebar,.iframe-is-opened .udp-modal__content,.iframe-is-opened .udp-modal__sidebar{display:none}.udp-modal__iframe{position:absolute;top:0;left:0;right:0;bottom:0;z-index:3;background:#FFF}.udp-modal__iframe iframe{position:absolute;width:100%;height:100%}@media(min-width:1200px){.udp-modal__modal{left:20px;top:calc(20px + 32px);bottom:20px;right:20px}}@media(max-width:782px){.udp-modal__modal{top:46px}}
|
||||
/*# sourceMappingURL=udp-checkout-embed-1-25-3.min.css.map */
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["includes/checkout-embed/assets/udp-checkout-embed.css"],"names":[],"mappings":"AAAA;;CAEC;EACC,mCAA2B;UAA3B,2BAA2B;CAC5B;;AAED;;AANA;;CAEC;EACC,mCAA2B;UAA3B,2BAA2B;CAC5B;;AAED;;AAEA;CACC,gBAAgB;AACjB;;AAEA;CACC,eAAe;CACf,MAAM;CACN,OAAO;CACP,SAAS;CACT,QAAQ;CACR,cAAc;AACf;;AAEA;CACC,WAAW;CACX,YAAY;CACZ,kBAAkB;CAClB,gBAAgB;CAChB,YAAY;CACZ,UAAU;AACX;;AAEA;CACC,kBAAkB;CAClB,UAAU;CACV,OAAO;CACP,SAAS;CACT,SAAS;CACT,QAAQ;CACR,gBAAgB;CAChB,oDAAkC;SAAlC,4CAAkC;CAClC,cAAc;AACf;;AAEA;CACC,kBAAkB;CAClB,cAAc;AACf;;AAEA;CACC,eAAe;CACf,8BAAsB;SAAtB,sBAAsB;CACtB,kBAAkB;AACnB;;AAEA;CACC,eAAe;AAChB;;AAEA;CACC,aAAa;AACd;;AAEA;CACC,uBAAuB;AACxB;;AAEA;CACC,WAAW;CACX,UAAU;CACV,cAAc;CACd,kBAAkB;CAClB,QAAQ;CACR,SAAS;CACT,mBAAmB;CACnB,kBAAkB;CAClB,WAAW;CACX,0CAA0C;CAC1C,kCAAkC;CAClC,2CAA2C;CAC3C,sBAAsB;CACtB,cAAc;CACd,kBAAkB;CAClB,cAAc;CACd,WAAW;CACX,UAAU;CACV,YAAY;AACb;;AAEA;;;;CAIC,aAAa;AACd;;AAEA;CACC,kBAAkB;CAClB,MAAM;CACN,OAAO;CACP,QAAQ;CACR,SAAS;CACT,UAAU;CACV,gBAAgB;AACjB;;AAEA;CACC,kBAAkB;CAClB,WAAW;CACX,YAAY;AACb;;AAEA;;CAEC;EACC,UAAU;EACV,sBAAsB;EACtB,YAAY;EACZ,WAAW;CACZ;;AAED;;AAEA;;CAEC;EACC,SAAS;CACV;;AAED","file":"udp-checkout-embed-1-25-3.min.css","sourcesContent":["@keyframes spin {\n\n\t100% {\n\t\ttransform: rotate( 360deg );\n\t}\n\n}\n\nbody.udp-modal-is-opened {\n\toverflow: hidden;\n}\n\n.udp-modal {\n\tposition: fixed;\n\ttop: 0;\n\tleft: 0;\n\tbottom: 0;\n\tright: 0;\n\tz-index: 20000;\n}\n\n.udp-modal__overlay {\n\twidth: 100%;\n\theight: 100%;\n\tposition: absolute;\n\tbackground: #000;\n\topacity: 0.8;\n\tz-index: 1;\n}\n\n.udp-modal__modal {\n\tposition: absolute;\n\tz-index: 2;\n\tleft: 0;\n\ttop: 32px;\n\tbottom: 0;\n\tright: 0;\n\tbackground: #FFF;\n\tbox-shadow: 0px 4px 10px #00000075;\n\toverflow: auto;\n}\n\n.udp-modal__content {\n\tposition: relative;\n\toverflow: auto;\n}\n\n.udp-modal__content .img {\n\tpadding: 0 20px;\n\tbox-sizing: border-box;\n\ttext-align: center;\n}\n\n.udp-modal__content .img img {\n\tmax-width: 100%;\n}\n\n.udp-modal__content .text {\n\tpadding: 40px;\n}\n\n.udp-modal.loading {\n\tbackground-image: url();\n}\n\n.udp-modal.loading::before {\n\theight: 1em;\n\twidth: 1em;\n\tdisplay: block;\n\tposition: absolute;\n\ttop: 50%;\n\tleft: 50%;\n\tmargin-left: -0.5em;\n\tmargin-top: -0.5em;\n\tcontent: '';\n\t-webkit-animation: spin 1s linear infinite;\n\tanimation: spin 1s linear infinite;\n\tbackground: url('loader.svg') center center;\n\tbackground-size: cover;\n\tline-height: 1;\n\ttext-align: center;\n\tfont-size: 2em;\n\tcolor: #000;\n\tz-index: 3;\n\topacity: 0.5;\n}\n\n.udp-modal.loading .udp-modal__content,\n.udp-modal.loading .udp-modal__sidebar,\n.iframe-is-opened .udp-modal__content,\n.iframe-is-opened .udp-modal__sidebar {\n\tdisplay: none;\n}\n\n.udp-modal__iframe {\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\tright: 0;\n\tbottom: 0;\n\tz-index: 3;\n\tbackground: #FFF;\n}\n\n.udp-modal__iframe iframe {\n\tposition: absolute;\n\twidth: 100%;\n\theight: 100%;\n}\n\n@media(min-width: 1200px) {\n\n\t.udp-modal__modal {\n\t\tleft: 20px;\n\t\ttop: calc(20px + 32px);\n\t\tbottom: 20px;\n\t\tright: 20px;\n\t}\n\n}\n\n@media(max-width: 782px) {\n\n\t.udp-modal__modal {\n\t\ttop: 46px;\n\t}\n\n}"]}
|
||||
@@ -0,0 +1 @@
|
||||
(o=>{var t={loading:!1,init:function(){var t;o("a[data-embed-checkout]").length&&(t=this,o(document).on("click","a[data-embed-checkout]",function(e){e.preventDefault(),t.modal.open(o(this))}))},modal:{open:function(e){this.$target=e,this.product_url&&(this.product_url,this.product_url==e.data("embed-checkout"))||(this.product_url=e.data("embed-checkout")),this.show_checkout()},setup:function(){this.$el&&(this.$el.remove(),this.$el=null);var e=o("#udp-modal-template").html();this.$el=o(e),window.addEventListener("message",function(e){var t=e.data;if(t&&t.action)switch(t.action){case"domready":this.$el.removeClass("loading");break;case"closemodal":o(document).trigger("udp/checkout/close",t.data,this.$target),this.close();break;case"ordercomplete":console.log("Order complete:",t.data),o(document).trigger("udp/checkout/done",t.data,this.$target)}}.bind(this))},close:function(e){e&&e.preventDefault(),o("body").removeClass("udp-modal-is-opened"),this.$iframe&&(this.$iframe.remove(),this.$iframe_container.remove()),this.$el.hide()},show_checkout:function(){window.open(this.product_url,"_blank","noopener, noreferrer")}}};jQuery(function(e){t.init()})})(jQuery);
|
||||
@@ -0,0 +1,140 @@
|
||||
@-webkit-keyframes spin {
|
||||
|
||||
100% {
|
||||
-webkit-transform: rotate( 360deg );
|
||||
transform: rotate( 360deg );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
|
||||
100% {
|
||||
-webkit-transform: rotate( 360deg );
|
||||
transform: rotate( 360deg );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
body.udp-modal-is-opened {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.udp-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 20000;
|
||||
}
|
||||
|
||||
.udp-modal__overlay {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
background: #000;
|
||||
opacity: 0.8;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.udp-modal__modal {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
left: 0;
|
||||
top: 32px;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background: #FFF;
|
||||
-webkit-box-shadow: 0px 4px 10px rgba(0,0,0,0.45882);
|
||||
box-shadow: 0px 4px 10px rgba(0,0,0,0.45882);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.udp-modal__content {
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.udp-modal__content .img {
|
||||
padding: 0 20px;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.udp-modal__content .img img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.udp-modal__content .text {
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.udp-modal.loading {
|
||||
background-image: url();
|
||||
}
|
||||
|
||||
.udp-modal.loading::before {
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-left: -0.5em;
|
||||
margin-top: -0.5em;
|
||||
content: '';
|
||||
-webkit-animation: spin 1s linear infinite;
|
||||
animation: spin 1s linear infinite;
|
||||
background: url('loader.svg') center center;
|
||||
background-size: cover;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
font-size: 2em;
|
||||
color: #000;
|
||||
z-index: 3;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.udp-modal.loading .udp-modal__content,
|
||||
.udp-modal.loading .udp-modal__sidebar,
|
||||
.iframe-is-opened .udp-modal__content,
|
||||
.iframe-is-opened .udp-modal__sidebar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.udp-modal__iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 3;
|
||||
background: #FFF;
|
||||
}
|
||||
|
||||
.udp-modal__iframe iframe {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media(min-width: 1200px) {
|
||||
|
||||
.udp-modal__modal {
|
||||
left: 20px;
|
||||
top: calc(20px + 32px);
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media(max-width: 782px) {
|
||||
|
||||
.udp-modal__modal {
|
||||
top: 46px;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
(function($) {
|
||||
var checkout_embed = {
|
||||
loading: false,
|
||||
init: function() {
|
||||
var buttons = $('a[data-embed-checkout]');
|
||||
|
||||
if (!buttons.length) return;
|
||||
|
||||
var that = this;
|
||||
$(document).on('click', 'a[data-embed-checkout]', function(e) {
|
||||
e.preventDefault();
|
||||
that.modal.open($(this));
|
||||
});
|
||||
},
|
||||
modal: {
|
||||
open: function($target) {
|
||||
// if first time opening or different product
|
||||
this.$target = $target;
|
||||
|
||||
if (!this.product_url || (this.product_url && this.product_url != $target.data('embed-checkout'))) {
|
||||
this.product_url = $target.data('embed-checkout');
|
||||
// Disabled because of SameSite cookie issues
|
||||
// this.setup();
|
||||
}
|
||||
|
||||
this.show_checkout();
|
||||
|
||||
if (0) {
|
||||
// Disabled because of SameSite cookie issues
|
||||
// adds / remove classes
|
||||
$('body').addClass('udp-modal-is-opened');
|
||||
this.$el.removeClass('iframe-is-opened');
|
||||
|
||||
// Show it.
|
||||
this.$el.appendTo('body').show();
|
||||
window.scrollTo(0,0);
|
||||
}
|
||||
},
|
||||
setup: function() {
|
||||
if (this.$el) {
|
||||
this.$el.remove();
|
||||
this.$el = null;
|
||||
}
|
||||
var template = $('#udp-modal-template').html();
|
||||
this.$el = $(template);
|
||||
// receives events from iframe
|
||||
window.addEventListener('message', function(event) {
|
||||
var response = event.data;
|
||||
if (response && response.action) {
|
||||
switch (response.action) {
|
||||
case 'domready':
|
||||
this.$el.removeClass('loading');
|
||||
break;
|
||||
case 'closemodal':
|
||||
$(document).trigger('udp/checkout/close', response.data, this.$target);
|
||||
this.close();
|
||||
break;
|
||||
case 'ordercomplete':
|
||||
console.log('Order complete:', response.data);
|
||||
$(document).trigger('udp/checkout/done', response.data, this.$target);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
close: function(event) {
|
||||
if (event) event.preventDefault();
|
||||
$('body').removeClass('udp-modal-is-opened');
|
||||
if (this.$iframe) {
|
||||
this.$iframe.remove();
|
||||
this.$iframe_container.remove();
|
||||
}
|
||||
this.$el.hide();
|
||||
},
|
||||
show_checkout: function() {
|
||||
if (1) {
|
||||
window.open(this.product_url, '_blank', 'noopener, noreferrer');
|
||||
} else {
|
||||
// Disabled because of SameSite problems
|
||||
this.$el.addClass('loading iframe-is-opened');
|
||||
this.$iframe = $('<iframe src="' + this.product_url + '"/>');
|
||||
this.$iframe_container = $('<div class="udp-modal__iframe"/>').appendTo(this.$el.find('.udp-modal__modal')).append(this.$iframe);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
jQuery(function(e) {
|
||||
checkout_embed.init();
|
||||
});
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) die('No direct access.');
|
||||
|
||||
/**
|
||||
* Class UDP_Checkout_Embed
|
||||
*
|
||||
* Create links to embed an external checkout page
|
||||
*/
|
||||
if (!class_exists('Updraft_Checkout_Embed')) {
|
||||
class Updraft_Checkout_Embed {
|
||||
|
||||
/**
|
||||
* Class version
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private static $version = '1.0.1';
|
||||
|
||||
/**
|
||||
* The name of the plugin using the class
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $plugin_name;
|
||||
|
||||
/**
|
||||
* The return url after purchase is complete / canceled
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $return_url;
|
||||
|
||||
/**
|
||||
* The array of products
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $products_list;
|
||||
|
||||
/**
|
||||
* The page in which the scripts are included
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $load_in_pages;
|
||||
|
||||
/**
|
||||
* The plugin base url
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $base_url;
|
||||
|
||||
/**
|
||||
* Products list
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $products = array();
|
||||
|
||||
/**
|
||||
* Construct
|
||||
*
|
||||
* @param string $plugin_name Current plugin using the class
|
||||
* @param string $return_url The return URL after purchase is complete / canceled. Specially useful with paypal, that forces a redirect.
|
||||
* @param array $products_list The list of products. Array or object that can be converted to an array
|
||||
* @param string $base_url The plugin url, to where 'checkout-embed' is located. Used to enqueue scripts and styles.
|
||||
* @param array $load_in_pages Pages in which the scripts are included. Use to limit the inclusion if necessary. See $this->enqueue_scripts
|
||||
*/
|
||||
public function __construct($plugin_name, $return_url, $products_list, $base_url, $load_in_pages = null) {
|
||||
$this->plugin_name = sanitize_key($plugin_name);
|
||||
$this->return_url = $return_url;
|
||||
$this->products_list = $products_list;
|
||||
$this->load_in_pages = $load_in_pages;
|
||||
$this->base_url = $base_url;
|
||||
add_action('admin_enqueue_scripts', array($this, 'enqueue_scripts'));
|
||||
add_action('admin_footer', array($this, 'print_template'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product using its slug
|
||||
*
|
||||
* @param string $product_slug
|
||||
* @param string $return_url
|
||||
* @return string|bool
|
||||
*/
|
||||
public function get_product($product_slug, $return_url = '') {
|
||||
$products = $this->get_products();
|
||||
if (empty($products)) return false;
|
||||
|
||||
if (is_object($products)) $products = get_object_vars($products);
|
||||
|
||||
if (is_array($products) && array_key_exists($product_slug, $products)) {
|
||||
|
||||
if (!$return_url) $return_url = $this->return_url;
|
||||
$return_url = add_query_arg($this->plugin_name.'_product', $product_slug, $return_url);
|
||||
|
||||
return apply_filters(
|
||||
$this->plugin_name.'_return_url',
|
||||
add_query_arg(
|
||||
array(
|
||||
$this->plugin_name.'_return_url' => urlencode($return_url),
|
||||
'checkout_embed_product_slug' => $product_slug
|
||||
),
|
||||
$products[$product_slug]
|
||||
),
|
||||
$product_slug
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the products on the remote url
|
||||
* Can return an object, if the products list given to the class is. (eg. json_decode gives an object if not specified otherwise)
|
||||
*
|
||||
* @return array|object
|
||||
*/
|
||||
private function get_products() {
|
||||
return apply_filters($this->plugin_name.'_checkout_embed_get_products', $this->products_list ? $this->products_list : array());
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the required scripts / styles
|
||||
*
|
||||
* @param string $hook
|
||||
*/
|
||||
public function enqueue_scripts($hook) {
|
||||
if (is_array($this->load_in_pages)) {
|
||||
if (!in_array($hook, $this->load_in_pages)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
wp_enqueue_script($this->plugin_name.'-checkout-embed', trailingslashit($this->base_url).'checkout-embed/assets/udp-checkout-embed.js', array('jquery'), self::$version, true);
|
||||
wp_enqueue_style($this->plugin_name.'-checkout-embed', trailingslashit($this->base_url).'checkout-embed/assets/udp-checkout-embed.css', null, self::$version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the template for the modal
|
||||
*/
|
||||
public function print_template() {
|
||||
if (is_array($this->load_in_pages)) {
|
||||
$screen = get_current_screen();
|
||||
if (!in_array($screen->base, $this->load_in_pages)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<div style="display: none;" id="udp-modal-template">
|
||||
<div class="udp-modal">
|
||||
<div class="udp-modal__overlay"></div>
|
||||
<div class="udp-modal__modal">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"updraftpremium":"https:\/\/teamupdraft.com\/product\/updraftplus\/?udp_checkout_embed=1&product_id=1247&utm_source=teamupdraft.com&utm_medium=in-plugin-purchase&utm_campaign=startup&utm_content=updraftpremium","updraftplus-vault-storage-5-gb":"https:\/\/teamupdraft.com\/product\/updraftvault\/?udp_checkout_embed=1&product_id=1431&utm_source=teamupdraft.com&utm_medium=in-plugin-purchase&utm_campaign=startup&utm_content=updraftplus-vault-storage-5-gb","updraftplus-vault-storage-15-gb":"https:\/\/teamupdraft.com\/product\/updraftvault\/?udp_checkout_embed=1&product_id=1431&utm_source=teamupdraft.com&utm_medium=in-plugin-purchase&utm_campaign=startup&utm_content=updraftplus-vault-storage-15-gb","updraftplus-vault-storage-50-gb":"https:\/\/teamupdraft.com\/product\/updraftvault\/?udp_checkout_embed=1&product_id=1431&utm_source=teamupdraft.com&utm_medium=in-plugin-purchase&utm_campaign=startup&utm_content=updraftplus-vault-storage-50-gb","updraftplus-vault-storage-250-gb":"https:\/\/teamupdraft.com\/product\/updraftvault\/?udp_checkout_embed=1&product_id=1431&utm_source=teamupdraft.com&utm_medium=in-plugin-purchase&utm_campaign=startup&utm_content=updraftplus-vault-storage-250-gb","updraftplus-clone-tokens":"https:\/\/teamupdraft.com\/product\/updraftclone-tokens\/?udp_checkout_embed=1&product_id=292349&utm_source=teamupdraft.com&utm_medium=in-plugin-purchase&utm_campaign=startup&utm_content=updraftplus-clone-tokens"}
|
||||
@@ -0,0 +1,49 @@
|
||||
# Embed the plugin's checkout page
|
||||
|
||||
## To use in a new plugin:
|
||||
|
||||
- Include and instantiate `Updraft_Checkout_Embed`
|
||||
|
||||
```php
|
||||
if (!class_exists('Updraft_Checkout_Embed')) include_once (UPDRAFTPLUS_DIR.'/includes/checkout-embed/class-udp-checkout-embed.php');
|
||||
global $udp_checkout_embed;
|
||||
$udp_checkout_embed = new Updraft_Checkout_Embed(
|
||||
'updraftplus'
|
||||
$data_url,
|
||||
$load_in_pages
|
||||
);
|
||||
```
|
||||
|
||||
### Params:
|
||||
- $plugin_name: (string) Current plugin using the class
|
||||
- $proructs_data_url: (string) url of the merchand website (eg: https://https://updraftplus.com)
|
||||
- $load_in_pages: (array) pages on which the script + css will be loaded
|
||||
|
||||
### Cache:
|
||||
The products data is cached and expires after 7 days. To force fetching it, add `udp-force-product-list-refresh=1` to the admin page url
|
||||
|
||||
## Using in the admin
|
||||
|
||||
- Once the php is setup, you can configure the links / buttons in the admin.
|
||||
|
||||
Add `data-embed-checkout="{$url}"` to any link. eg:
|
||||
|
||||
```php
|
||||
global $updraftplus_checkout_embed;
|
||||
|
||||
$link_data_attr = $updraftplus_checkout_embed->get_product('updraftpremium') ? 'data-embed-checkout="'.apply_filters('updraftplus_com_link', $updraftplus_checkout_embed->get_product('updraftpremium')).'"' : '';
|
||||
|
||||
<a target="_blank" title="Upgrade to Updraft Premium" href="<?php echo apply_filters('updraftplus_com_link', "https://updraftplus.com/shop/updraftplus-premium/");?>" <?php echo $link_data_attr; ?>><?php _e('get it here', 'updraftplus');?></a>
|
||||
```
|
||||
|
||||
- On completion (when the order is complete), the event 'udp/checkout/done' is triggered.
|
||||
- The event 'udp/checkout/close' is triggered when the user closes the modal, regardless of success.
|
||||
|
||||
Use this to do something with the data received:
|
||||
|
||||
```javascript
|
||||
$(document).on('udp/checkout/done', function(event, data, $element) {
|
||||
// ... do something with data, currently data.email and data.order_number
|
||||
// $element clicked to open the modal.
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,915 @@
|
||||
<?php
|
||||
|
||||
if (!defined('UPDRAFTPLUS_DIR')) die('No access.');
|
||||
|
||||
/**
|
||||
* A class to deal with management of backup history.
|
||||
* N.B. All database access should come through here. However, this class is not the only place with knowledge of the data structure.
|
||||
*/
|
||||
class UpdraftPlus_Backup_History {
|
||||
|
||||
private static $backup_history_on_restore = null;
|
||||
|
||||
/**
|
||||
* Get the backup history for an indicated timestamp, or the complete set of all backup histories
|
||||
*
|
||||
* @param Integer|Boolean $timestamp - Indicate a particular timestamp to get a particular backup job, or false to get a list of them all (sorted by most recent first).
|
||||
*
|
||||
* @return Array - either the particular backup indicated, or the full list.
|
||||
*/
|
||||
public static function get_history($timestamp = false) {
|
||||
|
||||
$backup_history = UpdraftPlus_Options::get_updraft_option('updraft_backup_history');
|
||||
// N.B. Doing a direct wpdb->get_var() here actually *introduces* a race condition
|
||||
|
||||
if (!is_array($backup_history)) $backup_history = array();
|
||||
|
||||
$backup_history = self::build_incremental_sets($backup_history);
|
||||
|
||||
if ($timestamp) return isset($backup_history[$timestamp]) ? $backup_history[$timestamp] : array();
|
||||
|
||||
// The most recent backup will be first. Then we can array_pop().
|
||||
krsort($backup_history);
|
||||
|
||||
return $backup_history;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add jobdata to all entries in an array of history items which do not already have it (key: 'jobdata'). If none is found, it will still be set, but empty.
|
||||
*
|
||||
* @param Array $backup_history - the list of history items
|
||||
*
|
||||
* @return Array
|
||||
*/
|
||||
public static function add_jobdata($backup_history) {
|
||||
|
||||
global $wpdb, $updraftplus;
|
||||
$table = is_multisite() ? $wpdb->sitemeta : $wpdb->options;
|
||||
$key_column = is_multisite() ? 'meta_key' : 'option_name';
|
||||
$value_column = is_multisite() ? 'meta_value' : 'option_value';
|
||||
|
||||
$any_more = true;
|
||||
|
||||
while ($any_more) {
|
||||
|
||||
$any_more = false;
|
||||
$columns = array();
|
||||
$nonces_map = array();
|
||||
|
||||
foreach ($backup_history as $timestamp => $backup) {
|
||||
if (isset($backup['jobdata'])) continue;
|
||||
$nonce = $backup['nonce'];
|
||||
$nonces_map[$nonce] = $timestamp;
|
||||
$columns[] = $nonce;
|
||||
// Approx. 2.5MB of data would be expected if they all had 5KB each (though in reality we expect very few of them to have any)
|
||||
if (count($columns) >= 500) {
|
||||
$any_more = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($columns)) break;
|
||||
|
||||
$columns_sql = '';
|
||||
foreach ($columns as $nonce) {
|
||||
if ($columns_sql) $columns_sql .= ',';
|
||||
$columns_sql .= "'updraft_jobdata_".esc_sql($nonce)."'";
|
||||
}
|
||||
|
||||
$sql = 'SELECT '.$key_column.', '.$value_column.' FROM '.$table.' WHERE '.$key_column.' IN ('.$columns_sql.')';
|
||||
$all_jobdata = $wpdb->get_results($sql);
|
||||
|
||||
foreach ($all_jobdata as $values) {
|
||||
// The 16 here is the length of 'updraft_jobdata_'
|
||||
$nonce = substr($values->$key_column, 16);
|
||||
if (empty($nonces_map[$nonce]) || empty($values->$value_column)) continue;
|
||||
$jobdata = $updraftplus->unserialize($values->$value_column);
|
||||
$backup_history[$nonces_map[$nonce]]['jobdata'] = empty($jobdata) ? array() : $jobdata;
|
||||
}
|
||||
foreach ($columns as $nonce) {
|
||||
if (!empty($nonces_map[$nonce]) && !isset($backup_history[$nonces_map[$nonce]]['jobdata'])) $backup_history[$nonces_map[$nonce]]['jobdata'] = array();
|
||||
}
|
||||
}
|
||||
|
||||
return $backup_history;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the backup history for an indicated nonce
|
||||
*
|
||||
* @param String $nonce - Backup nonce to get a particular backup job
|
||||
*
|
||||
* @return Array|Boolean - either the particular backup indicated, or false
|
||||
*/
|
||||
public static function get_backup_set_by_nonce($nonce) {
|
||||
if (empty($nonce)) return false;
|
||||
$backup_history = self::get_history();
|
||||
foreach ($backup_history as $timestamp => $backup_info) {
|
||||
if ($nonce == $backup_info['nonce']) {
|
||||
$backup_info['timestamp'] = $timestamp;
|
||||
return $backup_info;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HTML for the table of existing backups
|
||||
*
|
||||
* @param Array|Boolean $backup_history - a list of backups to use, or false to get the current list from the database
|
||||
* @param Boolean $backup_count - the amount of backups currently displayed in the existing backups table
|
||||
*
|
||||
* @uses UpdraftPlus_Admin::include_template()
|
||||
*
|
||||
* @return String - HTML for the table
|
||||
*/
|
||||
public static function existing_backup_table($backup_history = false, $backup_count = 0) {
|
||||
|
||||
global $updraftplus, $updraftplus_admin;
|
||||
|
||||
if (false === $backup_history) $backup_history = self::get_history();
|
||||
|
||||
if (!is_array($backup_history) || empty($backup_history)) return '<div class="postbox"><p class="updraft-no-backups-msg">'.__('You have not yet made any backups.', 'updraftplus').'</p> <p class="updraft-no-backups-msg">'.__('If you have an existing backup that you wish to upload and restore from, then please use the "Upload backup files" link above.', 'updraftplus').' '.__('Or, if they are in remote storage, you can connect that remote storage (in the "Settings" tab), save your settings, and use the "Rescan remote storage" link.', 'updraftplus').'</p></div>';
|
||||
|
||||
if (empty($backup_count)) {
|
||||
$backup_count = defined('UPDRAFTPLUS_EXISTING_BACKUPS_LIMIT') ? UPDRAFTPLUS_EXISTING_BACKUPS_LIMIT : 100;
|
||||
}
|
||||
|
||||
// Reverse date sort - i.e. most recent first
|
||||
krsort($backup_history);
|
||||
|
||||
$pass_values = array(
|
||||
'backup_history' => self::add_jobdata($backup_history),
|
||||
'updraft_dir' => $updraftplus->backups_dir_location(),
|
||||
'backupable_entities' => $updraftplus->get_backupable_file_entities(true, true),
|
||||
'backup_count' => $backup_count,
|
||||
'show_paging_actions' => false,
|
||||
);
|
||||
|
||||
return $updraftplus_admin->include_template('wp-admin/settings/existing-backups-table.php', true, $pass_values);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will scan the backup history and split the files up in to incremental sets, foreign backup sets will only have one incremental set.
|
||||
*
|
||||
* @param Array $backup_history - the saved backup history
|
||||
*
|
||||
* @return Array - returns the backup history but also includes the incremental sets
|
||||
*/
|
||||
public static function build_incremental_sets($backup_history) {
|
||||
|
||||
global $updraftplus;
|
||||
|
||||
$backupable_entities = array_keys($updraftplus->get_backupable_file_entities(true, false));
|
||||
|
||||
$accept = apply_filters('updraftplus_accept_archivename', array());
|
||||
|
||||
foreach ($backup_history as $btime => $bdata) {
|
||||
|
||||
$incremental_sets = array();
|
||||
|
||||
foreach ($backupable_entities as $entity) {
|
||||
|
||||
if (empty($bdata[$entity]) || !is_array($bdata[$entity])) continue;
|
||||
|
||||
foreach ($bdata[$entity] as $key => $filename) {
|
||||
|
||||
if (preg_match('/^backup_([\-0-9]{15})_.*_([0-9a-f]{12})-[\-a-z]+([0-9]+)?+(\.(zip|gz|gz\.crypt))?$/i', $filename, $matches)) {
|
||||
|
||||
$timestamp = strtotime(get_gmt_from_date(date_format(DateTime::createFromFormat('Y-m-d-Hi', $matches[1]), 'Y-m-d H:i')));
|
||||
|
||||
if (!isset($incremental_sets[$timestamp])) $incremental_sets[$timestamp] = array();
|
||||
|
||||
if (!isset($incremental_sets[$timestamp][$entity])) $incremental_sets[$timestamp][$entity] = array();
|
||||
|
||||
$incremental_sets[$timestamp][$entity][$key] = $filename;
|
||||
} else {
|
||||
$accepted = false;
|
||||
|
||||
foreach ($accept as $fkey => $acc) {
|
||||
if (preg_match('/'.$acc['pattern'].'/i', $filename)) $accepted = $fkey;
|
||||
}
|
||||
|
||||
if (!empty($accepted) && (false != ($btime = apply_filters('updraftplus_foreign_gettime', false, $accepted, $filename))) && $btime > 0) {
|
||||
|
||||
$timestamp = $btime;
|
||||
|
||||
if (!isset($incremental_sets[$timestamp])) $incremental_sets[$timestamp] = array();
|
||||
|
||||
if (!isset($incremental_sets[$timestamp][$entity])) $incremental_sets[$timestamp][$entity] = array();
|
||||
|
||||
$incremental_sets[$timestamp][$entity][] = $filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ksort($incremental_sets);
|
||||
$backup_history[$btime]["incremental_sets"] = $incremental_sets;
|
||||
}
|
||||
|
||||
return $backup_history;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the backup history. An abstraction function to make future changes easier.
|
||||
*
|
||||
* @param Array $backup_history - the backup history
|
||||
* @param Boolean $use_cache - whether or not to use the WP options cache
|
||||
*/
|
||||
public static function save_history($backup_history, $use_cache = true) {
|
||||
|
||||
global $updraftplus, $wpdb;
|
||||
|
||||
// This data is constructed at run-time from the other keys; we do not wish to save redundant data
|
||||
foreach ($backup_history as $btime => $bdata) {
|
||||
unset($backup_history[$btime]['incremental_sets']);
|
||||
}
|
||||
|
||||
$wpdb_previous_last_error = $wpdb->last_error;
|
||||
|
||||
// Explicitly set autoload to 'no', as the backup history can get quite big.
|
||||
$changed = UpdraftPlus_Options::update_updraft_option('updraft_backup_history', $backup_history, $use_cache, 'no');
|
||||
|
||||
if (!$changed && '' !== $wpdb->last_error && $wpdb_previous_last_error != $wpdb->last_error) {
|
||||
// if an error occurred, there is a possibility if this error is caused by invalid characters found in 'label'
|
||||
foreach ($backup_history as $btime => $bdata) {
|
||||
if (isset($bdata['label'])) {
|
||||
// try removing invalid characters from 'label'
|
||||
if (method_exists($wpdb, 'strip_invalid_text_for_column')) {
|
||||
$backup_history[$btime]['label'] = $wpdb->strip_invalid_text_for_column($wpdb->options, 'option_value', $backup_history[$btime]['label']);
|
||||
} else {
|
||||
|
||||
// This replacement, may of course, drop parts of the label. This is judged to be better than dropping it all - and WPDB::strip_invalid_text_for_column() exists on WP 4.2+.
|
||||
$backup_history[$btime]['label'] = preg_replace('/[^a-z0-9-_ ]/i', '', $backup_history[$btime]['label']);
|
||||
}
|
||||
|
||||
if ('' === $backup_history[$btime]['label']) {
|
||||
unset($backup_history[$btime]['label']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// try to save it again
|
||||
$changed = UpdraftPlus_Options::update_updraft_option('updraft_backup_history', $backup_history, $use_cache, 'no');
|
||||
}
|
||||
|
||||
if (!$changed) {
|
||||
|
||||
$max_packet_size = $updraftplus->max_packet_size(false, false);
|
||||
$serialization_size = strlen(addslashes(serialize($backup_history)));
|
||||
|
||||
// Take off the *approximate* over-head of UPDATE wp_options SET option_value='' WHERE option_name='updraft_backup_history'; (no need to be exact)
|
||||
if ($max_packet_size < ($serialization_size + 100)) {
|
||||
|
||||
$max_packet_size = $updraftplus->max_packet_size();
|
||||
|
||||
$changed = UpdraftPlus_Options::update_updraft_option('updraft_backup_history', $backup_history, $use_cache, 'no');
|
||||
|
||||
if (!$changed) {
|
||||
|
||||
$updraftplus->log('The attempt to write the backup history to the WP database returned a negative code and the max packet size looked small. However, WP does not distinguish between a failure and no change from a previous update, so, this code is not conclusive and if no other symptoms are observed then there is no reason to infer any problem. Info: The updated database packet size is '.$max_packet_size.'; the serialization size is '.$serialization_size);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by self::always_get_from_db()
|
||||
*
|
||||
* @return Mixed - the database option
|
||||
*/
|
||||
public static function filter_updraft_backup_history() {
|
||||
global $wpdb, $updraftplus;
|
||||
$row = $wpdb->get_row($wpdb->prepare("SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", 'updraft_backup_history'));
|
||||
if (is_object($row) && !empty($row->option_value)) return $updraftplus->unserialize($row->option_value);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure we get the value afresh from the db, instead of using the auto-loaded/cached value (which can be out of date, especially since backups are, by their nature, long-running)
|
||||
*/
|
||||
public static function always_get_from_db() {
|
||||
add_filter('pre_option_updraft_backup_history', array('UpdraftPlus_Backup_History', 'filter_updraft_backup_history'));
|
||||
}
|
||||
|
||||
/**
|
||||
* This function examines inside the updraft directory to see if any new archives have been uploaded. If so, it adds them to the backup set. (Non-present items are also removed, only if the service is 'none').
|
||||
*
|
||||
* N.B. The logic is a bit more subtle than it needs to be, because of backups being keyed by backup time, instead of backup nonce, and the subsequent introduction of the possibility of incremental backup sets taken at different times. This could be cleaned up to reduce the amount of code and make it simpler in places.
|
||||
*
|
||||
* @param Boolean $remote_scan - scan not only local, but also remote storage
|
||||
* @param Array|String $only_add_this_file - if set to an array (with keys 'file' and (optionally) 'label'), then a file will only be taken notice of if the filename matches the 'file' key (and the label will be associated with the backup set)
|
||||
* @param Boolean $debug - include debugging messages. These will be keyed with keys beginning 'debug-' so that they can be distinguished.
|
||||
*
|
||||
* @return Array - an array of messages which the caller may wish to display to the user. N.B. Messages are not necessarily just strings, but may be.
|
||||
*/
|
||||
public static function rebuild($remote_scan = false, $only_add_this_file = false, $debug = false) {
|
||||
|
||||
global $updraftplus;
|
||||
|
||||
$messages = array();
|
||||
$gmt_offset = get_option('gmt_offset');
|
||||
|
||||
// Array of nonces keyed by filename
|
||||
$backup_nonces_by_filename = array();
|
||||
// Array of backup times keyed by nonce
|
||||
$backup_times_by_nonce = array();
|
||||
|
||||
$changes = false;
|
||||
|
||||
$backupable_entities = $updraftplus->get_backupable_file_entities(true, false);
|
||||
|
||||
$backup_history = self::get_history();
|
||||
|
||||
$updraft_dir = $updraftplus->backups_dir_location();
|
||||
if (!is_dir($updraft_dir)) return array("Internal directory path does not indicate a directory ($updraft_dir)");
|
||||
|
||||
$accept = apply_filters('updraftplus_accept_archivename', array());
|
||||
|
||||
// First, process the database backup history to get a record of what is already known there. This means populating the arrays $backup_nonces_by_filename and $backup_times_by_nonce
|
||||
foreach ($backup_history as $btime => $bdata) {
|
||||
$found_file = false;
|
||||
foreach ($bdata as $key => $values) {
|
||||
// make sure we also handle multiple databases, which has a different array structure compared to other entities (e.g. plugins, themes, etc.)
|
||||
// we don't do strict comparison using identical operator (===) here because we want to catch boolean false or a non-boolean value which evaluates to false, hence we use equal operator (==)
|
||||
if (false == preg_match('/^db[0-9]*$/i', $key) && !isset($backupable_entities[$key])) continue;
|
||||
// Record which set this file is found in
|
||||
if (!is_array($values)) $values = array($values);
|
||||
foreach ($values as $filename) {
|
||||
if (!is_string($filename)) continue;
|
||||
if (preg_match('/^backup_([\-0-9]{15})_.*_([0-9a-f]{12})-[\-a-z]+([0-9]+)?+(\.(zip|gz|gz\.crypt))?$/i', $filename, $matches)) {
|
||||
$nonce = $matches[2];
|
||||
if (isset($bdata['service']) && ('none' === $bdata['service'] || (is_array($bdata['service']) && (array('none') === $bdata['service'] || (1 == count($bdata['service']) && isset($bdata['service'][0]) && empty($bdata['service'][0]))))) && !is_file($updraft_dir.'/'.$filename)) {
|
||||
// File without remote storage is no longer locally present
|
||||
} else {
|
||||
$found_file = true;
|
||||
$backup_nonces_by_filename[$filename] = $nonce;
|
||||
if (empty($backup_times_by_nonce[$nonce]) || $backup_times_by_nonce[$nonce] < 100) {
|
||||
$backup_times_by_nonce[$nonce] = $btime;
|
||||
} elseif ($btime < $backup_times_by_nonce[$nonce]) {
|
||||
$backup_times_by_nonce[$nonce] = $btime;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$accepted = false;
|
||||
foreach ($accept as $fkey => $acc) {
|
||||
if (preg_match('/'.$acc['pattern'].'/i', $filename)) $accepted = $fkey;
|
||||
}
|
||||
if (!empty($accepted) && (false != ($btime = apply_filters('updraftplus_foreign_gettime', false, $accepted, $filename))) && $btime > 0) {
|
||||
$found_file = true;
|
||||
// Generate a nonce; this needs to be deterministic and based on the filename only
|
||||
$nonce = substr(md5($filename), 0, 12);
|
||||
$backup_nonces_by_filename[$filename] = $nonce;
|
||||
if (empty($backup_times_by_nonce[$nonce]) || $backup_times_by_nonce[$nonce] < 100) {
|
||||
$backup_times_by_nonce[$nonce] = $btime;
|
||||
} elseif ($btime < $backup_times_by_nonce[$nonce]) {
|
||||
$backup_times_by_nonce[$nonce] = $btime;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$found_file) {
|
||||
// File recorded as being without remote storage is no longer present. It may in fact exist in remote storage, and this will be picked up later (when we scan the remote storage).
|
||||
unset($backup_history[$btime]);
|
||||
$changes = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Secondly, scan remote storage and get back lists of files and their sizes
|
||||
|
||||
// $remote_files has a key for each filename (basename), and the value is a list of remote destinations (e.g. 'dropbox', 's3') in which the file was found
|
||||
$remote_files = array();
|
||||
|
||||
// A list of nonces found remotely (to help with handling sets split across destinations)
|
||||
$remote_nonces_found = array();
|
||||
|
||||
$remote_sizes = array();
|
||||
|
||||
if ($remote_scan) {
|
||||
|
||||
$updraftplus->register_wp_http_option_hooks(true);
|
||||
|
||||
$storage_objects_and_ids = UpdraftPlus_Storage_Methods_Interface::get_storage_objects_and_ids(array_keys($updraftplus->backup_methods));
|
||||
|
||||
foreach ($storage_objects_and_ids as $method => $method_information) {
|
||||
|
||||
$object = $method_information['object'];
|
||||
|
||||
if (!method_exists($object, 'listfiles')) continue;
|
||||
|
||||
// Support of multi_options is now required for storage methods that implement listfiles()
|
||||
if (!$object->supports_feature('multi_options')) {
|
||||
error_log("UpdraftPlus: Multi-options not supported by: ".$method);
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($method_information['instance_settings'] as $instance_id => $options) {
|
||||
|
||||
$object->set_options($options, false, $instance_id);
|
||||
|
||||
$files = $object->listfiles('backup_');
|
||||
|
||||
$method_description = $object->get_description();
|
||||
|
||||
if (is_array($files)) {
|
||||
|
||||
if ($debug) {
|
||||
$messages[] = array(
|
||||
'method' => $method,
|
||||
'desc' => $method_description,
|
||||
'code' => 'file-listing',
|
||||
'message' => '',
|
||||
'data' => $files,
|
||||
'service_instance_id' => $instance_id,
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($files as $entry) {
|
||||
$filename = $entry['name'];
|
||||
if (!preg_match('/^backup_([\-0-9]{15})_.*_([0-9a-f]{12})-([\-a-z]+)([0-9]+)?(\.(zip|gz|gz\.crypt))?$/i', $filename, $matches)) continue;
|
||||
|
||||
$nonce = $matches[2];
|
||||
$btime = strtotime($matches[1]);
|
||||
// Of course, it's possible that the site doing the scanning has a different timezone from the site that the backups were created in, in which case, this calculation will have a confusing result to the user. That outcome cannot be completely eliminated (without making the filename to reflect UTC, which confuses more users).
|
||||
if (!empty($gmt_offset)) $btime -= $gmt_offset * 3600;
|
||||
|
||||
// Is the set already known?
|
||||
if (isset($backup_times_by_nonce[$nonce])) {
|
||||
// N.B. With an incremental set, the newly found file may be earlier than the known elements, so tha the backup array should be re-keyed.
|
||||
$btime_exact = $backup_times_by_nonce[$nonce];
|
||||
if ($btime > 100 && $btime_exact - $btime > 60 && !empty($backup_history[$btime_exact])) {
|
||||
$changes = true;
|
||||
$backup_history[$btime] = $backup_history[$btime_exact];
|
||||
unset($backup_history[$btime_exact]);
|
||||
$btime_exact = $btime;
|
||||
$backup_times_by_nonce[$nonce] = $btime;
|
||||
}
|
||||
$btime = $btime_exact;
|
||||
}
|
||||
if ($btime <= 100) continue;
|
||||
|
||||
// We need to set this so that if a second file is found in remote storage then the time will be picked up.
|
||||
$backup_times_by_nonce[$nonce] = $btime;
|
||||
|
||||
if (empty($backup_history[$btime]['service_instance_ids']) || empty($backup_history[$btime]['service_instance_ids'][$method])) {
|
||||
$backup_history[$btime]['service_instance_ids'][$method] = array($instance_id);
|
||||
$changes = true;
|
||||
} elseif (!in_array($instance_id, $backup_history[$btime]['service_instance_ids'][$method])) {
|
||||
$backup_history[$btime]['service_instance_ids'][$method][] = $instance_id;
|
||||
$changes = true;
|
||||
}
|
||||
|
||||
if (isset($remote_files[$filename])) {
|
||||
$remote_files[$filename][] = $method;
|
||||
} else {
|
||||
$remote_files[$filename] = array($method);
|
||||
}
|
||||
|
||||
if (!in_array($nonce, $remote_nonces_found)) $remote_nonces_found[] = $nonce;
|
||||
|
||||
if (!empty($entry['size'])) {
|
||||
if (empty($remote_sizes[$filename]) || $remote_sizes[$filename] < $entry['size']) $remote_sizes[$filename] = $entry['size'];
|
||||
}
|
||||
}
|
||||
} elseif (is_wp_error($files)) {
|
||||
foreach ($files->get_error_codes() as $code) {
|
||||
// Skip various codes which are not conditions to show to the user
|
||||
if (in_array($code, array('no_settings', 'no_addon', 'insufficient_php', 'no_listing'))) continue;
|
||||
$messages[] = array(
|
||||
'method' => $method,
|
||||
'desc' => $method_description,
|
||||
'code' => $code,
|
||||
'message' => $files->get_error_message($code),
|
||||
'data' => $files->get_error_data($code),
|
||||
'service_instance_id' => $instance_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$updraftplus->register_wp_http_option_hooks(false);
|
||||
}
|
||||
|
||||
// Thirdly, see if there are any more files in the local directory than the ones already known about (possibly subject to a limitation specified via $only_add_this_file)
|
||||
if (!$handle = opendir($updraft_dir)) return array("Failed to open the internal directory ($updraft_dir)");
|
||||
|
||||
while (false !== ($entry = readdir($handle))) {
|
||||
|
||||
if ('.' == $entry || '..' == $entry) continue;
|
||||
|
||||
$accepted_foreign = false;
|
||||
$potmessage = false;
|
||||
|
||||
if (false !== $only_add_this_file && $entry != $only_add_this_file['file']) continue;
|
||||
|
||||
if (preg_match('/^backup_([\-0-9]{15})_.*_([0-9a-f]{12})-([\-a-z]+)([0-9]+)?(\.(zip|gz|gz\.crypt))?$/i', $entry, $matches)) {
|
||||
// Interpret the time as one from the blog's local timezone, rather than as UTC
|
||||
// $matches[1] is YYYY-MM-DD-HHmm, to be interpreted as being the local timezone
|
||||
$btime = strtotime($matches[1]);
|
||||
if (!empty($gmt_offset)) $btime -= $gmt_offset * 3600;
|
||||
$nonce = $matches[2];
|
||||
$type = $matches[3];
|
||||
if ('db' == $type) {
|
||||
$type .= empty($matches[4]) ? '' : $matches[4];
|
||||
$index = 0;
|
||||
} else {
|
||||
$index = empty($matches[4]) ? '0' : max((int) $matches[4]-1, 0);
|
||||
}
|
||||
$itext = (0 == $index) ? '' : $index;
|
||||
} elseif (false != ($accepted_foreign = apply_filters('updraftplus_accept_foreign', false, $entry)) && false !== ($btime = apply_filters('updraftplus_foreign_gettime', false, $accepted_foreign, $entry))) {
|
||||
$nonce = substr(md5($entry), 0, 12);
|
||||
$type = (preg_match('/\.sql(\.(bz2|gz))?$/i', $entry) || preg_match('/-database-([-0-9]+)\.zip$/i', $entry) || preg_match('/backup_db_/', $entry)) ? 'db' : 'wpcore';
|
||||
$index = apply_filters('updraftplus_accepted_foreign_index', 0, $entry, $accepted_foreign);
|
||||
$itext = $index ? $index : '';
|
||||
$potmessage = array(
|
||||
'code' => 'foundforeign_'.md5($entry),
|
||||
'desc' => $entry,
|
||||
'method' => '',
|
||||
'message' => sprintf(__('Backup created by: %s.', 'updraftplus'), $accept[$accepted_foreign]['desc'])
|
||||
);
|
||||
} elseif ('.zip' == strtolower(substr($entry, -4, 4)) || preg_match('/\.sql(\.(bz2|gz))?$/i', $entry)) {
|
||||
$potmessage = array(
|
||||
'code' => 'possibleforeign_'.md5($entry),
|
||||
'desc' => $entry,
|
||||
'method' => '',
|
||||
'message' => __('This file does not appear to be an UpdraftPlus backup archive (such files are .zip or .gz files which have a name like: backup_(time)_(site name)_(code)_(type).(zip|gz)).', 'updraftplus').' <a href="'.$updraftplus->get_url('premium').'" target="_blank">'.__('If this is a backup created by a different backup plugin, then UpdraftPlus Premium may be able to help you.', 'updraftplus').'</a>'
|
||||
);
|
||||
$messages[$potmessage['code']] = $potmessage;
|
||||
continue;
|
||||
} else {
|
||||
// The filename pattern does not indicate any sort of backup archive
|
||||
continue;
|
||||
}
|
||||
|
||||
// The time from the filename does not include seconds. We need to identify the seconds to get the right time for storing it.
|
||||
if (isset($backup_times_by_nonce[$nonce])) {
|
||||
$btime_exact = $backup_times_by_nonce[$nonce];
|
||||
// If the btime we had was more than 60 seconds earlier, then this must be an increment - we then need to change the $backup_history array accordingly. We can pad the '60 second' test, as there's no option to run an increment more frequently than every 4 hours (though someone could run one manually from the CLI)
|
||||
if ($btime > 100 && $btime_exact - $btime > 60 && !empty($backup_history[$btime_exact])) {
|
||||
$changes = true;
|
||||
// We assume that $backup_history[$btime] is presently empty (except that the 'service_instance_ids' key may have been set earlier
|
||||
// Re-key array, indicating the newly-found time to be the start of the backup set
|
||||
$merge_services = false;
|
||||
if (!empty($backup_history[$btime]['service_instance_ids'])) {
|
||||
$merge_services = $backup_history[$btime]['service_instance_ids'];
|
||||
}
|
||||
$backup_history[$btime] = $backup_history[$btime_exact];
|
||||
if (is_array($merge_services)) {
|
||||
if (empty($backup_history[$btime]['service_instance_ids'])) {
|
||||
$backup_history[$btime]['service_instance_ids'] = $merge_services;
|
||||
} else {
|
||||
foreach ($merge_services as $service => $instance_ids) {
|
||||
if (empty($backup_history[$btime]['service_instance_ids'][$service])) {
|
||||
$backup_history[$btime]['service_instance_ids'][$service] = $instance_ids;
|
||||
} else {
|
||||
$backup_history[$btime]['service_instance_ids'][$service] = array_unique(array_merge($backup_history[$btime]['service_instance_ids'][$service], $instance_ids));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($backup_history[$btime_exact]);
|
||||
$backup_times_by_nonce[$nonce] = $btime;
|
||||
$btime_exact = $btime;
|
||||
}
|
||||
$btime = $btime_exact;
|
||||
} else {
|
||||
$backup_times_by_nonce[$nonce] = $btime;
|
||||
}
|
||||
if ($btime <= 100) continue;
|
||||
$file_size = @filesize($updraft_dir.'/'.$entry);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
|
||||
|
||||
if (!isset($backup_nonces_by_filename[$entry])) {
|
||||
$changes = true;
|
||||
if (is_array($potmessage)) $messages[$potmessage['code']] = $potmessage;
|
||||
if (is_array($only_add_this_file)) {
|
||||
if (isset($only_add_this_file['label'])) $backup_history[$btime]['label'] = $only_add_this_file['label'];
|
||||
$backup_history[$btime]['native'] = false;
|
||||
} elseif ('db' == $type && !$accepted_foreign) {
|
||||
// we now that multiple databases will add its index number after the 'db' (e.g. 'db1'), however, the $type == 'db' here has nothing to do with our multiple databases addon because this block of code inside the 'if (!isset($backup_nonces_by_filename[$entry]))' will never be executed if multiple databases is found to be in the backup history and that our backup file pattern matches with them, so this is not the place where we should check for our multiple databases backup file, this is instead the place for handling foreign databases (e.g. Backup Buddy and our other competitors). The $type were previously set to 'db' when its file was found to be a foreign database
|
||||
list ($mess, $warn, $err, $info) = $updraftplus->analyse_db_file(false, array(), $updraft_dir.'/'.$entry, true);// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Unused parameter is present because the method returns an array.
|
||||
if (!empty($info['label'])) {
|
||||
$backup_history[$btime]['label'] = $info['label'];
|
||||
}
|
||||
if (!empty($info['created_by_version'])) {
|
||||
$backup_history[$btime]['created_by_version'] = $info['created_by_version'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we have the right list of services, as an array
|
||||
$current_services = (!empty($backup_history[$btime]) && !empty($backup_history[$btime]['service'])) ? $backup_history[$btime]['service'] : array();
|
||||
if (is_string($current_services)) $current_services = array($current_services);
|
||||
if (!is_array($current_services)) $current_services = array();
|
||||
foreach ($current_services as $k => $v) {
|
||||
if ('none' === $v || '' == $v) unset($current_services[$k]);
|
||||
}
|
||||
|
||||
// If the file (the one we just found locally) was also found in the scan of remote storage...
|
||||
if (!empty($remote_files[$entry])) {
|
||||
// ... store the record if all services which previously stored it still do
|
||||
if (0 == count(array_diff($current_services, $remote_files[$entry]))) {
|
||||
if ($current_services != $remote_files[$entry]) $changes = true;
|
||||
$backup_history[$btime]['service'] = $remote_files[$entry];
|
||||
} else {
|
||||
// There are services in $current_services which are not in $remote_files[$entry]
|
||||
// This can be because the backup set files are split across different services
|
||||
$changes = true;
|
||||
$backup_history[$btime]['service'] = array_unique(array_merge($current_services, $remote_files[$entry]));
|
||||
}
|
||||
// Get the right size (our local copy may be too small)
|
||||
if (!empty($remote_sizes[$entry]) && $remote_sizes[$entry] > $file_size) {
|
||||
$file_size = $remote_sizes[$entry];
|
||||
$changes = true;
|
||||
}
|
||||
// Remove from $remote_files, so that we can later see what was left over (i.e. $remote_files will exclude files which are present locally).
|
||||
unset($remote_files[$entry]);
|
||||
|
||||
} elseif ($remote_scan && !in_array($nonce, $remote_nonces_found)) {
|
||||
// The file is not known remotely, and neither is any other from the same set, and a remote scan was done
|
||||
|
||||
if (!empty($backup_history[$btime])) {
|
||||
if (empty($backup_history[$btime]['service']) || ('none' !== $backup_history[$btime]['service'] && '' !== $backup_history[$btime]['service'] && array('none') !== $backup_history[$btime]['service'])) {
|
||||
$backup_history[$btime]['service'] = 'none';
|
||||
$changes = true;
|
||||
}
|
||||
} else {
|
||||
$backup_history[$btime]['service'] = 'none';
|
||||
$changes = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (preg_match('/^db[0-9]*$/i', $type)) { // make sure we also handle multiple databases, which has a bit different array structure
|
||||
$backup_history[$btime][$type] = $entry; // $backup_history[$btime][$type] is in string type not array
|
||||
} else {
|
||||
$backup_history[$btime][$type][$index] = $entry;
|
||||
}
|
||||
|
||||
if (!empty($backup_history[$btime][$type.$itext.'-size']) && $backup_history[$btime][$type.$itext.'-size'] < $file_size) {
|
||||
$backup_history[$btime][$type.$itext.'-size'] = $file_size;
|
||||
$changes = true;
|
||||
} elseif (empty($backup_history[$btime][$type.$itext.'-size']) && $file_size > 0) {
|
||||
$backup_history[$btime][$type.$itext.'-size'] = $file_size;
|
||||
$changes = true;
|
||||
}
|
||||
|
||||
$backup_history[$btime]['nonce'] = $nonce;
|
||||
if (!empty($accepted_foreign)) $backup_history[$btime]['meta_foreign'] = $accepted_foreign;
|
||||
}
|
||||
|
||||
// Fourthly: are there any files found in remote storage that are not stored locally?
|
||||
// If so, then we compare $remote_files with $backup_nonces_by_filename / $backup_times_by_nonce, and adjust $backup_history
|
||||
|
||||
foreach ($remote_files as $file => $services) {
|
||||
if (!preg_match('/^backup_([\-0-9]{15})_.*_([0-9a-f]{12})-([\-a-z]+)([0-9]+)?(\.(zip|gz|gz\.crypt))?$/i', $file, $matches)) continue;
|
||||
|
||||
$nonce = $matches[2];
|
||||
$type = $matches[3];
|
||||
|
||||
if ('db' == $type) {
|
||||
$index = 0;
|
||||
$type .= !empty($matches[4]) ? $matches[4] : '';
|
||||
} else {
|
||||
$index = empty($matches[4]) ? '0' : max((int) $matches[4]-1, 0);
|
||||
}
|
||||
|
||||
$itext = (0 == $index) ? '' : $index;
|
||||
$btime = strtotime($matches[1]);
|
||||
if (!empty($gmt_offset)) $btime -= $gmt_offset * 3600;
|
||||
|
||||
// N.B. We don't need to check if the backup set needs re-keying by an earlier time here, because that was already done when processing the whole list of remote files, above.
|
||||
if (isset($backup_times_by_nonce[$nonce])) $btime = $backup_times_by_nonce[$nonce];
|
||||
|
||||
if ($btime <= 100) continue;
|
||||
|
||||
// Remember that at this point, we already know that the file is not stored locally (else it would have been pruned earlier from $remote_files)
|
||||
// The check for a new set needs to take into account that $backup_history[$btime]['service_instance_ids'] may have been created further up this method
|
||||
if (isset($backup_history[$btime]) && array('service_instance_ids') !== array_keys($backup_history[$btime])) {
|
||||
if (!isset($backup_history[$btime]['service']) || (is_array($backup_history[$btime]['service']) && $backup_history[$btime]['service'] !== $services) || (is_string($backup_history[$btime]['service']) && (1 != count($services) || $services[0] !== $backup_history[$btime]['service']))) {
|
||||
$changes = true;
|
||||
if (isset($backup_history[$btime]['service'])) {
|
||||
$existing_services = is_array($backup_history[$btime]['service']) ? $backup_history[$btime]['service'] : array($backup_history[$btime]['service']);
|
||||
$backup_history[$btime]['service'] = array_unique(array_merge($services, $existing_services));
|
||||
foreach ($backup_history[$btime]['service'] as $k => $v) {
|
||||
if ('none' === $v || '' == $v) unset($backup_history[$btime]['service'][$k]);
|
||||
}
|
||||
} else {
|
||||
$backup_history[$btime]['service'] = $services;
|
||||
}
|
||||
$backup_history[$btime]['nonce'] = $nonce;
|
||||
}
|
||||
|
||||
if (!isset($backup_history[$btime][$type]) || (!preg_match('/^db[0-9]*$/i', $type) && !isset($backup_history[$btime][$type][$index]))) {
|
||||
$changes = true;
|
||||
if (preg_match('/^db[0-9]*$/i', $type)) {
|
||||
$backup_history[$btime][$type] = $file;
|
||||
} else {
|
||||
$backup_history[$btime][$type][$index] = $file;
|
||||
}
|
||||
$backup_history[$btime]['nonce'] = $nonce;
|
||||
if (!empty($remote_sizes[$file])) $backup_history[$btime][$type.$itext.'-size'] = $remote_sizes[$file];
|
||||
}
|
||||
} else {
|
||||
$changes = true;
|
||||
$backup_history[$btime]['service'] = $services;
|
||||
if (preg_match('/^db[0-9]*$/i', $type)) {
|
||||
$backup_history[$btime][$type] = $file;
|
||||
} else {
|
||||
$backup_history[$btime][$type][$index] = $file;
|
||||
}
|
||||
$backup_history[$btime]['nonce'] = $nonce;
|
||||
if (!empty($remote_sizes[$file])) $backup_history[$btime][$type.$itext.'-size'] = $remote_sizes[$file];
|
||||
$backup_history[$btime]['native'] = false;
|
||||
$messages['nonnative'] = array(
|
||||
'message' => __('One or more backups has been added from scanning remote storage; note that these backups will not be automatically deleted through the "retain" settings; if/when you wish to delete them then you must do so manually.', 'updraftplus'),
|
||||
'code' => 'nonnative',
|
||||
'desc' => '',
|
||||
'method' => ''
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// This is for consistency - if something is no longer present in the service list, then neither should it be in the ids list
|
||||
foreach ($backup_history as $btime => $bdata) {
|
||||
if (!empty($bdata['service_instance_ids'])) {
|
||||
foreach ($bdata['service_instance_ids'] as $method => $instance_ids) {
|
||||
if ((is_array($bdata['service']) && !in_array($method, $bdata['service'])) || (is_string($bdata['service']) && $method !== $bdata['service'])) {
|
||||
unset($backup_history[$btime]['service_instance_ids'][$method]);
|
||||
$changes = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$more_backup_history = apply_filters('updraftplus_more_rebuild', $backup_history);
|
||||
|
||||
if ($more_backup_history) {
|
||||
$backup_history = $more_backup_history;
|
||||
$changes = true;
|
||||
}
|
||||
|
||||
if ($changes) self::save_history($backup_history);
|
||||
|
||||
return $messages;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will look through the backup history and return the nonce of the latest full backup that has everything that is set in the UpdraftPlus settings to be backed up (this will exclude full backups sent to another site, e.g. for a migration or clone)
|
||||
*
|
||||
* @return String - the backup nonce of a full backup or an empty string if none are found
|
||||
*/
|
||||
public static function get_latest_full_backup() {
|
||||
|
||||
$backup_history = self::get_history();
|
||||
|
||||
global $updraftplus;
|
||||
|
||||
$backupable_entities = $updraftplus->get_backupable_file_entities(true, true);
|
||||
|
||||
foreach ($backupable_entities as $key => $info) {
|
||||
if (!UpdraftPlus_Options::get_updraft_option("updraft_include_$key", false)) {
|
||||
unset($backupable_entities[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($backup_history as $key => $backup) {
|
||||
|
||||
$remote_sent = !empty($backup['service']) && ((is_array($backup['service']) && in_array('remotesend', $backup['service'])) || 'remotesend' === $backup['service']);
|
||||
if ($remote_sent) continue;
|
||||
|
||||
foreach ($backupable_entities as $key => $info) {
|
||||
if (!isset($backup[$key])) continue 2;
|
||||
}
|
||||
|
||||
return $backup['nonce'];
|
||||
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will look through the backup history and return the nonce of the latest backup that can be used for an incremental backup (this will exclude full backups sent to another site, e.g. for a migration or clone)
|
||||
*
|
||||
* @param array $entities - an array of file entities that are included in this job
|
||||
*
|
||||
* @return String - the backup nonce of a full backup or an empty string if none are found
|
||||
*/
|
||||
public static function get_latest_backup($entities) {
|
||||
|
||||
if (empty($entities)) return '';
|
||||
|
||||
$backup_history = self::get_history();
|
||||
|
||||
foreach ($backup_history as $backup) {
|
||||
|
||||
$remote_sent = !empty($backup['service']) && ((is_array($backup['service']) && in_array('remotesend', $backup['service'])) || 'remotesend' === $backup['service']);
|
||||
if ($remote_sent) continue;
|
||||
|
||||
// We don't want to add an increment to a backup of another site
|
||||
if (isset($backup['native']) && false == $backup['native']) continue;
|
||||
|
||||
foreach ($entities as $type) {
|
||||
if (!isset($backup[$type])) continue 2;
|
||||
}
|
||||
|
||||
return $backup['nonce'];
|
||||
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will look through the backup history and return an array of entity types found in the history
|
||||
*
|
||||
* @return array - an array of backup entities found in the history or an empty array if there are none
|
||||
*/
|
||||
public static function get_existing_backup_entities() {
|
||||
|
||||
$backup_history = self::get_history();
|
||||
|
||||
global $updraftplus;
|
||||
|
||||
$backupable_entities = $updraftplus->get_backupable_file_entities(true, true);
|
||||
|
||||
$entities = array();
|
||||
|
||||
foreach ($backup_history as $key => $backup) {
|
||||
|
||||
$remote_sent = !empty($backup['service']) && ((is_array($backup['service']) && in_array('remotesend', $backup['service'])) || 'remotesend' === $backup['service']);
|
||||
if ($remote_sent) continue;
|
||||
|
||||
foreach ($backupable_entities as $key => $info) {
|
||||
if (isset($backup[$key])) $entities[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a backup into the history
|
||||
*
|
||||
* @param Integer $backup_time - the time of the backup
|
||||
* @param Array $backup_array - the backup
|
||||
*/
|
||||
public static function save_backup($backup_time, $backup_array) {
|
||||
$backup_history = self::get_history();
|
||||
|
||||
$backup_history[$backup_time] = isset($backup_history[$backup_time]) ? apply_filters('updraftplus_merge_backup_history', $backup_array, $backup_history[$backup_time]) : $backup_array;
|
||||
|
||||
self::save_history($backup_history, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save backup history into a file
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function preserve_backup_history() {
|
||||
self::$backup_history_on_restore = self::get_backup_history_option();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update backup history label based on backup-history.txt
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function restore_backup_history_label() {
|
||||
$backup_history = self::get_backup_history_option();
|
||||
$saved_backup_history = self::$backup_history_on_restore;
|
||||
$is_backup_history_changed = false;
|
||||
|
||||
if (empty($saved_backup_history)) return;
|
||||
|
||||
foreach ($saved_backup_history as $backup) {
|
||||
if (!isset($backup['label'])) continue;
|
||||
|
||||
foreach ($backup_history as $key => $value) {
|
||||
if ($backup['nonce'] === $value['nonce'] && !empty($backup_history[$key]['label']) && $backup_history[$key]['label'] !== $backup['label']) {
|
||||
$is_backup_history_changed = true;
|
||||
$backup_history[$key]['label'] = $backup['label'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($is_backup_history_changed) self::save_history($backup_history, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get backup history by checking for existence of the Multisite addon
|
||||
*
|
||||
* @return Array - the array of backup histories.
|
||||
*/
|
||||
private static function get_backup_history_option() {
|
||||
return UpdraftPlus_Options::get_updraft_option('updraft_backup_history');
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,767 @@
|
||||
<?php
|
||||
|
||||
if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed');
|
||||
|
||||
class UpdraftPlus_Database_Utility {
|
||||
|
||||
/**
|
||||
* Indicated which database is being used
|
||||
*
|
||||
* @var String
|
||||
*/
|
||||
private static $whichdb;
|
||||
|
||||
/**
|
||||
* The unfiltered table prefix - i.e. the real prefix that things are relative to
|
||||
*
|
||||
* @var String
|
||||
*/
|
||||
private static $table_prefix_raw;
|
||||
|
||||
/**
|
||||
* The object to perform database operations
|
||||
*
|
||||
* @var Object
|
||||
*/
|
||||
private static $dbhandle;
|
||||
|
||||
/**
|
||||
* The array of table status, used as a cache to reduce unnecessary DB reads for doing the same thing over and over
|
||||
*
|
||||
* @var Array
|
||||
*/
|
||||
private static $table_status = array();
|
||||
|
||||
/**
|
||||
* Initialize required variables
|
||||
*
|
||||
* @param String $whichdb - which database is being backed up
|
||||
* @param String $table_prefix_raw - the base table prefix
|
||||
* @param Object $dbhandle - WPDB object
|
||||
*/
|
||||
public static function init($whichdb, $table_prefix_raw, $dbhandle) {
|
||||
self::$whichdb = $whichdb;
|
||||
self::$table_prefix_raw = $table_prefix_raw;
|
||||
self::$dbhandle = $dbhandle;
|
||||
}
|
||||
|
||||
/**
|
||||
* The purpose of this function is to make sure that the options table is put in the database first, then the users table, then the site + blogs tables (if present - multisite), then the usermeta table; and after that the core WP tables - so that when restoring we restore the core tables first
|
||||
*
|
||||
* @param Array $a_arr the first array
|
||||
* @param Array $b_arr the second array
|
||||
*
|
||||
* @return Integer - the sort result, according to the rules of PHP custom sorting functions
|
||||
*/
|
||||
public static function backup_db_sorttables($a_arr, $b_arr) {
|
||||
$a = $a_arr['name'];
|
||||
$a_table_type = $a_arr['type'];
|
||||
$b = $b_arr['name'];
|
||||
$b_table_type = $b_arr['type'];
|
||||
|
||||
// Views must always go after tables (since they can depend upon them)
|
||||
if ('VIEW' == $a_table_type && 'VIEW' != $b_table_type) return 1;
|
||||
if ('VIEW' == $b_table_type && 'VIEW' != $a_table_type) return -1;
|
||||
|
||||
if ('wp' != self::$whichdb) return strcmp($a, $b);
|
||||
|
||||
global $updraftplus;
|
||||
if ($a == $b) return 0;
|
||||
$our_table_prefix = self::$table_prefix_raw;
|
||||
if ($a == $our_table_prefix.'options') return -1;
|
||||
if ($b == $our_table_prefix.'options') return 1;
|
||||
if ($a == $our_table_prefix.'site') return -1;
|
||||
if ($b == $our_table_prefix.'site') return 1;
|
||||
if ($a == $our_table_prefix.'blogs') return -1;
|
||||
if ($b == $our_table_prefix.'blogs') return 1;
|
||||
if ($a == $our_table_prefix.'users') return -1;
|
||||
if ($b == $our_table_prefix.'users') return 1;
|
||||
if ($a == $our_table_prefix.'usermeta') return -1;
|
||||
if ($b == $our_table_prefix.'usermeta') return 1;
|
||||
|
||||
if (empty($our_table_prefix)) return strcmp($a, $b);
|
||||
|
||||
try {
|
||||
$core_tables = array_merge(self::$dbhandle->tables, self::$dbhandle->global_tables, self::$dbhandle->ms_global_tables);
|
||||
} catch (Exception $e) {
|
||||
$updraftplus->log($e->getMessage());
|
||||
}
|
||||
|
||||
if (empty($core_tables)) $core_tables = array('terms', 'term_taxonomy', 'termmeta', 'term_relationships', 'commentmeta', 'comments', 'links', 'postmeta', 'posts', 'site', 'sitemeta', 'blogs', 'blogversions', 'blogmeta');
|
||||
|
||||
$na = UpdraftPlus_Manipulation_Functions::str_replace_once($our_table_prefix, '', $a);
|
||||
$nb = UpdraftPlus_Manipulation_Functions::str_replace_once($our_table_prefix, '', $b);
|
||||
if (in_array($na, $core_tables) && !in_array($nb, $core_tables)) return -1;
|
||||
if (!in_array($na, $core_tables) && in_array($nb, $core_tables)) return 1;
|
||||
return strcmp($a, $b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if the table has a composite primary key (composed from multiple columns)
|
||||
*
|
||||
* @param String $table - table to examine
|
||||
* @param Object|Null $wpdb_obj - WPDB-like object (requires the get_results() method), or null to use the global default
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public static function table_has_composite_private_key($table, $wpdb_obj = null) {
|
||||
|
||||
$wpdb = (null === $wpdb_obj) ? $GLOBALS['wpdb'] : $wpdb_obj;
|
||||
|
||||
$table_structure = $wpdb->get_results("DESCRIBE ".UpdraftPlus_Manipulation_Functions::backquote($table));
|
||||
if (!$table_structure) return false;
|
||||
|
||||
$primary_key_columns_found = 0;
|
||||
|
||||
foreach ($table_structure as $struct) {
|
||||
if (isset($struct->Key) && 'PRI' == $struct->Key) {
|
||||
$primary_key_columns_found++;
|
||||
if ($primary_key_columns_found > 1) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set MySQL server system variable
|
||||
*
|
||||
* @param String $variable The name of the system variable
|
||||
* @param String $value The variable value
|
||||
* @param Resource|Object $db_handle The database link identifier(resource) given by mysqli_init or mysql_connect
|
||||
* @return Boolean Returns true on success, false otherwise
|
||||
*/
|
||||
public static function set_system_variable($variable, $value, $db_handle) {
|
||||
|
||||
$is_mysqli = is_a($db_handle, 'mysqli');
|
||||
if (!is_resource($db_handle) && !$is_mysqli) return false;
|
||||
|
||||
$sql = "SET SESSION %s='%s'";
|
||||
if ($is_mysqli) {
|
||||
// @codingStandardsIgnoreLine
|
||||
$res = @mysqli_query($db_handle, sprintf($sql, mysqli_real_escape_string($db_handle, $variable), mysqli_real_escape_string($db_handle, $value)));
|
||||
} else {
|
||||
// @codingStandardsIgnoreLine
|
||||
$res = @mysql_query(sprintf($sql, mysql_real_escape_string($variable, $db_handle), mysql_real_escape_string($value, $db_handle)), $db_handle);
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get MySQL server system variable.
|
||||
*
|
||||
* @param String $variable The name of the system variable
|
||||
* @param Resource|Object $db_handle The database link identifier(resource) given by mysqli_init or mysql_connect
|
||||
* @return String|Boolean|Null Returns value of the system variable, false on query failure or null if there is no result for the corresponding variable
|
||||
*/
|
||||
public static function get_system_variable($variable, $db_handle) {
|
||||
|
||||
$is_mysqli = is_a($db_handle, 'mysqli');
|
||||
if (!is_resource($db_handle) && !$is_mysqli) return false;
|
||||
|
||||
$sql = 'SELECT @@SESSION.%s';
|
||||
|
||||
if ($is_mysqli) {
|
||||
// @codingStandardsIgnoreLine
|
||||
$res = @mysqli_query($db_handle, sprintf($sql, mysqli_real_escape_string($db_handle, $variable)));
|
||||
} else {
|
||||
// @codingStandardsIgnoreLine
|
||||
$res = @mysql_query(sprintf($sql, mysql_real_escape_string($variable, $db_handle)), $db_handle);
|
||||
}
|
||||
if (false === $res) {
|
||||
return $res;
|
||||
}
|
||||
if ($is_mysqli) {
|
||||
// @codingStandardsIgnoreLine
|
||||
$res = mysqli_fetch_array($res);
|
||||
return isset($res[0]) ? $res[0] : null;
|
||||
} else {
|
||||
// @codingStandardsIgnoreLine
|
||||
$res = mysql_result($res, 0);
|
||||
return false === $res ? null : $res;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* This function is adapted from the set_sql_mode() method in WordPress wpdb class but with few modifications applied, this can be used to switch between different sets of SQL modes.
|
||||
*
|
||||
* @see https://developer.wordpress.org/reference/classes/wpdb/set_sql_mode/
|
||||
* @see https://dev.mysql.com/doc/refman/5.6/en/sql-mode.html
|
||||
* @see https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html
|
||||
* @see https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html
|
||||
* @see https://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_sql_mode
|
||||
* @see https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_sql_mode
|
||||
* @see https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_sql_mode
|
||||
* @see https://mariadb.com/kb/en/library/sql-mode/#strict-mode
|
||||
* @see https://mariadb.com/kb/en/library/sql-mode/#setting-sql_mode
|
||||
*
|
||||
* @param Array $modes - Optional. A list of SQL modes to set.
|
||||
* @param Array $remove_modes - modes to remove if they are currently active
|
||||
* @param Resource|Object|NULL $db_handle - Optional. If specified, it should either the valid database link identifier(resource) given by mysql(i) or null to instead use the global WPDB object, or a WPDB-compatible object.
|
||||
*/
|
||||
public static function set_sql_mode($modes = array(), $remove_modes = array(), $db_handle = null) {
|
||||
|
||||
global $updraftplus, $wpdb;
|
||||
|
||||
$wpdb_handle_if_used = (null !== $db_handle && is_a($db_handle, 'WPDB')) ? $db_handle : $wpdb;
|
||||
|
||||
// If any of these are set, they will be unset
|
||||
$strict_modes = array(
|
||||
// according to mariadb and mysql docs, strict mode can be one of these or both
|
||||
'STRICT_TRANS_TABLES',
|
||||
'STRICT_ALL_TABLES',
|
||||
);
|
||||
|
||||
$incompatible_modes = array_unique(array_merge(array(
|
||||
'NO_ZERO_DATE',
|
||||
'ONLY_FULL_GROUP_BY',
|
||||
'TRADITIONAL',
|
||||
), $strict_modes));
|
||||
|
||||
$class = __CLASS__;
|
||||
|
||||
if (is_null($db_handle) || is_a($db_handle, 'WPDB')) {
|
||||
$initial_modes_str = $wpdb_handle_if_used->get_var('SELECT @@SESSION.sql_mode');
|
||||
} else {
|
||||
$initial_modes_str = call_user_func_array(array($class, 'get_system_variable'), array('sql_mode', $db_handle));
|
||||
}
|
||||
if (is_scalar($initial_modes_str) && !is_bool($initial_modes_str)) {
|
||||
$modes = array_unique(array_merge($modes, array_change_key_case(explode(',', $initial_modes_str), CASE_UPPER)));
|
||||
} else {
|
||||
$updraftplus->log("Couldn't get the sql_mode value (".serialize($initial_modes_str)."); will not attempt any adjustment");
|
||||
return;
|
||||
}
|
||||
|
||||
$modes = array_change_key_case($modes, CASE_UPPER);
|
||||
|
||||
$unwanted_modes = array_merge($incompatible_modes, $remove_modes);
|
||||
|
||||
foreach ($modes as $i => $mode) {
|
||||
if (in_array($mode, $unwanted_modes)) {
|
||||
unset($modes[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
$modes_str = implode(',', $modes);
|
||||
|
||||
if (is_null($db_handle) || is_a($db_handle, 'WPDB')) {
|
||||
$res = $wpdb_handle_if_used->query($wpdb_handle_if_used->prepare("SET SESSION sql_mode = %s", $modes_str));
|
||||
} else {
|
||||
$res = call_user_func_array(array($class, 'set_system_variable'), array('sql_mode', $modes_str, $db_handle));
|
||||
}
|
||||
|
||||
if (isset($initial_modes_str) && false == array_diff(explode(',', $initial_modes_str), $modes)) {
|
||||
$updraftplus->log("SQL compatibility mode is: $modes_str");
|
||||
} else {
|
||||
$updraftplus->log("SQL compatibility mode".((false === $res) ? " not" : "")." successfully changed".(isset($initial_modes_str) ? " from $initial_modes_str" : "")." to $modes_str");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the SQL "create table" column definition (non validating) and check whether it's a generated column and retrieve its column options
|
||||
*
|
||||
* @see https://dev.mysql.com/doc/refman/8.0/en/create-table.html
|
||||
* @see https://mariadb.com/kb/en/create-table/
|
||||
*
|
||||
* @param String $table_column_definition the column definition statement in which the generated column needs to be identified
|
||||
* @param Integer $starting_offset the string position of the column definition in a "create table" statement
|
||||
* @return Array|False an array of generated column fragment (column definition, column name, generated column type, etc); false otherwise
|
||||
*
|
||||
* Example input:
|
||||
*
|
||||
* $column_definition = "fullname varchar(101) GENERATED ALWAYS AS (CONCAT(first_name,' ',last_name)) VIRTUAL NOT NULL COMMENT 'this is the comment',"
|
||||
*
|
||||
* Corresponding result:
|
||||
*
|
||||
* [
|
||||
* "column_definition" => "fullname varchar(101) GENERATED ALWAYS AS (CONCAT(first_name,' ',last_name)) VIRTUAL NOT NULL COMMENT 'this is the comment',",
|
||||
* "column_name" => "fullname",
|
||||
* "column_data_type_definition" => [
|
||||
* [
|
||||
* "GENERATED ALWAYS AS (CONCAT(first_name,' ',last_name))",
|
||||
* 90
|
||||
* ],
|
||||
* [
|
||||
* "VIRTUAL NOT NULL",
|
||||
* 123 // string position
|
||||
* ],
|
||||
* [
|
||||
* "COMMENT 'this is the comment'",
|
||||
* 345 // string position
|
||||
* ]
|
||||
* ],
|
||||
* "is_virtual" => true
|
||||
* ]
|
||||
*/
|
||||
public static function get_generated_column_info($table_column_definition, $starting_offset) {
|
||||
|
||||
// check whether or not the column definition ($table_column_definition) is a generated column, if so then get all the column definitions
|
||||
// https://regex101.com/r/Fy2Bkd/12
|
||||
if (preg_match_all('/^\s*\`((?:[^`]|``)+)\`([^,\'"]+?)(?:((?:GENERATED\s*ALWAYS\s*)?AS\s*\(.+\))([\w\s]*)(COMMENT\s*(?:\'(?:[^\']|\'\')*\'|\"(?:[^"]|"")*\"))([\w\s]*)|((?:GENERATED\s*ALWAYS\s*)?AS\s*\(.+\)([\w\s]*)))/i', $table_column_definition, $column_definitions, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
|
||||
|
||||
if (empty($column_definitions)) return false;
|
||||
|
||||
/**
|
||||
* If the above preg_match_all function succeed, it returns an array with the following format:
|
||||
*
|
||||
* Array(3) {
|
||||
* [0]=> // 1st set of the matched/captured string
|
||||
* array(6) {
|
||||
* [0]=> // 1st index represents a full column definition
|
||||
* array(2) {
|
||||
* [0]=> string(131) ", `full_name` char(41) GENERATED ALWAYS AS (concat(`firstname`,'()`)(()',`lastname`)) VIRTUAL NOT NULL COMMENT 'fu(ll)'_name'' COLUMN_FORMAT DEFAULT"
|
||||
* [1]=> int(541)
|
||||
* }
|
||||
* [1]=> // 2nd index represents a column name
|
||||
* array(2) {
|
||||
* [0]=> string(9) "full_name"
|
||||
* [1]=> int(547)
|
||||
* }
|
||||
* [2]=> // 3rd index represents data type option that is captured before "generated always as"
|
||||
* array(2) {
|
||||
* [0]=> string(18) " char(41) "
|
||||
* [1]=> int(555)
|
||||
* }
|
||||
* [3]=> // 4rd index represents data type option which is specific for "generated always as"
|
||||
* array(2) {
|
||||
* [0]=> string(18) "GENERATED ALWAYS AS (concat(`firstname`,'()`)(()',`lastname`))"
|
||||
* [1]=> int(629) // this is the position or starting offset of the captured data type's option, this can later be used to help with the unsupported keyword replacement stuff among db server
|
||||
* }
|
||||
* [4]=> // 5th index represents data type option that is captured before COMMENT keyword and after "generated always as"
|
||||
* array(2) {
|
||||
* [0]=> string(13) " VIRTUAL NOT NULL " // this is the comment string that could be filled with any word even the reserved keyword (e.g. not null, virtual, stored, etc..)
|
||||
* [1]=> int(656) // this is the position or starting offset of the captured data type's option, this can later be used to help with the unsupported keyword replacement stuff among db server
|
||||
* }
|
||||
* [5]=> // 6th index represents the comment
|
||||
* array(2) {
|
||||
* [0]=> string(2) "COMMENT 'fu(ll)'_name''"
|
||||
* [1]=> int(670) // this is the position or starting offset of the captured comment's string
|
||||
* }
|
||||
* [6]=> // 7th index represents data type option that is captured after the COMMENT keyword
|
||||
* array(2) {
|
||||
* [0]=> string(2) "COLUMN_FORMAT DEFAULT"
|
||||
* [1]=> int(670)
|
||||
* }
|
||||
* }
|
||||
* array(8) { // 2nd set
|
||||
* [0]=>
|
||||
* array(2) {
|
||||
* [0]=> string(95) ", `full_name6` char(41) GENERATED ALWAYS AS (concat(`firstname`,' ',`lastname2`))STORED NULL"
|
||||
* [1]=> int(1121)
|
||||
* }
|
||||
* [1]=>
|
||||
* array(2) {
|
||||
* [0]=> string(10) "full_name6"
|
||||
* [1]=> int(1127)
|
||||
* }
|
||||
* [2]=>
|
||||
* array(2) {
|
||||
* [0]=> string(0) " char(41) "
|
||||
* [1]=> int(1139)
|
||||
* }
|
||||
* [3]=>
|
||||
* array(2) {
|
||||
* [0]=> string(0) ""
|
||||
* [1]=> int(-1)
|
||||
* }
|
||||
* [4]=>
|
||||
* array(2) {
|
||||
* [0]=> string(0) ""
|
||||
* [1]=> int(-1)
|
||||
* }
|
||||
* [5]=>
|
||||
* array(2) {
|
||||
* [0]=> string(0) "" // an empty string of this captured token indicates that the column definition doesn't have COMMENT keyword
|
||||
* [1]=> int(-1)
|
||||
* }
|
||||
* [6]=>
|
||||
* array(2) {
|
||||
* [0]=> string(0) ""
|
||||
* [1]=> int(-1)
|
||||
* }
|
||||
* [7]=> // 8th index will appear if there's no COMMENT keyword found in the column definition and it represents data type option that is specific for "generated always as"
|
||||
* array(2) {
|
||||
* [0]=> string(11) "GENERATED ALWAYS AS (concat(`firstname`,' ',`lastname2`))"
|
||||
* [1]=> int(1205)
|
||||
* }
|
||||
* [8]=> // 9th index will appear if there's no COMMENT keyword found in the column definition and it represents the captured data type options
|
||||
* array(2) {
|
||||
* [0]=> string(11) "STORED NULL"
|
||||
* [1]=> int(1270)
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
|
||||
foreach ($column_definitions as $column_definition) {
|
||||
$data_type_definition = (!empty($column_definition[4][0]) ? $column_definition[4][0] : '').(!empty($column_definition[6][0]) ? $column_definition[6][0] : '').(!empty($column_definition[8][0]) ? $column_definition[8][0] : '');
|
||||
// if no virtual, stored or persistent option is specified then it's virtual by default. It's not possible having two generated columns type in the column definition e.g fullname varchar(101) GENERATED ALWAYS AS (CONCAT(first_name,' ',last_name)) VIRTUAL STORED NOT NULL COMMENT 'comment text', both MySQL and MariaDB will produces an error
|
||||
$is_virtual = preg_match('/\bvirtual\b/i', $data_type_definition) || (!preg_match('/\bstored\b/i', $data_type_definition) && !preg_match('/\bpersistent\b/i', $data_type_definition));
|
||||
|
||||
$fragment = array(
|
||||
// full syntax of the column definition
|
||||
"column_definition" => $column_definition[0][0],
|
||||
// the extracted column name
|
||||
"column_name" => $column_definition[1][0],
|
||||
'column_data_type_definition' => array(),
|
||||
"is_virtual" => $is_virtual,
|
||||
);
|
||||
if (!empty($column_definition[2])) {
|
||||
$fragment['column_data_type_definition']['DATA_TYPE_TOKEN'] = $column_definition[2];
|
||||
$fragment['column_data_type_definition']['DATA_TYPE_TOKEN'][1] = (int) $starting_offset + (int) $fragment['column_data_type_definition']['DATA_TYPE_TOKEN'][1];
|
||||
}
|
||||
if (!empty($column_definition[3])) {
|
||||
$fragment['column_data_type_definition']['GENERATED_ALWAYS_TOKEN'] = $column_definition[3];
|
||||
if (empty($fragment['column_data_type_definition'][1]) && !empty($column_definition[7][0])) $fragment['column_data_type_definition']['GENERATED_ALWAYS_TOKEN'] = $column_definition[7];
|
||||
$fragment['column_data_type_definition']['GENERATED_ALWAYS_TOKEN'][1] = (int) $starting_offset + (int) $fragment['column_data_type_definition']['GENERATED_ALWAYS_TOKEN'][1];
|
||||
}
|
||||
if (!empty($column_definition[4])) {
|
||||
$fragment['column_data_type_definition'][2] = $column_definition[4];
|
||||
$fragment['column_data_type_definition'][2][1] = (int) $starting_offset + (int) $fragment['column_data_type_definition'][2][1];
|
||||
}
|
||||
if (!empty($column_definition[5])) {
|
||||
$fragment['column_data_type_definition']['COMMENT_TOKEN'] = $column_definition[5];
|
||||
$fragment['column_data_type_definition']['COMMENT_TOKEN'][1] = (int) $starting_offset + (int) $fragment['column_data_type_definition']['COMMENT_TOKEN'][1];
|
||||
}
|
||||
if (!empty($column_definition[6])) {
|
||||
$fragment['column_data_type_definition'][4] = $column_definition[6];
|
||||
$fragment['column_data_type_definition'][4][1] = (int) $starting_offset + (int) $fragment['column_data_type_definition'][4][1];
|
||||
}
|
||||
if (!empty($column_definition[8])) {
|
||||
$fragment['column_data_type_definition'][5] = $column_definition[8];
|
||||
$fragment['column_data_type_definition'][5][1] = (int) $starting_offset + (int) $fragment['column_data_type_definition'][5][1];
|
||||
}
|
||||
}
|
||||
}
|
||||
return isset($fragment) ? $fragment : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve information concerning whether the currently running database server supports generated columns (VIRTUAL, STORED, PERSISTENT)
|
||||
*
|
||||
* @param String $engine Optional. If specified, it should either a well-known database engine like InnoDB, MyISAM, etc or an empty string to instead use database default storage engine; e.g. 'MyISAM'
|
||||
* @return Array|Boolean an array of supported generated column syntax options (whether or not persistent type, not null, virtual index are supported) or false if generated column isn't supported
|
||||
*
|
||||
* The return value is structured thus:
|
||||
*
|
||||
* [
|
||||
* // InnoDB supports PERSISTENT generated columns type, whereas MyISAM does not
|
||||
* "is_persistent_supported" => false,
|
||||
* // InnoDB supports NOT NULL constraint, whereas MyISAM does not
|
||||
* "is_not_null_supported" => true,
|
||||
* // if it's on MariaDB, you can use insert ignore statement to prevent generated columns errors but not on MySQL
|
||||
* "can_insert_ignore_to_generated_column" => true,
|
||||
* // No matter what the database engine you use, MySQL doesn't yet support indexing on generated columns
|
||||
* "is_virtual_index_supported" => false
|
||||
* ]
|
||||
*/
|
||||
public static function is_generated_column_supported($engine = '') {
|
||||
|
||||
global $table_prefix, $wpdb;
|
||||
|
||||
$random_table_name = $table_prefix.'updraft_tmp_'.rand(0, 9999999).md5(microtime(true));
|
||||
|
||||
$drop_statement = "DROP TABLE IF EXISTS `$random_table_name`;";
|
||||
|
||||
// both mysql and mariadb support generated column, virtual is the default type and the other option type is called stored, mariadb has an alias for stored type which is called persistent, whereas mysql doesn't have such thing.
|
||||
// MySQL supports NULL and NOT NULL constraints. On the other hand, MariaDB doesn't support it.
|
||||
$sql = array(
|
||||
"CREATE TABLE `$random_table_name` (`virtual_column` varchar(17) GENERATED ALWAYS AS ('virtual_column') VIRTUAL COMMENT 'virtual_column')".(!empty($engine) ? " ENGINE=$engine" : "").";",
|
||||
"ALTER TABLE `$random_table_name` ADD `persistent_column` VARCHAR(17) AS ('persistent_column') PERSISTENT COMMENT 'generated_column';",
|
||||
"ALTER TABLE `$random_table_name` ADD `virtual_column_not_null` VARCHAR(17) AS ('virtual_column_not_null') VIRTUAL NOT NULL COMMENT 'virtual_column_not_null';",
|
||||
// check if we can get through this: Error Code: 3105. The value specified for generated column 'generated_column' in table 'wp_generated_column_test' is not allowed.
|
||||
// DEFAULT is the only allowed value for virtual and stored type (i.e INSERT IGNORE INTO `wp_generated_column_test` (`virtual_column`) VALUES(DEFAULT)), other than that will produce an error, luckily insert ignore works fine on MariaDB but not on MySQL
|
||||
"INSERT IGNORE INTO `$random_table_name` (`virtual_column`) VALUES('virtual_column');",
|
||||
// MySQL does not support the create option 'Index on virtual generated column' on MyISAM storage engine
|
||||
"CREATE INDEX `idx_wp_udp_generated_column_test_generated_column` ON `$random_table_name` (virtual_column) COMMENT 'virtual_column' ALGORITHM DEFAULT LOCK DEFAULT;",
|
||||
);
|
||||
|
||||
$old_val = $wpdb->suppress_errors();
|
||||
$wpdb->query($drop_statement);
|
||||
$is_generated_column_supported = $wpdb->query($sql[0]);
|
||||
if ($is_generated_column_supported) {
|
||||
$is_generated_column_supported = array(
|
||||
'is_persistent_supported' => $wpdb->query($sql[1]),
|
||||
'is_not_null_supported' => $wpdb->query($sql[2]),
|
||||
'can_insert_ignore_to_generated_column' => (bool) $wpdb->query($sql[3]),
|
||||
'is_virtual_index_supported' => $wpdb->query($sql[4])
|
||||
);
|
||||
} else {
|
||||
$is_generated_column_supported = false;
|
||||
}
|
||||
$wpdb->query($drop_statement);
|
||||
$wpdb->suppress_errors($old_val);
|
||||
|
||||
return $is_generated_column_supported;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the "insert into" statement, capture the column names (if any) and check whether one of the captured columns matches the given list of the "$generated_columns"
|
||||
*
|
||||
* @see https://regex101.com/r/JZiJqH/2
|
||||
*
|
||||
* @param String $insert_statement the insert statement in which the generated columns will be checked
|
||||
* @param Array $generated_columns the list of the available "generated columns"
|
||||
* @return Boolean|Null True if "generated columns" exist in the "insert into" statement, false otherwise, null on empty or unmatched insert statement
|
||||
*/
|
||||
public static function generated_columns_exist_in_the_statement($insert_statement, $generated_columns) {
|
||||
|
||||
$exist = null;
|
||||
if (preg_match('/\s*insert.+?into(?:\s*`(?:[^`]|`)+?`|[^\(]+)(?:\s*\((.+?)\))?\s*values.+/i', $insert_statement, $matches)) {
|
||||
/**
|
||||
* the reqex above will search for matches of either the insert statement gives data based on the specified column names (i.e INSERT INTO `table_name`(`col1`,'col2`,`virtual_column`,`stored_column`,`col5`) values('1','2','3','4','5')) or not (i.e INSERT INTO `table_name` values('1',',2','3','4','5')), and if the above preg_match function succeed, it returns an array with the following format:
|
||||
*
|
||||
* Array(2) {
|
||||
* [0]=> "INSERT INTO `table_name`(`col1`,'col2`,`virtual_column`,`stored_column`,`col5`) values('1','2','3','4','5')"
|
||||
* [1]=> "`col1`,`col2`,`virtual_column`,`col4`,`stored_column`"
|
||||
* }
|
||||
* OR
|
||||
* Array(1) {
|
||||
* [0]=> "INSERT INTO `table_name` values('1','2','3','4','5')"
|
||||
* }
|
||||
*/
|
||||
$columns = isset($matches[1]) ? preg_split('/\`\s*,\s*\`/', preg_replace('/\`((?:[^\`]|\`)+)\`/', "$1", trim($matches[1]))) : array();
|
||||
/**
|
||||
* the preg_replace is used to remove the leading and trailing backtick, so that the string becomes: col1`,`col2`,`virtual_column`,`col4`,`stored_column
|
||||
* the preg_split is used to split all strings that match `,` pattern
|
||||
* Array(5) {
|
||||
* [0]=> string(5) "col1"
|
||||
* [1]=> string(4) "col2"
|
||||
* [2]=> string(14) "virtual_column"
|
||||
* [3]=> string(4) "col4"
|
||||
* [4]=> string(14) "stored_column"
|
||||
* }
|
||||
*/
|
||||
$exist = (false == $columns) || (true == array_intersect($generated_columns, $columns));
|
||||
}
|
||||
return $exist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the currently running database server supports stored routines
|
||||
*
|
||||
* @return Array|WP_Error an array of booleans indicating whether or not some of syntax variations are supported, or WP_Error object if stored routine isn't supported
|
||||
*
|
||||
* Return format example:
|
||||
*
|
||||
* [
|
||||
* "is_create_or_replace_supported" => true, // true on MariaDB, false on MySQL
|
||||
* "is_if_not_exists_function_supported" => true, // true on MariaDB, false on MySQL
|
||||
* "is_aggregate_function_supported" => true, // true on MariaDB, false on MySQL
|
||||
* "is_binary_logging_enabled" => true, // true if --bin-log is specified for both MariaDB and MySQL
|
||||
* "is_function_creators_trusted" => false // the default value is false (MariaDB/MySQL)
|
||||
* ]
|
||||
*
|
||||
* OR a database error message, e.g. "Access denied for user 'root'@'localhost' to database 'wordpress'"
|
||||
*/
|
||||
public static function is_stored_routine_supported() {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$function_name = 'updraft_test_stored_routine';
|
||||
$sql = array(
|
||||
"DROP_FUNCTION" => "DROP FUNCTION IF EXISTS ".$function_name,
|
||||
// sql to check whether stored routines is supported
|
||||
"CREATE_FUNCTION" => "CREATE FUNCTION ".$function_name."() RETURNS tinyint(1) DETERMINISTIC READS SQL DATA RETURN true",
|
||||
// sql to check whether create or replace syntax is supported
|
||||
"CREATE_REPLACE_FUNCTION" => "CREATE OR REPLACE FUNCTION ".$function_name."() RETURNS tinyint(1) DETERMINISTIC READS SQL DATA RETURN true",
|
||||
// sql to check whether if not exists syntax is supported (mariadb starting with 10.1.3)
|
||||
"CREATE_FUNCTION_IF_NOT_EXISTS" => "CREATE FUNCTION IF NOT EXISTS ".$function_name."() RETURNS tinyint(1) DETERMINISTIC READS SQL DATA RETURN true",
|
||||
// sql to check whether aggregate function is supported (mariadb starting with 10.3.3)
|
||||
"CREATE_REPLACE_AGGREGATE" => "CREATE OR REPLACE AGGREGATE FUNCTION ".$function_name."() RETURNS tinyint(1) DETERMINISTIC READS SQL DATA BEGIN RETURN true; FETCH GROUP NEXT ROW; END;"
|
||||
);
|
||||
|
||||
$old_val = $wpdb->suppress_errors();
|
||||
$wpdb->query($sql['DROP_FUNCTION']);
|
||||
$is_stored_routine_supported = $wpdb->query($sql['CREATE_FUNCTION']);
|
||||
if ($is_stored_routine_supported) {
|
||||
$is_binary_logging_enabled = 1 == $wpdb->get_var('SELECT @@GLOBAL.log_bin');
|
||||
// not sure why the log_bin variable cant be retrieved on mysql 5.0, seems like there's a bug on that version, so we use another alternative to check whether or not binary logging is enabled
|
||||
$is_binary_logging_enabled = false === $is_binary_logging_enabled ? $wpdb->get_results("SHOW GLOBAL VARIABLES LIKE 'log_bin'", ARRAY_A) : $is_binary_logging_enabled;
|
||||
$is_binary_logging_enabled = is_array($is_binary_logging_enabled) && isset($is_binary_logging_enabled[0]['Value']) && '' != $is_binary_logging_enabled[0]['Value'] ? $is_binary_logging_enabled[0]['Value'] : $is_binary_logging_enabled;
|
||||
$is_binary_logging_enabled = is_string($is_binary_logging_enabled) && ('ON' === strtoupper($is_binary_logging_enabled) || '1' === $is_binary_logging_enabled) ? true : $is_binary_logging_enabled;
|
||||
$is_binary_logging_enabled = is_string($is_binary_logging_enabled) && ('OFF' === strtoupper($is_binary_logging_enabled) || '0' === $is_binary_logging_enabled) ? false : $is_binary_logging_enabled;
|
||||
$is_stored_routine_supported = array(
|
||||
'is_create_or_replace_supported' => $wpdb->query($sql['CREATE_REPLACE_FUNCTION']),
|
||||
'is_if_not_exists_function_supported' => $wpdb->query($sql['CREATE_FUNCTION_IF_NOT_EXISTS']),
|
||||
'is_aggregate_function_supported' => $wpdb->query($sql['CREATE_REPLACE_AGGREGATE']),
|
||||
'is_binary_logging_enabled' => $is_binary_logging_enabled,
|
||||
'is_function_creators_trusted' => 1 == $wpdb->get_var('SELECT @@GLOBAL.log_bin_trust_function_creators'),
|
||||
);
|
||||
$wpdb->query($sql['DROP_FUNCTION']);
|
||||
} else {
|
||||
$is_stored_routine_supported = new WP_Error('routine_creation_error', sprintf(__('An error occurred while attempting to check the support of stored routines creation (%s %s)', 'updraftplus'), $wpdb->last_error.' -', $sql['CREATE_FUNCTION']));
|
||||
}
|
||||
$wpdb->suppress_errors($old_val);
|
||||
|
||||
return $is_stored_routine_supported;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all the stored routines (functions and procedures) in the currently running database
|
||||
*
|
||||
* @return Array|WP_Error an array of routine statuses, or an empty array if there is no stored routine in the database, or WP_Error object on failure
|
||||
*
|
||||
* Output example:
|
||||
*
|
||||
* [
|
||||
* [
|
||||
* "Db" => "wordpress",
|
||||
* "Name" => "_NextVal",
|
||||
* "Type" => "FUNCTION",
|
||||
* "Definer" => "root@localhost",
|
||||
* "Modified" => "2019-11-22 15:11:15",
|
||||
* "Created" => "2019-11-22 14:20:29",
|
||||
* "Security_type" => "DEFINER",
|
||||
* "Comment" => "",
|
||||
* "Function" => "_NextVal",
|
||||
* "sql_mode" => "",
|
||||
* "Create Function" => "
|
||||
* CREATE DEFINER=`root`@`localhost` FUNCTION `_NextVal`(vname VARCHAR(30)) RETURNS int(11)
|
||||
* BEGIN
|
||||
* -- Retrieve and update in single statement
|
||||
* UPDATE _sequences
|
||||
* SET next = next + 1
|
||||
* WHERE name = vname;
|
||||
* RETURN (SELECT next FROM _sequences LIMIT 1);
|
||||
* END"
|
||||
* ],
|
||||
* [
|
||||
* "Db" => "wordpress",
|
||||
* "Name" => "CreateSequence",
|
||||
* "Type" => "Procedure",
|
||||
* "Definer" => "root@localhost",
|
||||
* "Modified" => "2019-11-22 15:11:15",
|
||||
* "Created" => "2019-11-22 14:20:29",
|
||||
* "Security_type" => "DEFINER",
|
||||
* "Comment" => "",
|
||||
* "Procedure" => "CreateSequence",
|
||||
* "sql_mode" => "",
|
||||
* "Create Procedure" => "
|
||||
* CREATE DEFINER=`root`@`localhost` PROCEDURE `CreateSequence`(name VARCHAR(30), start INT, inc INT)
|
||||
* BEGIN
|
||||
* -- Create a table to store sequences
|
||||
* CREATE TABLE _sequences (
|
||||
* name VARCHAR(70) NOT NULL UNIQUE,
|
||||
* next INT NOT NULL,
|
||||
* inc INT NOT NULL,
|
||||
* );
|
||||
* -- Add the new sequence
|
||||
* INSERT INTO _sequences VALUES (name, start, inc);
|
||||
* END"
|
||||
* ]
|
||||
* ]
|
||||
*/
|
||||
public static function get_stored_routines() {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$old_val = $wpdb->suppress_errors();
|
||||
try {
|
||||
$err_msg = __('An error occurred while attempting to retrieve routine status (%s %s)', 'updraftplus');
|
||||
$function_status = $wpdb->get_results($wpdb->prepare('SHOW FUNCTION STATUS WHERE DB = %s', DB_NAME), ARRAY_A);
|
||||
if (!empty($wpdb->last_error)) throw new Exception(sprintf($err_msg, $wpdb->last_error.' -', $wpdb->last_query), 0);
|
||||
$procedure_status = $wpdb->get_results($wpdb->prepare('SHOW PROCEDURE STATUS WHERE DB = %s', DB_NAME), ARRAY_A);
|
||||
if (!empty($wpdb->last_error)) throw new Exception(sprintf($err_msg, $wpdb->last_error.' -', $wpdb->last_query), 0);
|
||||
$stored_routines = array_merge((array) $function_status, (array) $procedure_status);
|
||||
foreach ((array) $stored_routines as $key => $routine) {
|
||||
if (empty($routine['Name']) || empty($routine['Type'])) continue;
|
||||
$routine_name = $routine['Name'];
|
||||
// Since routine name can include backquotes and routine name is typically enclosed with backquotes as well, the backquote escaping for the routine name can be done by adding a leading backquote
|
||||
$quoted_escaped_routine_name = UpdraftPlus_Manipulation_Functions::backquote(str_replace('`', '``', $routine_name));
|
||||
$routine = $wpdb->get_results($wpdb->prepare('SHOW CREATE %1$s %2$s', $routine['Type'], $quoted_escaped_routine_name), ARRAY_A);
|
||||
if (!empty($wpdb->last_error)) throw new Exception(sprintf(__('An error occurred while attempting to retrieve the routine SQL/DDL statement (%s %s)', 'updraftplus'), $wpdb->last_error.' -', $wpdb->last_query), 1);
|
||||
$stored_routines[$key] = array_merge($stored_routines[$key], $routine ? $routine[0] : array());
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
$stored_routines = new WP_Error(1 === $ex->getCode() ? 'routine_sql_error' : 'routine_status_error', $ex->getMessage());
|
||||
}
|
||||
$wpdb->suppress_errors($old_val);
|
||||
|
||||
return $stored_routines;
|
||||
}
|
||||
|
||||
/**
|
||||
* First half of escaping for LIKE special characters % and _ before preparing for MySQL.
|
||||
* Use this only before wpdb::prepare() or esc_sql(). Reversing the order is very bad for security. This is a shim function for WP versions before 4.0.
|
||||
*
|
||||
* @param String $text The raw text to be escaped. The input typed by the user should have no extra or deleted slashes.
|
||||
* @return String Text in the form of a LIKE phrase. The output is not SQL safe. Call wpdb::prepare() or wpdb::_real_escape() next.
|
||||
*/
|
||||
public static function esc_like($text) {
|
||||
return function_exists('esc_like') ? esc_like($text) : addcslashes($text, '_%\\');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return installation or activation link of WP-Optimize plugin
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public static function get_install_activate_link_of_wp_optimize_plugin() {
|
||||
// If WP-Optimize is activated, then return empty.
|
||||
if (class_exists('WP_Optimize')) return '';
|
||||
|
||||
// Generally it is 'wp-optimize/wp-optimize.php',
|
||||
// but we can't assume that the user hasn't renamed the plugin folder - with 3 million UDP users and 1 million AIOWPS, there will be some who have.
|
||||
$wp_optimize_plugin_file_rel_to_plugins_dir = UpdraftPlus_Database_Utility::get_wp_optimize_plugin_file_rel_to_plugins_dir();
|
||||
|
||||
// If UpdraftPlus is installed but not activated, then return activate link.
|
||||
if ($wp_optimize_plugin_file_rel_to_plugins_dir) {
|
||||
$activate_url = add_query_arg(array(
|
||||
'_wpnonce' => wp_create_nonce('activate-plugin_'.$wp_optimize_plugin_file_rel_to_plugins_dir),
|
||||
'action' => 'activate',
|
||||
'plugin' => $wp_optimize_plugin_file_rel_to_plugins_dir,
|
||||
), network_admin_url('plugins.php'));
|
||||
|
||||
// If is network admin then add to link network activation.
|
||||
if (is_network_admin()) {
|
||||
$activate_url = add_query_arg(array('networkwide' => 1), $activate_url);
|
||||
}
|
||||
return sprintf('%s <a href="%s">%s</a>', __('WP-Optimize is installed but currently inactive.', 'updraftplus'), $activate_url, __('Follow this link to activate the WP-Optimize plugin.', 'updraftplus'));
|
||||
}
|
||||
|
||||
// If WP-Optimize is neither activated nor installed then return the installation link
|
||||
return '<a href="'.wp_nonce_url(self_admin_url('update.php?action=install-plugin&updraftplus_noautobackup=1&plugin=wp-optimize'), 'install-plugin_wp-optimize').'">'.__('Follow this link to install the WP-Optimize plugin.', 'updraftplus').'</a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path to the WP-Optimize plugin file relative to the plugins directory.
|
||||
*
|
||||
* @return String|false path to the WP-Optimize plugin file relative to the plugins directory
|
||||
*/
|
||||
public static function get_wp_optimize_plugin_file_rel_to_plugins_dir() {
|
||||
if (!function_exists('get_plugins')) {
|
||||
include_once ABSPATH . '/wp-admin/includes/plugin.php';
|
||||
}
|
||||
|
||||
$installed_plugins = get_plugins();
|
||||
$installed_plugins_keys = array_keys($installed_plugins);
|
||||
foreach ($installed_plugins_keys as $plugin_file_rel_to_plugins_dir) {
|
||||
$temp_plugin_file_name = substr($plugin_file_rel_to_plugins_dir, strpos($plugin_file_rel_to_plugins_dir, '/') + 1);
|
||||
if ('wp-optimize.php' == $temp_plugin_file_name) {
|
||||
return $plugin_file_rel_to_plugins_dir;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class UpdraftPlus_WPDB_OtherDB_Utility extends wpdb {
|
||||
/**
|
||||
* This adjusted bail() does two things: 1) Never dies and 2) logs in the UD log
|
||||
*
|
||||
* @param String $message a string containing a message
|
||||
* @param String $error_code a string containing an error code
|
||||
* @return Boolean returns false
|
||||
*/
|
||||
public function bail($message, $error_code = '500') {
|
||||
global $updraftplus;
|
||||
if ('db_connect_fail' == $error_code) $message = 'Connection failed: check your access details, that the database server is up, and that the network connection is not firewalled.';
|
||||
$updraftplus->log("WPDB_OtherDB error: $message ($error_code)");
|
||||
// Now do the things that would have been done anyway
|
||||
$this->error = class_exists('WP_Error') ? new WP_Error($error_code, $message) : $message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,888 @@
|
||||
<?php
|
||||
|
||||
if (!defined('ABSPATH')) die('No direct access.');
|
||||
|
||||
/**
|
||||
* Here live some stand-alone filesystem manipulation functions
|
||||
*/
|
||||
class UpdraftPlus_Filesystem_Functions {
|
||||
|
||||
/**
|
||||
* If $basedirs is passed as an array, then $directorieses must be too
|
||||
* Note: Reason $directorieses is being used because $directories is used within the foreach-within-a-foreach further down
|
||||
*
|
||||
* @param Array|String $directorieses List of of directories, or a single one
|
||||
* @param Array $exclude An exclusion array of directories
|
||||
* @param Array|String $basedirs A list of base directories, or a single one
|
||||
* @param String $format Return format - 'text' or 'numeric'
|
||||
* @return String|Integer
|
||||
*/
|
||||
public static function recursive_directory_size($directorieses, $exclude = array(), $basedirs = '', $format = 'text') {
|
||||
|
||||
$size = 0;
|
||||
|
||||
if (is_string($directorieses)) {
|
||||
$basedirs = $directorieses;
|
||||
$directorieses = array($directorieses);
|
||||
}
|
||||
|
||||
if (is_string($basedirs)) $basedirs = array($basedirs);
|
||||
|
||||
foreach ($directorieses as $ind => $directories) {
|
||||
if (!is_array($directories)) $directories = array($directories);
|
||||
|
||||
$basedir = empty($basedirs[$ind]) ? $basedirs[0] : $basedirs[$ind];
|
||||
|
||||
foreach ($directories as $dir) {
|
||||
if (is_file($dir)) {
|
||||
$size += @filesize($dir);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
|
||||
} else {
|
||||
$suffix = ('' != $basedir) ? ((0 === strpos($dir, $basedir.'/')) ? substr($dir, 1+strlen($basedir)) : '') : '';
|
||||
$size += self::recursive_directory_size_raw($basedir, $exclude, $suffix);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ('numeric' == $format) return $size;
|
||||
|
||||
return UpdraftPlus_Manipulation_Functions::convert_numeric_size_to_text($size);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that WP_Filesystem is instantiated and functional. Otherwise, outputs necessary HTML and dies.
|
||||
*
|
||||
* @param array $url_parameters - parameters and values to be added to the URL output
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function ensure_wp_filesystem_set_up_for_restore($url_parameters = array()) {
|
||||
|
||||
global $wp_filesystem, $updraftplus;
|
||||
|
||||
$build_url = UpdraftPlus_Options::admin_page().'?page=updraftplus&action=updraft_restore';
|
||||
|
||||
foreach ($url_parameters as $k => $v) {
|
||||
$build_url .= '&'.$k.'='.$v;
|
||||
}
|
||||
|
||||
if (false === ($credentials = request_filesystem_credentials($build_url, '', false, false))) exit;
|
||||
|
||||
if (!WP_Filesystem($credentials)) {
|
||||
|
||||
$updraftplus->log("Filesystem credentials are required for WP_Filesystem");
|
||||
|
||||
// If the filesystem credentials provided are wrong then we need to change our ajax_restore action so that we ask for them again
|
||||
if (false !== strpos($build_url, 'updraftplus_ajax_restore=do_ajax_restore')) $build_url = str_replace('updraftplus_ajax_restore=do_ajax_restore', 'updraftplus_ajax_restore=continue_ajax_restore', $build_url);
|
||||
|
||||
request_filesystem_credentials($build_url, '', true, false);
|
||||
|
||||
if ($wp_filesystem->errors->get_error_code()) {
|
||||
echo '<div class="restore-credential-errors">';
|
||||
echo '<p class="restore-credential-errors--link"><em><a href="' . esc_url(apply_filters('updraftplus_com_link', "https://updraftplus.com/faqs/asked-ftp-details-upon-restorationmigration-updates/")) . '" target="_blank">' . esc_html__('Why am I seeing this?', 'updraftplus') . '</a></em></p>';
|
||||
echo '<div class="restore-credential-errors--list">';
|
||||
foreach ($wp_filesystem->errors->get_error_messages() as $message) show_message($message);
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the html of "Web-server disk space" line which resides above of the existing backup table
|
||||
*
|
||||
* @param Boolean $will_immediately_calculate_disk_space Whether disk space should be counted now or when user click Refresh link
|
||||
*
|
||||
* @return String Web server disk space html to render
|
||||
*/
|
||||
public static function web_server_disk_space($will_immediately_calculate_disk_space = true) {
|
||||
if ($will_immediately_calculate_disk_space) {
|
||||
$disk_space_used = self::get_disk_space_used('updraft', 'numeric');
|
||||
if ($disk_space_used > apply_filters('updraftplus_display_usage_line_threshold_size', 104857600)) { // 104857600 = 100 MB = (100 * 1024 * 1024)
|
||||
$disk_space_text = UpdraftPlus_Manipulation_Functions::convert_numeric_size_to_text($disk_space_used);
|
||||
$refresh_link_text = __('refresh', 'updraftplus');
|
||||
return self::web_server_disk_space_html($disk_space_text, $refresh_link_text);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
} else {
|
||||
$disk_space_text = '';
|
||||
$refresh_link_text = __('calculate', 'updraftplus');
|
||||
return self::web_server_disk_space_html($disk_space_text, $refresh_link_text);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the html of "Web-server disk space" line which resides above of the existing backup table
|
||||
*
|
||||
* @param String $disk_space_text The texts which represents disk space usage
|
||||
* @param String $refresh_link_text Refresh disk space link text
|
||||
*
|
||||
* @return String - Web server disk space HTML
|
||||
*/
|
||||
public static function web_server_disk_space_html($disk_space_text, $refresh_link_text) {
|
||||
return '<li class="updraft-server-disk-space" title="'.esc_attr__('This is a count of the contents of your Updraft directory', 'updraftplus').'"><strong>'.__('Web-server disk space in use by UpdraftPlus', 'updraftplus').':</strong> <span class="updraft_diskspaceused"><em>'.$disk_space_text.'</em></span> <a class="updraft_diskspaceused_update" href="#">'.$refresh_link_text.'</a></li>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up temporary files found in the updraft directory (and some in the site root - pclzip)
|
||||
* Always cleans up temporary files over 12 hours old.
|
||||
* With parameters, also cleans up those.
|
||||
* Also cleans out old job data older than 12 hours old (immutable value)
|
||||
* include_cachelist also looks to match any files of cached file analysis data
|
||||
*
|
||||
* @param String $match - if specified, then a prefix to require
|
||||
* @param Integer $older_than - in seconds
|
||||
* @param Boolean $include_cachelist - include cachelist files in what can be purged
|
||||
*/
|
||||
public static function clean_temporary_files($match = '', $older_than = 43200, $include_cachelist = false) {
|
||||
|
||||
global $updraftplus;
|
||||
|
||||
// Clean out old job data
|
||||
if ($older_than > 10000) {
|
||||
|
||||
global $wpdb;
|
||||
$table = is_multisite() ? $wpdb->sitemeta : $wpdb->options;
|
||||
$key_column = is_multisite() ? 'meta_key' : 'option_name';
|
||||
$value_column = is_multisite() ? 'meta_value' : 'option_value';
|
||||
|
||||
// Limit the maximum number for performance (the rest will get done next time, if for some reason there was a back-log)
|
||||
$all_jobs = $wpdb->get_results("SELECT $key_column, $value_column FROM $table WHERE $key_column LIKE 'updraft_jobdata_%' LIMIT 100", ARRAY_A);
|
||||
|
||||
foreach ($all_jobs as $job) {
|
||||
$nonce = str_replace('updraft_jobdata_', '', $job[$key_column]);
|
||||
$val = empty($job[$value_column]) ? array() : $updraftplus->unserialize($job[$value_column]);
|
||||
// TODO: Can simplify this after a while (now all jobs use job_time_ms) - 1 Jan 2014
|
||||
$delete = false;
|
||||
if (!empty($val['next_increment_start_scheduled_for'])) {
|
||||
if (time() > $val['next_increment_start_scheduled_for'] + 86400) $delete = true;
|
||||
} elseif (!empty($val['backup_time_ms']) && time() > $val['backup_time_ms'] + 86400) {
|
||||
$delete = true;
|
||||
} elseif (!empty($val['job_time_ms']) && time() > $val['job_time_ms'] + 86400) {
|
||||
$delete = true;
|
||||
} elseif (!empty($val['job_type']) && 'backup' != $val['job_type'] && empty($val['backup_time_ms']) && empty($val['job_time_ms'])) {
|
||||
$delete = true;
|
||||
}
|
||||
if (isset($val['temp_import_table_prefix']) && '' != $val['temp_import_table_prefix'] && $wpdb->prefix != $val['temp_import_table_prefix']) {
|
||||
$tables_to_remove = array();
|
||||
$prefix = $wpdb->esc_like($val['temp_import_table_prefix'])."%";
|
||||
$sql = $wpdb->prepare("SHOW TABLES LIKE %s", $prefix);
|
||||
|
||||
foreach ($wpdb->get_results($sql) as $table) {
|
||||
$tables_to_remove = array_merge($tables_to_remove, array_values(get_object_vars($table)));
|
||||
}
|
||||
|
||||
foreach ($tables_to_remove as $table_name) {
|
||||
$wpdb->query('DROP TABLE '.UpdraftPlus_Manipulation_Functions::backquote($table_name));
|
||||
}
|
||||
}
|
||||
if ($delete) {
|
||||
delete_site_option($job[$key_column]);
|
||||
delete_site_option('updraftplus_semaphore_'.$nonce);
|
||||
}
|
||||
}
|
||||
}
|
||||
$updraft_dir = $updraftplus->backups_dir_location();
|
||||
$now_time = time();
|
||||
$files_deleted = 0;
|
||||
$include_cachelist = defined('DOING_CRON') && DOING_CRON && doing_action('updraftplus_clean_temporary_files') ? true : $include_cachelist;
|
||||
if ($handle = opendir($updraft_dir)) {
|
||||
while (false !== ($entry = readdir($handle))) {
|
||||
$manifest_match = preg_match("/updraftplus-manifest\.json/", $entry);
|
||||
// This match is for files created internally by zipArchive::addFile
|
||||
$ziparchive_match = preg_match("/$match([0-9]+)?\.zip\.tmp\.(?:[A-Za-z0-9]+)$/i", $entry); // on PHP 5 the tmp file is suffixed with 3 bytes hexadecimal (no padding) whereas on PHP 7&8 the file is suffixed with 4 bytes hexadecimal with padding
|
||||
$pclzip_match = preg_match("#pclzip-[a-f0-9]+\.(?:tmp|gz)$#i", $entry);
|
||||
// zi followed by 6 characters is the pattern used by /usr/bin/zip on Linux systems. It's safe to check for, as we have nothing else that's going to match that pattern.
|
||||
$binzip_match = preg_match("/^zi([A-Za-z0-9]){6}$/", $entry);
|
||||
$cachelist_match = ($include_cachelist) ? preg_match("/-cachelist-.*(?:info|\.tmp)$/i", $entry) : false;
|
||||
$browserlog_match = preg_match('/^log\.[0-9a-f]+-browser\.txt$/', $entry);
|
||||
$downloader_client_match = preg_match("/$match([0-9]+)?\.zip\.tmp\.(?:[A-Za-z0-9]+)\.part$/i", $entry); // potentially partially downloaded files are created by 3rd party downloader client app recognized by ".part" extension at the end of the backup file name (e.g. .zip.tmp.3b9r8r.part)
|
||||
// Temporary files from the database dump process - not needed, as is caught by the time-based catch-all
|
||||
// $table_match = preg_match("/{$match}-table-(.*)\.table(\.tmp)?\.gz$/i", $entry);
|
||||
// The gz goes in with the txt, because we *don't* want to reap the raw .txt files
|
||||
if ((preg_match("/$match\.(tmp|table|txt\.gz)(\.gz)?$/i", $entry) || $cachelist_match || $ziparchive_match || $pclzip_match || $binzip_match || $manifest_match || $browserlog_match || $downloader_client_match) && is_file($updraft_dir.'/'.$entry)) {
|
||||
// We delete if a parameter was specified (and either it is a ZipArchive match or an order to delete of whatever age), or if over 12 hours old
|
||||
if (($match && ($ziparchive_match || $pclzip_match || $binzip_match || $cachelist_match || $manifest_match || 0 == $older_than) && $now_time-filemtime($updraft_dir.'/'.$entry) >= $older_than) || $now_time-filemtime($updraft_dir.'/'.$entry)>43200) {
|
||||
$skip_dblog = (0 == $files_deleted % 25) ? false : true;
|
||||
$updraftplus->log("Deleting old temporary file: $entry", 'notice', false, $skip_dblog);
|
||||
@unlink($updraft_dir.'/'.$entry);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise if the file doesn't exist.
|
||||
$files_deleted++;
|
||||
}
|
||||
} elseif (preg_match('/^log\.[0-9a-f]+\.txt$/', $entry) && $now_time-filemtime($updraft_dir.'/'.$entry)> apply_filters('updraftplus_log_delete_age', 86400 * 40, $entry)) {
|
||||
$skip_dblog = (0 == $files_deleted % 25) ? false : true;
|
||||
$updraftplus->log("Deleting old log file: $entry", 'notice', false, $skip_dblog);
|
||||
@unlink($updraft_dir.'/'.$entry);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise if the file doesn't exist.
|
||||
$files_deleted++;
|
||||
}
|
||||
}
|
||||
@closedir($handle);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
|
||||
}
|
||||
|
||||
// Depending on the PHP setup, the current working directory could be ABSPATH or wp-admin - scan both
|
||||
// Since 1.9.32, we set them to go into $updraft_dir, so now we must check there too. Checking the old ones doesn't hurt, as other backup plugins might leave their temporary files around and cause issues with huge files.
|
||||
foreach (array(ABSPATH, ABSPATH.'wp-admin/', $updraft_dir.'/') as $path) {
|
||||
if ($handle = opendir($path)) {
|
||||
while (false !== ($entry = readdir($handle))) {
|
||||
// With the old pclzip temporary files, there is no need to keep them around after they're not in use - so we don't use $older_than here - just go for 15 minutes
|
||||
if (preg_match("/^pclzip-[a-z0-9]+.tmp$/", $entry) && $now_time-filemtime($path.$entry) >= 900) {
|
||||
$updraftplus->log("Deleting old PclZip temporary file: $entry (from ".basename($path).")");
|
||||
@unlink($path.$entry);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise if the file doesn't exist.
|
||||
}
|
||||
}
|
||||
@closedir($handle);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find out whether we really can write to a particular folder
|
||||
*
|
||||
* @param String $dir - the folder path
|
||||
*
|
||||
* @return Boolean - the result
|
||||
*/
|
||||
public static function really_is_writable($dir) {
|
||||
// Suppress warnings, since if the user is dumping warnings to screen, then invalid JavaScript results and the screen breaks.
|
||||
if (!@is_writable($dir)) return false;// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
|
||||
// Found a case - GoDaddy server, Windows, PHP 5.2.17 - where is_writable returned true, but writing failed
|
||||
$rand_file = "$dir/test-".md5(rand().time()).".txt";
|
||||
while (file_exists($rand_file)) {
|
||||
$rand_file = "$dir/test-".md5(rand().time()).".txt";
|
||||
}
|
||||
$ret = @file_put_contents($rand_file, 'testing...');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
|
||||
@unlink($rand_file);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise if the file doesn't exist.
|
||||
return ($ret > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a directory from the local filesystem
|
||||
*
|
||||
* @param String $dir - the directory
|
||||
* @param Boolean $contents_only - if set to true, then do not remove the directory, but only empty it of contents
|
||||
*
|
||||
* @return Boolean - success/failure
|
||||
*/
|
||||
public static function remove_local_directory($dir, $contents_only = false) {
|
||||
// PHP 5.3+ only
|
||||
// foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST) as $path) {
|
||||
// $path->isFile() ? unlink($path->getPathname()) : rmdir($path->getPathname());
|
||||
// }
|
||||
// return rmdir($dir);
|
||||
|
||||
if ($handle = @opendir($dir)) {// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
|
||||
while (false !== ($entry = readdir($handle))) {
|
||||
if ('.' !== $entry && '..' !== $entry) {
|
||||
if (is_dir($dir.'/'.$entry)) {
|
||||
self::remove_local_directory($dir.'/'.$entry, false);
|
||||
} else {
|
||||
@unlink($dir.'/'.$entry);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise if the file doesn't exist.
|
||||
}
|
||||
}
|
||||
}
|
||||
@closedir($handle);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
|
||||
}
|
||||
|
||||
return $contents_only ? true : rmdir($dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform gzopen(), but with various extra bits of help for potential problems
|
||||
*
|
||||
* @param String $file - the filesystem path
|
||||
* @param Array $warn - warnings
|
||||
* @param Array $err - errors
|
||||
*
|
||||
* @return Boolean|Resource - returns false upon failure, otherwise the handle as from gzopen()
|
||||
*/
|
||||
public static function gzopen_for_read($file, &$warn, &$err) {
|
||||
if (!function_exists('gzopen') || !function_exists('gzread')) {
|
||||
$missing = '';
|
||||
if (!function_exists('gzopen')) $missing .= 'gzopen';
|
||||
if (!function_exists('gzread')) $missing .= ($missing) ? ', gzread' : 'gzread';
|
||||
$err[] = sprintf(__("Your web server's PHP installation has these functions disabled: %s.", 'updraftplus'), $missing).' '.sprintf(__('Your hosting company must enable these functions before %s can work.', 'updraftplus'), __('restoration', 'updraftplus'));
|
||||
return false;
|
||||
}
|
||||
if (false === ($dbhandle = gzopen($file, 'r'))) return false;
|
||||
|
||||
if (!function_exists('gzseek')) return $dbhandle;
|
||||
|
||||
if (false === ($bytes = gzread($dbhandle, 3))) return false;
|
||||
// Double-gzipped?
|
||||
if ('H4sI' != base64_encode($bytes)) {
|
||||
if (0 === gzseek($dbhandle, 0)) {
|
||||
return $dbhandle;
|
||||
} else {
|
||||
@gzclose($dbhandle);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
|
||||
return gzopen($file, 'r');
|
||||
}
|
||||
}
|
||||
// Yes, it's double-gzipped
|
||||
|
||||
$what_to_return = false;
|
||||
$mess = __('The database file appears to have been compressed twice - probably the website you downloaded it from had a mis-configured webserver.', 'updraftplus');
|
||||
$messkey = 'doublecompress';
|
||||
$err_msg = '';
|
||||
|
||||
if (false === ($fnew = fopen($file.".tmp", 'w')) || !is_resource($fnew)) {
|
||||
|
||||
@gzclose($dbhandle);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
|
||||
$err_msg = __('The attempt to undo the double-compression failed.', 'updraftplus');
|
||||
|
||||
} else {
|
||||
|
||||
@fwrite($fnew, $bytes);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
|
||||
$emptimes = 0;
|
||||
while (!gzeof($dbhandle)) {
|
||||
$bytes = @gzread($dbhandle, 262144);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
|
||||
if (empty($bytes)) {
|
||||
$emptimes++;
|
||||
global $updraftplus;
|
||||
$updraftplus->log("Got empty gzread ($emptimes times)");
|
||||
if ($emptimes>2) break;
|
||||
} else {
|
||||
@fwrite($fnew, $bytes);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
|
||||
}
|
||||
}
|
||||
|
||||
gzclose($dbhandle);
|
||||
fclose($fnew);
|
||||
// On some systems (all Windows?) you can't rename a gz file whilst it's gzopened
|
||||
if (!rename($file.".tmp", $file)) {
|
||||
$err_msg = __('The attempt to undo the double-compression failed.', 'updraftplus');
|
||||
} else {
|
||||
$mess .= ' '.__('The attempt to undo the double-compression succeeded.', 'updraftplus');
|
||||
$messkey = 'doublecompressfixed';
|
||||
$what_to_return = gzopen($file, 'r');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$warn[$messkey] = $mess;
|
||||
if (!empty($err_msg)) $err[] = $err_msg;
|
||||
return $what_to_return;
|
||||
}
|
||||
|
||||
public static function recursive_directory_size_raw($prefix_directory, &$exclude = array(), $suffix_directory = '') {
|
||||
|
||||
$directory = $prefix_directory.('' == $suffix_directory ? '' : '/'.$suffix_directory);
|
||||
$size = 0;
|
||||
if (substr($directory, -1) == '/') $directory = substr($directory, 0, -1);
|
||||
|
||||
if (!file_exists($directory) || !is_dir($directory) || !is_readable($directory)) return -1;
|
||||
if (file_exists($directory.'/.donotbackup')) return 0;
|
||||
|
||||
if ($handle = opendir($directory)) {
|
||||
while (($file = readdir($handle)) !== false) {
|
||||
if ('.' != $file && '..' != $file) {
|
||||
$spath = ('' == $suffix_directory) ? $file : $suffix_directory.'/'.$file;
|
||||
if (false !== ($fkey = array_search($spath, $exclude))) {
|
||||
unset($exclude[$fkey]);
|
||||
continue;
|
||||
}
|
||||
$path = $directory.'/'.$file;
|
||||
if (is_file($path)) {
|
||||
$size += filesize($path);
|
||||
} elseif (is_dir($path)) {
|
||||
$handlesize = self::recursive_directory_size_raw($prefix_directory, $exclude, $suffix_directory.('' == $suffix_directory ? '' : '/').$file);
|
||||
if ($handlesize >= 0) {
|
||||
$size += $handlesize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir($handle);
|
||||
}
|
||||
|
||||
return $size;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information on disk space used by an entity, or by UD's internal directory. Returns as a human-readable string.
|
||||
*
|
||||
* @param String $entity - the entity (e.g. 'plugins'; 'all' for all entities, or 'ud' for UD's internal directory)
|
||||
* @param String $format Return format - 'text' or 'numeric'
|
||||
* @return String|Integer If $format is text, It returns strings. Otherwise integer value.
|
||||
*/
|
||||
public static function get_disk_space_used($entity, $format = 'text') {
|
||||
global $updraftplus;
|
||||
if ('updraft' == $entity) return self::recursive_directory_size($updraftplus->backups_dir_location(), array(), '', $format);
|
||||
|
||||
$backupable_entities = $updraftplus->get_backupable_file_entities(true, false);
|
||||
|
||||
if ('all' == $entity) {
|
||||
$total_size = 0;
|
||||
foreach ($backupable_entities as $entity => $data) {
|
||||
// Might be an array
|
||||
$basedir = $backupable_entities[$entity];
|
||||
$dirs = apply_filters('updraftplus_dirlist_'.$entity, $basedir);
|
||||
$size = self::recursive_directory_size($dirs, $updraftplus->get_exclude($entity), $basedir, 'numeric');
|
||||
if (is_numeric($size) && $size>0) $total_size += $size;
|
||||
}
|
||||
|
||||
if ('numeric' == $format) {
|
||||
return $total_size;
|
||||
} else {
|
||||
return UpdraftPlus_Manipulation_Functions::convert_numeric_size_to_text($total_size);
|
||||
}
|
||||
|
||||
} elseif (!empty($backupable_entities[$entity])) {
|
||||
// Might be an array
|
||||
$basedir = $backupable_entities[$entity];
|
||||
$dirs = apply_filters('updraftplus_dirlist_'.$entity, $basedir);
|
||||
return self::recursive_directory_size($dirs, $updraftplus->get_exclude($entity), $basedir, $format);
|
||||
}
|
||||
|
||||
// Default fallback
|
||||
return apply_filters('updraftplus_get_disk_space_used_none', __('Error', 'updraftplus'), $entity, $backupable_entities);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unzips a specified ZIP file to a location on the filesystem via the WordPress
|
||||
* Filesystem Abstraction. Forked from WordPress core in version 5.1-alpha-44182,
|
||||
* to allow us to provide feedback on progress.
|
||||
*
|
||||
* Assumes that WP_Filesystem() has already been called and set up. Does not extract
|
||||
* a root-level __MACOSX directory, if present.
|
||||
*
|
||||
* Attempts to increase the PHP memory limit before uncompressing. However,
|
||||
* the most memory required shouldn't be much larger than the archive itself.
|
||||
*
|
||||
* @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
|
||||
*
|
||||
* @param String $file - Full path and filename of ZIP archive.
|
||||
* @param String $to - Full path on the filesystem to extract archive to.
|
||||
* @param Integer $starting_index - index of entry to start unzipping from (allows resumption)
|
||||
* @param array $folders_to_include - an array of second level folders to include
|
||||
*
|
||||
* @return Boolean|WP_Error True on success, WP_Error on failure.
|
||||
*/
|
||||
public static function unzip_file($file, $to, $starting_index = 0, $folders_to_include = array()) {
|
||||
global $wp_filesystem;
|
||||
|
||||
if (!$wp_filesystem || !is_object($wp_filesystem)) {
|
||||
return new WP_Error('fs_unavailable', __('Could not access filesystem.'));
|
||||
}
|
||||
|
||||
// Unzip can use a lot of memory, but not this much hopefully.
|
||||
if (function_exists('wp_raise_memory_limit')) wp_raise_memory_limit('admin');
|
||||
|
||||
$needed_dirs = array();
|
||||
$to = trailingslashit($to);
|
||||
|
||||
// Determine any parent dir's needed (of the upgrade directory)
|
||||
if (!$wp_filesystem->is_dir($to)) { // Only do parents if no children exist
|
||||
$path = preg_split('![/\\\]!', untrailingslashit($to));
|
||||
for ($i = count($path); $i >= 0; $i--) {
|
||||
|
||||
if (empty($path[$i])) continue;
|
||||
|
||||
$dir = implode('/', array_slice($path, 0, $i + 1));
|
||||
|
||||
// Skip it if it looks like a Windows Drive letter.
|
||||
if (preg_match('!^[a-z]:$!i', $dir)) continue;
|
||||
|
||||
// A folder exists; therefore, we don't need the check the levels below this
|
||||
if ($wp_filesystem->is_dir($dir)) break;
|
||||
|
||||
$needed_dirs[] = $dir;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static $added_unzip_action = false;
|
||||
if (!$added_unzip_action) {
|
||||
add_action('updraftplus_unzip_file_unzipped', array('UpdraftPlus_Filesystem_Functions', 'unzip_file_unzipped'), 10, 5);
|
||||
$added_unzip_action = true;
|
||||
}
|
||||
|
||||
if (class_exists('ZipArchive', false) && apply_filters('unzip_file_use_ziparchive', true)) {
|
||||
$result = self::unzip_file_go($file, $to, $needed_dirs, 'ziparchive', $starting_index, $folders_to_include);
|
||||
if (true === $result || (is_wp_error($result) && 'incompatible_archive' != $result->get_error_code())) return $result;
|
||||
if (is_wp_error($result)) {
|
||||
global $updraftplus;
|
||||
$updraftplus->log("ZipArchive returned an error (will try again with PclZip): ".$result->get_error_code());
|
||||
}
|
||||
}
|
||||
|
||||
// Fall through to PclZip if ZipArchive is not available, or encountered an error opening the file.
|
||||
// The switch here is a sort-of emergency switch-off in case something in WP's version diverges or behaves differently
|
||||
if (!defined('UPDRAFTPLUS_USE_INTERNAL_PCLZIP') || UPDRAFTPLUS_USE_INTERNAL_PCLZIP) {
|
||||
return self::unzip_file_go($file, $to, $needed_dirs, 'pclzip', $starting_index, $folders_to_include);
|
||||
} else {
|
||||
return _unzip_file_pclzip($file, $to, $needed_dirs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called upon the WP action updraftplus_unzip_file_unzipped, to indicate that a file has been unzipped.
|
||||
*
|
||||
* @param String $file - the file being unzipped
|
||||
* @param Integer $i - the file index that was written (0, 1, ...)
|
||||
* @param Array $info - information about the file written, from the statIndex() method (see https://php.net/manual/en/ziparchive.statindex.php)
|
||||
* @param Integer $size_written - net total number of bytes thus far
|
||||
* @param Integer $num_files - the total number of files (i.e. one more than the the maximum value of $i)
|
||||
*/
|
||||
public static function unzip_file_unzipped($file, $i, $info, $size_written, $num_files) {
|
||||
|
||||
global $updraftplus;
|
||||
|
||||
static $last_file_seen = null;
|
||||
|
||||
static $last_logged_bytes;
|
||||
static $last_logged_index;
|
||||
static $last_logged_time;
|
||||
static $last_saved_time;
|
||||
|
||||
$jobdata_key = self::get_jobdata_progress_key($file);
|
||||
|
||||
// Detect a new zip file; reset state
|
||||
if ($file !== $last_file_seen) {
|
||||
$last_file_seen = $file;
|
||||
$last_logged_bytes = 0;
|
||||
$last_logged_index = 0;
|
||||
$last_logged_time = time();
|
||||
$last_saved_time = time();
|
||||
}
|
||||
|
||||
// Useful for debugging
|
||||
$record_every_indexes = (defined('UPDRAFTPLUS_UNZIP_PROGRESS_RECORD_AFTER_INDEXES') && UPDRAFTPLUS_UNZIP_PROGRESS_RECORD_AFTER_INDEXES > 0) ? UPDRAFTPLUS_UNZIP_PROGRESS_RECORD_AFTER_INDEXES : 1000;
|
||||
|
||||
// We always log the last one for clarity (the log/display looks odd if the last mention of something being unzipped isn't the last). Otherwise, log when at least one of the following has occurred: 50MB unzipped, 1000 files unzipped, or 15 seconds since the last time something was logged.
|
||||
if ($i >= $num_files -1 || $size_written > $last_logged_bytes + 100 * 1048576 || $i > $last_logged_index + $record_every_indexes || time() > $last_logged_time + 15) {
|
||||
|
||||
$updraftplus->jobdata_set($jobdata_key, array('index' => $i, 'info' => $info, 'size_written' => $size_written));
|
||||
|
||||
$updraftplus->log(sprintf(__('Unzip progress: %d out of %d files', 'updraftplus').' (%s, %s)', $i+1, $num_files, UpdraftPlus_Manipulation_Functions::convert_numeric_size_to_text($size_written), $info['name']), 'notice-restore');
|
||||
$updraftplus->log(sprintf('Unzip progress: %d out of %d files (%s, %s)', $i+1, $num_files, UpdraftPlus_Manipulation_Functions::convert_numeric_size_to_text($size_written), $info['name']), 'notice');
|
||||
|
||||
do_action('updraftplus_unzip_progress_restore_info', $file, $i, $size_written, $num_files);
|
||||
|
||||
$last_logged_bytes = $size_written;
|
||||
$last_logged_index = $i;
|
||||
$last_logged_time = time();
|
||||
$last_saved_time = time();
|
||||
}
|
||||
|
||||
// Because a lot can happen in 5 seconds, we update the job data more often
|
||||
if (time() > $last_saved_time + 5) {
|
||||
// N.B. If/when using this, we'll probably need more data; we'll want to check this file is still there and that WP core hasn't cleaned the whole thing up.
|
||||
$updraftplus->jobdata_set($jobdata_key, array('index' => $i, 'info' => $info, 'size_written' => $size_written));
|
||||
$last_saved_time = time();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method abstracts the calculation for a consistent jobdata key name for the indicated name
|
||||
*
|
||||
* @param String $file - the filename; only the basename will be used
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public static function get_jobdata_progress_key($file) {
|
||||
return 'last_index_'.md5(basename($file));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compatibility function (exists in WP 4.8+)
|
||||
*/
|
||||
public static function wp_doing_cron() {
|
||||
if (function_exists('wp_doing_cron')) return wp_doing_cron();
|
||||
return apply_filters('wp_doing_cron', defined('DOING_CRON') && DOING_CRON);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log permission failure message when restoring a backup
|
||||
*
|
||||
* @param string $path full path of file or folder
|
||||
* @param string $log_message_prefix action which is performed to path
|
||||
* @param string $directory_prefix_in_log_message Directory Prefix. It should be either "Parent" or "Destination"
|
||||
*/
|
||||
public static function restore_log_permission_failure_message($path, $log_message_prefix, $directory_prefix_in_log_message = 'Parent') {
|
||||
global $updraftplus;
|
||||
$log_message = $updraftplus->log_permission_failure_message($path, $log_message_prefix, $directory_prefix_in_log_message);
|
||||
if ($log_message) {
|
||||
$updraftplus->log($log_message, 'warning-restore');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively copies files using the WP_Filesystem API and $wp_filesystem global from a source to a destination directory, optionally removing the source after a successful copy.
|
||||
*
|
||||
* @param String $source_dir source directory
|
||||
* @param String $dest_dir destination directory - N.B. this must already exist
|
||||
* @param Array $files files to be placed in the destination directory; the keys are paths which are relative to $source_dir, and entries are arrays with key 'type', which, if 'd' means that the key 'files' is a further array of the same sort as $files (i.e. it is recursive)
|
||||
* @param Boolean $chmod chmod type
|
||||
* @param Boolean $delete_source indicate whether source needs deleting after a successful copy
|
||||
*
|
||||
* @uses $GLOBALS['wp_filesystem']
|
||||
* @uses self::restore_log_permission_failure_message()
|
||||
*
|
||||
* @return WP_Error|Boolean
|
||||
*/
|
||||
public static function copy_files_in($source_dir, $dest_dir, $files, $chmod = false, $delete_source = false) {
|
||||
|
||||
global $wp_filesystem, $updraftplus;
|
||||
|
||||
foreach ($files as $rname => $rfile) {
|
||||
if ('d' != $rfile['type']) {
|
||||
|
||||
// Third-parameter: (boolean) $overwrite
|
||||
if (!$wp_filesystem->move($source_dir.'/'.$rname, $dest_dir.'/'.$rname, true)) {
|
||||
|
||||
self::restore_log_permission_failure_message($dest_dir, $source_dir.'/'.$rname.' -> '.$dest_dir.'/'.$rname, 'Destination');
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
// $rfile['type'] is 'd'
|
||||
|
||||
// Attempt to remove any already-existing file with the same name
|
||||
if ($wp_filesystem->is_file($dest_dir.'/'.$rname)) @$wp_filesystem->delete($dest_dir.'/'.$rname, false, 'f');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- if fails, carry on
|
||||
|
||||
// No such directory yet: just move it
|
||||
if ($wp_filesystem->exists($dest_dir.'/'.$rname) && !$wp_filesystem->is_dir($dest_dir.'/'.$rname) && !$wp_filesystem->move($source_dir.'/'.$rname, $dest_dir.'/'.$rname, false)) {
|
||||
|
||||
self::restore_log_permission_failure_message($dest_dir, 'Move '.$source_dir.'/'.$rname.' -> '.$dest_dir.'/'.$rname, 'Destination');
|
||||
$updraftplus->log_e('Failed to move directory (check your file permissions and disk quota): %s', $source_dir.'/'.$rname." -> ".$dest_dir.'/'.$rname);
|
||||
|
||||
return false;
|
||||
|
||||
} elseif (!empty($rfile['files'])) {
|
||||
|
||||
if (!$wp_filesystem->exists($dest_dir.'/'.$rname)) $wp_filesystem->mkdir($dest_dir.'/'.$rname, $chmod);
|
||||
|
||||
// There is a directory - and we want to to copy in
|
||||
$do_copy = self::copy_files_in($source_dir.'/'.$rname, $dest_dir.'/'.$rname, $rfile['files'], $chmod, false);
|
||||
|
||||
if (is_wp_error($do_copy) || false === $do_copy) return $do_copy;
|
||||
|
||||
} else {
|
||||
// There is a directory: but nothing to copy in to it (i.e. $file['files'] is empty). Just remove the directory.
|
||||
@$wp_filesystem->rmdir($source_dir.'/'.$rname);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the method.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We are meant to leave the working directory empty. Hence, need to rmdir() once a directory is empty. But not the root of it all in case of others/wpcore.
|
||||
if ($delete_source || false !== strpos($source_dir, '/')) {
|
||||
if (!$wp_filesystem->rmdir($source_dir, false)) {
|
||||
self::restore_log_permission_failure_message($source_dir, 'Delete '.$source_dir);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to unzip an archive; forked from _unzip_file_ziparchive() in WordPress 5.1-alpha-44182, and modified to use the UD zip classes.
|
||||
*
|
||||
* Assumes that WP_Filesystem() has already been called and set up.
|
||||
*
|
||||
* @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
|
||||
*
|
||||
* @param String $file - full path and filename of ZIP archive.
|
||||
* @param String $to - full path on the filesystem to extract archive to.
|
||||
* @param Array $needed_dirs - a partial list of required folders needed to be created.
|
||||
* @param String $method - either 'ziparchive' or 'pclzip'.
|
||||
* @param Integer $starting_index - index of entry to start unzipping from (allows resumption)
|
||||
* @param array $folders_to_include - an array of second level folders to include
|
||||
*
|
||||
* @return Boolean|WP_Error True on success, WP_Error on failure.
|
||||
*/
|
||||
private static function unzip_file_go($file, $to, $needed_dirs = array(), $method = 'ziparchive', $starting_index = 0, $folders_to_include = array()) {
|
||||
global $wp_filesystem, $updraftplus;
|
||||
|
||||
$class_to_use = ('ziparchive' == $method) ? 'UpdraftPlus_ZipArchive' : 'UpdraftPlus_PclZip';
|
||||
|
||||
if (!class_exists($class_to_use)) updraft_try_include_file('includes/class-zip.php', 'require_once');
|
||||
|
||||
$updraftplus->log('Unzipping '.basename($file).' to '.$to.' using '.$class_to_use.', starting index '.$starting_index);
|
||||
|
||||
$z = new $class_to_use;
|
||||
|
||||
$flags = (version_compare(PHP_VERSION, '5.2.12', '>') && defined('ZIPARCHIVE::CHECKCONS')) ? ZIPARCHIVE::CHECKCONS : 4;
|
||||
|
||||
// This is just for crazy people with mbstring.func_overload enabled (deprecated from PHP 7.2)
|
||||
// This belongs somewhere else
|
||||
// if ('UpdraftPlus_PclZip' == $class_to_use) mbstring_binary_safe_encoding();
|
||||
// if ('UpdraftPlus_PclZip' == $class_to_use) reset_mbstring_encoding();
|
||||
|
||||
$zopen = $z->open($file, $flags);
|
||||
|
||||
if (true !== $zopen) {
|
||||
return new WP_Error('incompatible_archive', __('Incompatible Archive.'), array($method.'_error' => $z->last_error));
|
||||
}
|
||||
|
||||
$uncompressed_size = 0;
|
||||
|
||||
$num_files = $z->numFiles;
|
||||
|
||||
if (false === $num_files) return new WP_Error('incompatible_archive', __('Incompatible Archive.'), array($method.'_error' => $z->last_error));
|
||||
|
||||
for ($i = $starting_index; $i < $num_files; $i++) {
|
||||
if (!$info = $z->statIndex($i)) {
|
||||
return new WP_Error('stat_failed_'.$method, __('Could not retrieve file from archive.').' ('.$z->last_error.')');
|
||||
}
|
||||
|
||||
// Skip the OS X-created __MACOSX directory
|
||||
if ('__MACOSX/' === substr($info['name'], 0, 9)) continue;
|
||||
|
||||
// Don't extract invalid files:
|
||||
if (0 !== validate_file($info['name'])) continue;
|
||||
|
||||
if (!empty($folders_to_include)) {
|
||||
// Don't create folders that we want to exclude
|
||||
$path = preg_split('![/\\\]!', untrailingslashit($info['name']));
|
||||
if (isset($path[1]) && !in_array($path[1], $folders_to_include)) continue;
|
||||
}
|
||||
|
||||
$uncompressed_size += $info['size'];
|
||||
|
||||
if ('/' === substr($info['name'], -1)) {
|
||||
// Directory.
|
||||
$needed_dirs[] = $to . untrailingslashit($info['name']);
|
||||
} elseif ('.' !== ($dirname = dirname($info['name']))) {
|
||||
// Path to a file.
|
||||
$needed_dirs[] = $to . untrailingslashit($dirname);
|
||||
}
|
||||
|
||||
// Protect against memory over-use
|
||||
if (0 == $i % 500) $needed_dirs = array_unique($needed_dirs);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* disk_free_space() could return false. Assume that any falsey value is an error.
|
||||
* A disk that has zero free bytes has bigger problems.
|
||||
* Require we have enough space to unzip the file and copy its contents, with a 10% buffer.
|
||||
*/
|
||||
if (self::wp_doing_cron()) {
|
||||
$available_space = function_exists('disk_free_space') ? @disk_free_space(WP_CONTENT_DIR) : false;// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Call is speculative
|
||||
if ($available_space && ($uncompressed_size * 2.1) > $available_space) {
|
||||
return new WP_Error('disk_full_unzip_file', __('Could not copy files.', 'updraftplus').' '.__('You may have run out of disk space.'), compact('uncompressed_size', 'available_space'));
|
||||
}
|
||||
}
|
||||
|
||||
$needed_dirs = array_unique($needed_dirs);
|
||||
foreach ($needed_dirs as $dir) {
|
||||
// Check the parent folders of the folders all exist within the creation array.
|
||||
if (untrailingslashit($to) == $dir) {
|
||||
// Skip over the working directory, We know this exists (or will exist)
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the directory is not within the working directory then skip it
|
||||
if (false === strpos($dir, $to)) continue;
|
||||
|
||||
$parent_folder = dirname($dir);
|
||||
while (!empty($parent_folder) && untrailingslashit($to) != $parent_folder && !in_array($parent_folder, $needed_dirs)) {
|
||||
$needed_dirs[] = $parent_folder;
|
||||
$parent_folder = dirname($parent_folder);
|
||||
}
|
||||
}
|
||||
asort($needed_dirs);
|
||||
|
||||
// Create those directories if need be:
|
||||
foreach ($needed_dirs as $_dir) {
|
||||
// Only check to see if the Dir exists upon creation failure. Less I/O this way.
|
||||
if (!$wp_filesystem->mkdir($_dir, FS_CHMOD_DIR) && !$wp_filesystem->is_dir($_dir)) {
|
||||
return new WP_Error('mkdir_failed_'.$method, __('Could not create directory.'), substr($_dir, strlen($to)));
|
||||
}
|
||||
}
|
||||
unset($needed_dirs);
|
||||
|
||||
$size_written = 0;
|
||||
|
||||
$content_cache = array();
|
||||
$content_cache_highest = -1;
|
||||
|
||||
for ($i = $starting_index; $i < $num_files; $i++) {
|
||||
|
||||
if (!$info = $z->statIndex($i)) {
|
||||
return new WP_Error('stat_failed_'.$method, __('Could not retrieve file from archive.'));
|
||||
}
|
||||
|
||||
// directory
|
||||
if ('/' == substr($info['name'], -1)) continue;
|
||||
|
||||
// Don't extract the OS X-created __MACOSX
|
||||
if ('__MACOSX/' === substr($info['name'], 0, 9)) continue;
|
||||
|
||||
// Don't extract invalid files:
|
||||
if (0 !== validate_file($info['name'])) continue;
|
||||
|
||||
if (!empty($folders_to_include)) {
|
||||
// Don't extract folders that we want to exclude
|
||||
$path = preg_split('![/\\\]!', untrailingslashit($info['name']));
|
||||
if (isset($path[1]) && !in_array($path[1], $folders_to_include)) continue;
|
||||
}
|
||||
|
||||
// N.B. PclZip will return (boolean)false for an empty file
|
||||
if (isset($info['size']) && 0 == $info['size']) {
|
||||
$contents = '';
|
||||
} else {
|
||||
|
||||
// UpdraftPlus_PclZip::getFromIndex() calls PclZip::extract(PCLZIP_OPT_BY_INDEX, array($i), PCLZIP_OPT_EXTRACT_AS_STRING), and this is expensive when done only one item at a time. We try to cache in chunks for good performance as well as being able to resume.
|
||||
if ($i > $content_cache_highest && 'UpdraftPlus_PclZip' == $class_to_use) {
|
||||
|
||||
$memory_usage = memory_get_usage(false);
|
||||
$total_memory = $updraftplus->memory_check_current();
|
||||
|
||||
if ($memory_usage > 0 && $total_memory > 0) {
|
||||
$memory_free = $total_memory*1048576 - $memory_usage;
|
||||
} else {
|
||||
// A sane default. Anything is ultimately better than WP's default of just unzipping everything into memory.
|
||||
$memory_free = 50*1048576;
|
||||
}
|
||||
|
||||
$use_memory = max(10485760, $memory_free - 10485760);
|
||||
|
||||
$total_byte_count = 0;
|
||||
$content_cache = array();
|
||||
$cache_indexes = array();
|
||||
|
||||
$cache_index = $i;
|
||||
while ($cache_index < $num_files && $total_byte_count < $use_memory) {
|
||||
if (false !== ($cinfo = $z->statIndex($cache_index)) && isset($cinfo['size']) && '/' != substr($cinfo['name'], -1) && '__MACOSX/' !== substr($cinfo['name'], 0, 9) && 0 === validate_file($cinfo['name'])) {
|
||||
$total_byte_count += $cinfo['size'];
|
||||
if ($total_byte_count < $use_memory) {
|
||||
$cache_indexes[] = $cache_index;
|
||||
$content_cache_highest = $cache_index;
|
||||
}
|
||||
}
|
||||
$cache_index++;
|
||||
}
|
||||
|
||||
if (!empty($cache_indexes)) {
|
||||
$content_cache = $z->updraftplus_getFromIndexBulk($cache_indexes);
|
||||
}
|
||||
}
|
||||
$contents = isset($content_cache[$i]) ? $content_cache[$i] : $z->getFromIndex($i);
|
||||
}
|
||||
|
||||
if (false === $contents && ('pclzip' !== $method || 0 !== $info['size'])) {
|
||||
return new WP_Error('extract_failed_'.$method, __('Could not extract file from archive.').' '.$z->last_error, json_encode($info));
|
||||
}
|
||||
|
||||
if (!$wp_filesystem->put_contents($to . $info['name'], $contents, FS_CHMOD_FILE)) {
|
||||
return new WP_Error('copy_failed_'.$method, __('Could not copy file.'), $info['name']);
|
||||
}
|
||||
|
||||
if (!empty($info['size'])) $size_written += $info['size'];
|
||||
|
||||
do_action('updraftplus_unzip_file_unzipped', $file, $i, $info, $size_written, $num_files);
|
||||
|
||||
}
|
||||
|
||||
$z->close();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
if (!defined('ABSPATH')) die('No direct access.');
|
||||
|
||||
class UpdraftPlus_HTTP_Error_Descriptions {
|
||||
|
||||
|
||||
/**
|
||||
* Get HTTP response code description
|
||||
*
|
||||
* @param String $http_error_code The http error code
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public static function get_http_status_code_description($http_error_code) {
|
||||
|
||||
$http_error_code_descriptions = array(
|
||||
|
||||
/*
|
||||
3xx redirection
|
||||
This class of status code indicates the client must take additional action to complete the request. Many of these status codes are used in URL redirection.
|
||||
A user agent may carry out the additional action with no user interaction only if the method used in the second request is GET or HEAD. A user agent may automatically redirect a request. A user agent should detect and intervene to prevent cyclical redirects.
|
||||
*/
|
||||
300 => __('Multiple Choices.', 'updraftplus').' '.__('Indicates multiple options for the resource from which the client may choose (via agent-driven content negotiation).', 'updraftplus'),
|
||||
301 => __('Moved Permanently.', 'updraftplus').' '.__('This and all future requests should be directed to the given URI.', 'updraftplus'),
|
||||
302 => __('Found (Previously "Moved temporarily").', 'updraftplus').' '.__('Tells the client to look at (browse to) another URL.', 'updraftplus'),
|
||||
303 => __('See Other.', 'updraftplus').' '.__('The response to the request can be found under another URI using the GET method.', 'updraftplus').' '.__('When received in response to a POST (or PUT/DELETE), the client should presume that the server has received the data and should issue a new GET request to the given URI', 'updraftplus'),
|
||||
304 => __('Not Modified.', 'updraftplus').' '.__('Indicates that the resource has not been modified since the version specified by the request headers If-Modified-Since or If-None-Match.', 'updraftplus'),
|
||||
305 => __('Use Proxy.', 'updraftplus').' '.__('The requested resource is available only through a proxy, the address for which is provided in the response.', 'updraftplus'),
|
||||
307 => __('Temporary Redirect.', 'updraftplus').' '.__('In this case, the request should be repeated with another URI; however, future requests should still use the original URI.', 'updraftplus'),
|
||||
308 => __('Permanent Redirect.', 'updraftplus').' '.__('This and all future requests should be directed to the given URI.', 'updraftplus'),
|
||||
|
||||
/*
|
||||
4xx client errors
|
||||
This class of status code is intended for situations in which the error seems to have been caused by the client. Except when responding to a HEAD request, the server should include an entity containing an explanation of the error situation, and whether it is a temporary or permanent condition. These status codes are applicable to any request method. User agents should display any included entity to the user.
|
||||
*/
|
||||
400 => __('Bad Request.', 'updraftplus').' '.__('The server cannot or will not process the request due to an apparent client error (e.g., malformed request syntax, size too large, invalid request message framing, or deceptive request routing).', 'updraftplus'),
|
||||
401 => __('Unauthorized.', 'updraftplus').' '.__('Authentication is required and has failed or has not yet been provided.', 'updraftplus'),
|
||||
403 => __('Forbidden.', 'updraftplus').' '.__('The request contained valid data and was understood by the server, but the server is refusing action.', 'updraftplus').' '.__('This may be due to the user not having the necessary permissions for a resource or needing an account of some sort, or attempting a prohibited action (e.g. creating a duplicate record where only one is allowed).', 'updraftplus'),
|
||||
404 => __('Not Found.', 'updraftplus').' '.__('The requested resource could not be found but may be available in the future', 'updraftplus').' '.__('Subsequent requests by the client are permissible.', 'updraftplus'),
|
||||
405 => __('Method Not Allowed', 'updraftplus').' '.__('A request method is not supported for the requested resource; for example, a GET request on a form that requires data to be presented via POST, or a PUT request on a read-only resource.', 'updraftplus'),
|
||||
406 => __('Not Acceptable', 'updraftplus').' '.__('The requested resource is capable of generating only content not acceptable according to the Accept headers sent in the request.', 'updraftplus'),
|
||||
407 => __('Proxy Authentication Required', 'updraftplus').' '.__('The client must first authenticate itself with the proxy.', 'updraftplus'),
|
||||
408 => __('Request Timeout', 'updraftplus').' '.__('The server timed out waiting for the request', 'updraftplus').' '.__('The client MAY repeat the request without modifications at any later time.', 'updraftplus'),
|
||||
409 => __('Conflict', 'updraftplus').' '.__('Indicates that the request could not be processed because of conflict in the current state of the resource, such as an edit conflict between multiple simultaneous updates.', 'updraftplus'),
|
||||
410 => __('Gone', 'updraftplus').' '.__('Indicates that the resource requested is no longer available and will not be available again.', 'updraftplus'),
|
||||
411 => __('Length Required', 'updraftplus').' '.__('The request did not specify the length of its content, which is required by the requested resource.', 'updraftplus'),
|
||||
412 => __('Precondition Failed', 'updraftplus').' '.__('The server does not meet one of the preconditions that the requester put on the request header fields.', 'updraftplus'),
|
||||
413 => __('Payload Too Large', 'updraftplus').' '.__('The request is larger than the server is willing or able to process', 'updraftplus').' '.__('Previously called "Request Entity Too Large".', 'updraftplus'),
|
||||
414 => __('URI Too Long', 'updraftplus').' '.__('The URI provided was too long for the server to process', 'updraftplus').' '.__('Often the result of too much data being encoded as a query-string of a GET request, in which it should be converted to a POST request.', 'updraftplus'),
|
||||
415 => __('Unsupported Media Type', 'updraftplus').' '.__('The request entity has a media type which the server or resource does not support', 'updraftplus').' '.__('For example, the client uploads an image as image/svg+xml, but the server requires that images use a different format.', 'updraftplus'),
|
||||
416 => __('Range Not Satisfiable', 'updraftplus').' '.__('The client has asked for a portion of the file (byte serving), but the server cannot supply that portion', 'updraftplus').' '.__('For example, if the client asked for a part of the file that lies beyond the end of the file.', 'updraftplus'),
|
||||
417 => __('Expectation Failed', 'updraftplus').' '.__('The server cannot meet the requirements of the Expect request-header field.', 'updraftplus'),
|
||||
421 => __('Misdirected Request', 'updraftplus').' '.__('The request was directed at a server that is not able to produce a response (for example because of connection reuse).', 'updraftplus'),
|
||||
422 => __('Unprocessable Entity', 'updraftplus').' '.__('The request was well-formed but was unable to be followed due to semantic errors.', 'updraftplus'),
|
||||
423 => __('Locked', 'updraftplus').' '.__('The resource that is being accessed is locked.', 'updraftplus'),
|
||||
424 => __('Failed Dependency', 'updraftplus').' '.__('The request failed because it depended on another request and that request failed (e.g., a PROPPATCH).', 'updraftplus'),
|
||||
425 => __('Too Early', 'updraftplus').' '.__('Indicates that the server is unwilling to risk processing a request that might be replayed.', 'updraftplus'),
|
||||
426 => __('Upgrade Required', 'updraftplus').' '.__('The client should switch to a different protocol such as TLS/1.3, given in the Upgrade header field.', 'updraftplus'),
|
||||
428 => __('Precondition Required', 'updraftplus').' '.__('The origin server requires the request to be conditional', 'updraftplus').' '.__('Intended to prevent the \'lost update\' problem, where a client GETs a resource\'s state, modifies it, and PUTs it back to the server, when meanwhile a third party has modified the state on the server, leading to a conflict.', 'updraftplus'),
|
||||
429 => __('Too Many Requests', 'updraftplus').' '.__('The user has sent too many requests in a given amount of time', 'updraftplus').' '.__('Intended for use with rate-limiting schemes.', 'updraftplus'),
|
||||
431 => __('Request Header Fields Too Large', 'updraftplus').' '.__('The server is unwilling to process the request because either an individual header field, or all the header fields collectively, are too large.', 'updraftplus'),
|
||||
451 => __('Unavailable For Legal Reasons', 'updraftplus').' '.__('A server operator has received a legal demand to deny access to a resource or to a set of resources that includes the requested resource.', 'updraftplus'),
|
||||
|
||||
/*
|
||||
5xx server errors
|
||||
Response status codes beginning with the digit "5" indicate cases in which the server is aware that it has encountered an error or is otherwise incapable of performing the request. Except when responding to a HEAD request, the server should include an entity containing an explanation of the error situation, and indicate whether it is a temporary or permanent condition. Likewise, user agents should display any included entity to the user. These response codes are applicable to any request method.
|
||||
*/
|
||||
500 => __('Internal Server Error', 'updraftplus').' '.__('A generic error message, given when an unexpected condition was encountered and no more specific message is suitable.', 'updraftplus'),
|
||||
501 => __('Not Implemented', 'updraftplus').' '.__('The server either does not recognize the request method, or it lacks the ability to fulfil the request', 'updraftplus').' '.__('Usually this implies future availability (e.g., a new feature of a web-service API).', 'updraftplus'),
|
||||
502 => __('Bad Gateway', 'updraftplus').' '.__('The server was acting as a gateway or proxy and received an invalid response from the upstream server.', 'updraftplus'),
|
||||
503 => __('Service Unavailable', 'updraftplus').' '.__('The server cannot handle the request (because it is overloaded or down for maintenance)', 'updraftplus').' '.__('Generally, this is a temporary state.', 'updraftplus'),
|
||||
504 => __('Gateway Timeout', 'updraftplus').' '.__('The server was acting as a gateway or proxy and did not receive a timely response from the upstream server.', 'updraftplus'),
|
||||
505 => __('HTTP Version Not Supported', 'updraftplus').' '.__('The server does not support the HTTP protocol version used in the request.', 'updraftplus'),
|
||||
506 => __('Variant Also Negotiates', 'updraftplus').' '.__('Transparent content negotiation for the request results in a circular reference.', 'updraftplus'),
|
||||
507 => __('Insufficient Storage', 'updraftplus').' '.__('The server is unable to store the representation needed to complete the request.', 'updraftplus'),
|
||||
508 => __('Loop Detected', 'updraftplus').' '.__('The server detected an infinite loop while processing the request.', 'updraftplus'),
|
||||
510 => __('Not Extended', 'updraftplus').' '.__('Further extensions to the request are required for the server to fulfil it.', 'updraftplus'),
|
||||
511 => __('Network Authentication Required', 'updraftplus').' '.__('The client needs to authenticate to gain network access', 'updraftplus').' '.__('Intended for use by intercepting proxies used to control access to the network (e.g., "captive portals" used to require agreement to Terms of Service before granting full Internet access via a Wi-Fi hotspot).', 'updraftplus'),
|
||||
);
|
||||
|
||||
if (isset($http_error_code_descriptions[$http_error_code])) {
|
||||
return $http_error_code.' ('.$http_error_code_descriptions[$http_error_code].')';
|
||||
} else {
|
||||
return $http_error_code;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
<?php
|
||||
|
||||
if (!defined('UPDRAFTPLUS_DIR')) die('No access.');
|
||||
|
||||
/**
|
||||
* A class for scheduling-related code.
|
||||
* N.B. This class began life Nov 2018; it is not guaranteed to contain all scheduling-related code. The variables used have also generally remained in the UpdraftPlus class.
|
||||
*/
|
||||
class UpdraftPlus_Job_Scheduler {
|
||||
|
||||
/**
|
||||
* This function is purely for timing - we just want to know the maximum run-time; not whether we have achieved anything during it. It will also run a check on whether the resumption interval is being approached.
|
||||
*/
|
||||
public static function record_still_alive() {
|
||||
|
||||
global $updraftplus;
|
||||
|
||||
// Update the record of maximum detected runtime on each run
|
||||
$time_passed = $updraftplus->jobdata_get('run_times');
|
||||
if (!is_array($time_passed)) $time_passed = array();
|
||||
|
||||
$time_this_run = microtime(true)-$updraftplus->opened_log_time;
|
||||
$time_passed[$updraftplus->current_resumption] = $time_this_run;
|
||||
$updraftplus->jobdata_set('run_times', $time_passed);
|
||||
|
||||
$resume_interval = $updraftplus->jobdata_get('resume_interval');
|
||||
if ($time_this_run + 30 > $resume_interval) {
|
||||
$new_interval = ceil($time_this_run + 30);
|
||||
set_site_transient('updraft_initial_resume_interval', (int) $new_interval, 8*86400);
|
||||
$updraftplus->log("The time we have been running (".round($time_this_run, 1).") is approaching the resumption interval ($resume_interval) - increasing resumption interval to $new_interval");
|
||||
$updraftplus->jobdata_set('resume_interval', $new_interval);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method helps with efficient scheduling
|
||||
*/
|
||||
public static function reschedule_if_needed() {
|
||||
|
||||
global $updraftplus;
|
||||
|
||||
// If nothing is scheduled, then no re-scheduling is needed, so return
|
||||
if (empty($updraftplus->newresumption_scheduled)) return;
|
||||
|
||||
$time_away = $updraftplus->newresumption_scheduled - time();
|
||||
|
||||
// 45 is chosen because it is 15 seconds more than what is used to detect recent activity on files (file mod times). (If we use exactly the same, then it's more possible to slightly miss each other)
|
||||
if ($time_away > 1 && $time_away <= 45) {
|
||||
$updraftplus->log('The scheduled resumption is within 45 seconds - will reschedule');
|
||||
// Increase interval generally by 45 seconds, on the assumption that our prior estimates were innaccurate (i.e. not just 45 seconds *this* time)
|
||||
self::increase_resume_and_reschedule(45);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that something useful happened. Calling this at appropriate times is an important part of scheduling decisions.
|
||||
*/
|
||||
public static function something_useful_happened() {
|
||||
|
||||
global $updraftplus;
|
||||
|
||||
self::record_still_alive();
|
||||
|
||||
if (!$updraftplus->something_useful_happened) {
|
||||
|
||||
// Update the record of when something useful happened
|
||||
$useful_checkins = $updraftplus->jobdata_get('useful_checkins');
|
||||
if (!is_array($useful_checkins)) $useful_checkins = array();
|
||||
if (!in_array($updraftplus->current_resumption, $useful_checkins)) {
|
||||
$useful_checkins[] = $updraftplus->current_resumption;
|
||||
$updraftplus->jobdata_set('useful_checkins', $useful_checkins);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$updraftplus->something_useful_happened = true;
|
||||
|
||||
$clone_job = $updraftplus->jobdata_get('clone_job');
|
||||
|
||||
if (!empty($clone_job)) {
|
||||
static $last_call = false;
|
||||
|
||||
// Check we haven't yet made a call or that 15 minutes has passed before we make another call
|
||||
if (!$last_call || time() - $last_call > 900) {
|
||||
$last_call = time();
|
||||
$clone_id = $updraftplus->jobdata_get('clone_id');
|
||||
$secret_token = $updraftplus->jobdata_get('secret_token');
|
||||
$log_data = $updraftplus->get_last_log_chunk($updraftplus->file_nonce);
|
||||
$log_contents = isset($log_data['log_contents']) ? $log_data['log_contents'] : '';
|
||||
$first_byte = isset($log_data['first_byte']) ? $log_data['first_byte'] : 0;
|
||||
$response = $updraftplus->get_updraftplus_clone()->backup_checkin(array('clone_id' => $clone_id, 'secret_token' => $secret_token, 'first_byte' => $first_byte, 'log_contents' => $log_contents));
|
||||
if (!isset($response['status']) || 'success' != $response['status']) {
|
||||
$updraftplus->log("UpdraftClone backup check-in failed.");
|
||||
} else {
|
||||
$updraftplus->log("UpdraftClone backup check-in made successfully.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$updraft_dir = $updraftplus->backups_dir_location();
|
||||
if (file_exists($updraft_dir.'/deleteflag-'.$updraftplus->nonce.'.txt')) {
|
||||
$updraftplus->log("User request for abort: backup job will be immediately halted");
|
||||
@unlink($updraft_dir.'/deleteflag-'.$updraftplus->nonce.'.txt');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the method.
|
||||
$updraftplus->backup_finish(true, true, true);
|
||||
die;
|
||||
}
|
||||
|
||||
if ($updraftplus->current_resumption >= 9 && false == $updraftplus->newresumption_scheduled) {
|
||||
$updraftplus->log("This is resumption ".$updraftplus->current_resumption.", but meaningful activity is still taking place; so a new one will be scheduled");
|
||||
// We just use max here to make sure we get a number at all
|
||||
$resume_interval = max($updraftplus->jobdata_get('resume_interval'), 75);
|
||||
// Don't consult the minimum here
|
||||
// if (!is_numeric($resume_interval) || $resume_interval<300) { $resume_interval = 300; }
|
||||
$schedule_for = time()+$resume_interval;
|
||||
$updraftplus->newresumption_scheduled = $schedule_for;
|
||||
wp_schedule_single_event($schedule_for, 'updraft_backup_resume', array($updraftplus->current_resumption + 1, $updraftplus->nonce));
|
||||
} else {
|
||||
self::reschedule_if_needed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reschedule the next resumption for the specified amount of time in the future
|
||||
*
|
||||
* @uses wp_schedule_single_event()
|
||||
*
|
||||
* @param Integer $how_far_ahead - a number of seconds
|
||||
*/
|
||||
public static function reschedule($how_far_ahead) {
|
||||
|
||||
global $updraftplus;
|
||||
|
||||
// Reschedule - remove presently scheduled event
|
||||
$next_resumption = $updraftplus->current_resumption + 1;
|
||||
wp_clear_scheduled_hook('updraft_backup_resume', array($next_resumption, $updraftplus->nonce));
|
||||
// Add new event
|
||||
// This next line may be too cautious; but until 14-Aug-2014, it was 300.
|
||||
// Update 20-Mar-2015 - lowered from 180 to 120
|
||||
// Update 03-Aug-2018 - lowered from 120 to 100
|
||||
// Update 09-Oct-2020 - lowered from 100 to 60
|
||||
if ($how_far_ahead < 60) $how_far_ahead = 60;
|
||||
$schedule_for = time() + $how_far_ahead;
|
||||
$updraftplus->log("Rescheduling resumption $next_resumption: moving to $how_far_ahead seconds from now ($schedule_for)");
|
||||
wp_schedule_single_event($schedule_for, 'updraft_backup_resume', array($next_resumption, $updraftplus->nonce));
|
||||
$updraftplus->newresumption_scheduled = $schedule_for;
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminate a backup run because other activity on the backup has been detected
|
||||
*
|
||||
* @uses die()
|
||||
*
|
||||
* @param String $file - Indicate the file whose recent modification is indicative of activity
|
||||
* @param Integer $time_now - The epoch time at which the detection took place
|
||||
* @param Integer $time_mod - The epoch time at which the file was modified
|
||||
* @param Boolean $increase_resumption - Whether or not to increase the resumption interval
|
||||
*/
|
||||
public static function terminate_due_to_activity($file, $time_now, $time_mod, $increase_resumption = true) {
|
||||
|
||||
global $updraftplus;
|
||||
|
||||
// We check-in, to avoid 'no check in last time!' detectors firing.
|
||||
self::record_still_alive();
|
||||
|
||||
// Log
|
||||
$file_size = file_exists($file) ? round(filesize($file)/1024, 1). 'KB' : 'n/a';
|
||||
$updraftplus->log("Terminate: ".basename($file)." exists with activity within the last 30 seconds (time_mod=$time_mod, time_now=$time_now, diff=".(floor($time_now-$time_mod)).", size=$file_size). This likely means that another UpdraftPlus run is at work; so we will exit.");
|
||||
|
||||
$increase_by = $increase_resumption ? 120 : 0;
|
||||
self::increase_resume_and_reschedule($increase_by, true);
|
||||
|
||||
// Die, unless there was a deliberate over-ride (for development purposes)
|
||||
if (!defined('UPDRAFTPLUS_ALLOW_RECENT_ACTIVITY') || !UPDRAFTPLUS_ALLOW_RECENT_ACTIVITY) die;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase the resumption interval and reschedule the next resumption
|
||||
*
|
||||
* @uses self::reschedule()
|
||||
*
|
||||
* @param Integer $howmuch - how much to add to the existing resumption interval
|
||||
* @param Boolean $due_to_overlap - setting this changes the strategy for calculating the next resumption; it indicates that the reason for an increase is because of recent activity detection
|
||||
*/
|
||||
private static function increase_resume_and_reschedule($howmuch = 120, $due_to_overlap = false) {
|
||||
|
||||
global $updraftplus;
|
||||
|
||||
$resume_interval = max((int) $updraftplus->jobdata_get('resume_interval'), (0 === $howmuch) ? 120 : 300);
|
||||
|
||||
if (empty($updraftplus->newresumption_scheduled) && $due_to_overlap) {
|
||||
$updraftplus->log('A new resumption will be scheduled to prevent the job ending');
|
||||
}
|
||||
|
||||
$new_resume = $resume_interval + $howmuch;
|
||||
// It may be that we're increasing for the second (or more) time during a run, and that we already know that the new value will be insufficient, and can be increased
|
||||
if ($updraftplus->opened_log_time > 100 && microtime(true)-$updraftplus->opened_log_time > $new_resume) {
|
||||
$new_resume = ceil(microtime(true)-$updraftplus->opened_log_time)+45;
|
||||
$howmuch = $new_resume-$resume_interval;
|
||||
}
|
||||
|
||||
// This used to be always $new_resume, until 14-Aug-2014. However, people who have very long-running processes can end up with very long times between resumptions as a result.
|
||||
// Actually, let's not try this yet. I think it is safe, but think there is a more conservative solution available.
|
||||
// $how_far_ahead = min($new_resume, 600);
|
||||
// Nov 2018 - scheduling the next resumption unnecessarily-far-in-the-future after an overlap is still occurring, so, we're adjusting this to have a maximum value in that particular case
|
||||
$how_far_ahead = $due_to_overlap ? min($new_resume, 900) : $new_resume;
|
||||
|
||||
// If it is very long-running, then that would normally be known soon.
|
||||
// If the interval is already 12 minutes or more, then try the next resumption 10 minutes from now (i.e. sooner than it would have been). Thus, we are guaranteed to get at least 24 minutes of processing in the first 34.
|
||||
if ($updraftplus->current_resumption <= 1 && $new_resume > 720) $how_far_ahead = 600;
|
||||
|
||||
if (!empty($updraftplus->newresumption_scheduled) || $due_to_overlap) self::reschedule($how_far_ahead);
|
||||
|
||||
$updraftplus->jobdata_set('resume_interval', $new_resume);
|
||||
|
||||
$updraftplus->log("To decrease the likelihood of overlaps, increasing resumption interval to: $resume_interval + $howmuch = $new_resume");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,517 @@
|
||||
<?php
|
||||
|
||||
if (!defined('ABSPATH')) die('No direct access.');
|
||||
|
||||
/**
|
||||
* Here live manipulation functions that just transform input into output and do not perform any other activities
|
||||
*/
|
||||
class UpdraftPlus_Manipulation_Functions {
|
||||
|
||||
/**
|
||||
* Replace last occurrence
|
||||
*
|
||||
* @param String $search The value being searched for, otherwise known as the needle
|
||||
* @param String $replace The replacement value that replaces found search values
|
||||
* @param String $subject The string or array being searched and replaced on, otherwise known as the haystack
|
||||
* @param Boolean $case_sensitive Whether the replacement should be case sensitive or not
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public static function str_lreplace($search, $replace, $subject, $case_sensitive = true) {
|
||||
$pos = $case_sensitive ? strrpos($subject, $search) : strripos($subject, $search);
|
||||
if (false !== $pos) $subject = substr_replace($subject, $replace, $pos, strlen($search));
|
||||
return $subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the first, and only the first, instance within a string
|
||||
*
|
||||
* @param String $needle - the search term
|
||||
* @param String $replace - the replacement term
|
||||
* @param String $haystack - the string to replace within
|
||||
*
|
||||
* @return String - the filtered string
|
||||
*/
|
||||
public static function str_replace_once($needle, $replace, $haystack) {
|
||||
$pos = strpos($haystack, $needle);
|
||||
return (false !== $pos) ? substr_replace($haystack, $replace, $pos, strlen($needle)) : $haystack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove slashes that precede a comma or the end of the string
|
||||
*
|
||||
* @param String $string - input string
|
||||
*
|
||||
* @return String - the altered string
|
||||
*/
|
||||
public static function strip_dirslash($string) {
|
||||
return preg_replace('#/+(,|$)#', '$1', $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove slashes from a string or array of strings.
|
||||
*
|
||||
* The function wp_unslash() is WP 3.6+, so therefore we have a compatibility method here
|
||||
*
|
||||
* @param String|Array $value String or array of strings to unslash.
|
||||
* @return String|Array Unslashed $value
|
||||
*/
|
||||
public static function wp_unslash($value) {
|
||||
return function_exists('wp_unslash') ? wp_unslash($value) : stripslashes_deep($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a filename into components
|
||||
*
|
||||
* @param String $filename - the filename
|
||||
*
|
||||
* @return Array|Boolean - the parsed values, or false if parsing failed
|
||||
*/
|
||||
public static function parse_filename($filename) {
|
||||
if (preg_match('/^backup_([\-0-9]{10})-([0-9]{4})_.*_([0-9a-f]{12})-([\-a-z]+)([0-9]+)?+\.(zip|gz|gz\.crypt)$/i', $filename, $matches)) {
|
||||
return array(
|
||||
'date' => strtotime($matches[1].' '.$matches[2]),
|
||||
'nonce' => $matches[3],
|
||||
'type' => $matches[4],
|
||||
'index' => (empty($matches[5]) ? 0 : $matches[5]-1),
|
||||
'extension' => $matches[6]
|
||||
);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a number of bytes into a suitable textual string
|
||||
*
|
||||
* @param Integer $size - the number of bytes
|
||||
*
|
||||
* @return String - the resulting textual string
|
||||
*/
|
||||
public static function convert_numeric_size_to_text($size) {
|
||||
if ($size > 1073741824) {
|
||||
return round($size / 1073741824, 1).' GB';
|
||||
} elseif ($size > 1048576) {
|
||||
return round($size / 1048576, 1).' MB';
|
||||
} elseif ($size > 1024) {
|
||||
return round($size / 1024, 1).' KB';
|
||||
} else {
|
||||
return round($size, 1).' B';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add backquotes to tables and db-names in SQL queries. Taken from phpMyAdmin.
|
||||
*
|
||||
* @param string $a_name - the table name
|
||||
* @return string - the quoted table name
|
||||
*/
|
||||
public static function backquote($a_name) {
|
||||
if (!empty($a_name) && '*' != $a_name) {
|
||||
if (is_array($a_name)) {
|
||||
$result = array();
|
||||
foreach ($a_name as $key => $val) {
|
||||
$result[$key] = '`'.$val.'`';
|
||||
}
|
||||
return $result;
|
||||
} else {
|
||||
return '`'.$a_name.'`';
|
||||
}
|
||||
} else {
|
||||
return $a_name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove empty (according to empty()) members of an array
|
||||
*
|
||||
* @param Array $list - input array
|
||||
* @return Array - pruned array
|
||||
*/
|
||||
public static function remove_empties($list) {
|
||||
if (!is_array($list)) return $list;
|
||||
foreach ($list as $ind => $entry) {
|
||||
if (empty($entry)) unset($list[$ind]);
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort restoration entities
|
||||
*
|
||||
* @param String $a - first entity
|
||||
* @param String $b - second entity
|
||||
*
|
||||
* @return Integer - sort result
|
||||
*/
|
||||
public static function sort_restoration_entities($a, $b) {
|
||||
if ($a == $b) return 0;
|
||||
// Put the database first
|
||||
// Put wpcore after plugins/uploads/themes (needed for restores of foreign all-in-one formats)
|
||||
if ('db' == $a || 'wpcore' == $b) return -1;
|
||||
if ('db' == $b || 'wpcore' == $a) return 1;
|
||||
// After wpcore, next last is others
|
||||
if ('others' == $b) return -1;
|
||||
if ('others' == $a) return 1;
|
||||
// And then uploads - this is only because we want to make sure uploads is after plugins, so that we know before we get to the uploads whether the version of UD which might have to unpack them can do this new-style or not.
|
||||
if ('uploads' == $b) return -1;
|
||||
if ('uploads' == $a) return 1;
|
||||
return strcmp($a, $b);
|
||||
}
|
||||
|
||||
/**
|
||||
* This options filter removes ABSPATH off the front of updraft_dir, if it is given absolutely and contained within it
|
||||
*
|
||||
* @param String $updraft_dir Directory
|
||||
* @return String
|
||||
*/
|
||||
public static function prune_updraft_dir_prefix($updraft_dir) {
|
||||
if ('/' == substr($updraft_dir, 0, 1) || "\\" == substr($updraft_dir, 0, 1) || preg_match('/^[a-zA-Z]:/', $updraft_dir)) {
|
||||
$wcd = trailingslashit(WP_CONTENT_DIR);
|
||||
if (strpos($updraft_dir, $wcd) === 0) {
|
||||
$updraft_dir = substr($updraft_dir, strlen($wcd));
|
||||
}
|
||||
}
|
||||
return $updraft_dir;
|
||||
}
|
||||
|
||||
public static function get_mime_type_from_filename($filename, $allow_gzip = true) {
|
||||
if ('.zip' == substr($filename, -4, 4)) {
|
||||
return 'application/zip';
|
||||
} elseif ('.tar' == substr($filename, -4, 4)) {
|
||||
return 'application/x-tar';
|
||||
} elseif ('.tar.gz' == substr($filename, -7, 7)) {
|
||||
return 'application/x-tgz';
|
||||
} elseif ('.tar.bz2' == substr($filename, -8, 8)) {
|
||||
return 'application/x-bzip-compressed-tar';
|
||||
} elseif ($allow_gzip && '.gz' == substr($filename, -3, 3)) {
|
||||
// When we sent application/x-gzip as a content-type header to the browser, we found a case where the server compressed it a second time (since observed several times)
|
||||
return 'application/x-gzip';
|
||||
} else {
|
||||
return 'application/octet-stream';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the value to ensure it is between 1 and 9999
|
||||
*
|
||||
* @param Integer $input
|
||||
*
|
||||
* @return Integer
|
||||
*/
|
||||
public static function retain_range($input) {
|
||||
$input = (int) $input;
|
||||
return ($input > 0) ? min($input, 9999) : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find matching string from $str_arr1 and $str_arr2
|
||||
*
|
||||
* @param array $str_arr1 array of strings
|
||||
* @param array $str_arr2 array of strings
|
||||
* @param boolean $match_until_first_numeric only match until first numeric occurrence
|
||||
* @return string matching str which will be best for replacement
|
||||
*/
|
||||
public static function get_matching_str_from_array_elems($str_arr1, $str_arr2, $match_until_first_numeric = true) {
|
||||
$matching_str = '';
|
||||
if ($match_until_first_numeric) {
|
||||
$str_partial_arr = array();
|
||||
foreach ($str_arr1 as $str1) {
|
||||
$str1_str_length = strlen($str1);
|
||||
$temp_str1_chars = str_split($str1);
|
||||
$temp_partial_str = '';
|
||||
// The flag is for whether non-numeric character passed after numeric character occurrence in str1. For ex. str1 is utf8mb4, the flag wil be true when parsing m after utf8.
|
||||
$numeric_char_pass_flag = false;
|
||||
$char_position_in_str1 = 0;
|
||||
while ($char_position_in_str1 < $str1_str_length) {
|
||||
if ($numeric_char_pass_flag && !is_numeric($temp_str1_chars[$char_position_in_str1])) {
|
||||
break;
|
||||
}
|
||||
if (is_numeric($temp_str1_chars[$char_position_in_str1])) {
|
||||
$numeric_char_pass_flag = true;
|
||||
}
|
||||
$temp_partial_str .= $temp_str1_chars[$char_position_in_str1];
|
||||
$char_position_in_str1++;
|
||||
}
|
||||
$str_partial_arr[] = $temp_partial_str;
|
||||
}
|
||||
foreach ($str_partial_arr as $str_partial) {
|
||||
if (!empty($matching_str)) {
|
||||
break;
|
||||
}
|
||||
foreach ($str_arr2 as $str2) {
|
||||
if (0 === stripos($str2, $str_partial)) {
|
||||
$matching_str = $str2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$str1_partial_first_arr = array();
|
||||
$str1_partial_first_arr = array();
|
||||
$str1_partial_start_n_middle_arr = array();
|
||||
$str1_partial_middle_n_last_arr = array();
|
||||
$str1_partial_last_arr = array();
|
||||
foreach ($str_arr1 as $str1) {
|
||||
$str1_partial_arr = explode('_', $str1);
|
||||
$str1_parts_count = count($str1_partial_arr);
|
||||
$str1_partial_first_arr[] = $str1_partial_arr[0];
|
||||
$str1_last_part_index = $str1_parts_count - 1;
|
||||
if ($str1_last_part_index > 0) {
|
||||
$str1_partial_last_arr[] = $str1_partial_arr[$str1_last_part_index];
|
||||
$str1_partial_start_n_middle_arr[] = substr($str1, 0, stripos($str1, '_'));
|
||||
$str1_partial_middle_n_last_arr[] = substr($str1, stripos($str1, '_') + 1);
|
||||
}
|
||||
}
|
||||
for ($case_no = 1; $case_no <= 5; $case_no++) {
|
||||
if (!empty($matching_str)) {
|
||||
break;
|
||||
}
|
||||
foreach ($str_arr2 as $str2) {
|
||||
switch ($case_no) {
|
||||
// Case 1: Both Start and End match
|
||||
case 1:
|
||||
$str2_partial_arr = explode('_', $str2);
|
||||
$str2_first_part = $str2_partial_arr[0];
|
||||
$str2_parts_count = count($str2_partial_arr);
|
||||
$str2_last_part_index = $str2_parts_count - 1;
|
||||
if ($str2_last_part_index > 0) {
|
||||
$str2_last_part = $str2_partial_arr[$str2_last_part_index];
|
||||
} else {
|
||||
$str2_last_part = '';
|
||||
}
|
||||
if (!empty($str2_last_part) && !empty($str1_partial_last_arr) && in_array($str2_first_part, $str1_partial_first_arr) && in_array($str2_last_part, $str1_partial_last_arr)) {
|
||||
$matching_str = $str2;
|
||||
}
|
||||
break;
|
||||
// Case 2: Start Middle Match
|
||||
case 2:
|
||||
$str2_partial_first_n_middle_parts = substr($str2, 0, stripos($str2, '_'));
|
||||
if (in_array($str2_partial_first_n_middle_parts, $str1_partial_start_n_middle_arr)) {
|
||||
$matching_str = $str2;
|
||||
}
|
||||
break;
|
||||
// Case 3: End Middle Match
|
||||
case 3:
|
||||
$str2_partial_middle_n_last_parts = stripos($str2, '_') !== false ? substr($str2, stripos($str2, '_') + 1) : '';
|
||||
if (!empty($str2_partial_middle_n_last_parts) && in_array($str2_partial_middle_n_last_parts, $str1_partial_middle_n_last_arr)) {
|
||||
$matching_str = $str2;
|
||||
}
|
||||
break;
|
||||
// Case 4: Start Match (low possibilities)
|
||||
case 4:
|
||||
$str2_partial_arr = explode('_', $str2);
|
||||
$str2_first_part = $str2_partial_arr[0];
|
||||
if (in_array($str2_first_part, $str1_partial_first_arr)) {
|
||||
$matching_str = $str2;
|
||||
}
|
||||
break;
|
||||
// Case 5: End Match (low possibilities)
|
||||
case 5:
|
||||
$str2_partial_arr = explode('_', $str2);
|
||||
$str2_parts_count = count($str2_partial_arr);
|
||||
$str2_last_part_index = $str2_parts_count - 1;
|
||||
if ($str2_last_part_index > 0) {
|
||||
$str2_last_part = $str2_partial_arr[$str2_last_part_index];
|
||||
} else {
|
||||
$str2_last_part = '';
|
||||
}
|
||||
if (!empty($str2_last_part) && in_array($str2_last_part, $str1_partial_last_arr)) {
|
||||
$matching_str = $str2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!empty($matching_str)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $matching_str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a normalised version of a URL, useful for comparisons. This may produce a URL that does not actually reference the same location; its purpose is only to use in comparisons of two URLs that *both* go through this function.
|
||||
*
|
||||
* @param String $url - the URL
|
||||
*
|
||||
* @return String - normalised
|
||||
*/
|
||||
public static function normalise_url($url) {
|
||||
$parsed_descrip_url = parse_url($url);
|
||||
if (is_array($parsed_descrip_url)) {
|
||||
if (preg_match('/^www\./i', $parsed_descrip_url['host'], $matches)) $parsed_descrip_url['host'] = substr($parsed_descrip_url['host'], 4);
|
||||
$normalised_descrip_url = 'http://'.strtolower($parsed_descrip_url['host']);
|
||||
if (!empty($parsed_descrip_url['port'])) $normalised_descrip_url .= ':'.$parsed_descrip_url['port'];
|
||||
if (!empty($parsed_descrip_url['path'])) $normalised_descrip_url .= untrailingslashit($parsed_descrip_url['path']);
|
||||
} else {
|
||||
$normalised_descrip_url = untrailingslashit($url);
|
||||
}
|
||||
return $normalised_descrip_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a filesystem path.
|
||||
*
|
||||
* On windows systems, replaces backslashes with forward slashes
|
||||
* and forces upper-case drive letters.
|
||||
* Allows for two leading slashes for Windows network shares, but
|
||||
* ensures that all other duplicate slashes are reduced to a single.
|
||||
*
|
||||
* @param string $path Path to normalize.
|
||||
* @return string Normalized path.
|
||||
*/
|
||||
public static function wp_normalize_path($path) {
|
||||
// wp_normalize_path is not present before WP 3.9
|
||||
if (function_exists('wp_normalize_path')) return wp_normalize_path($path);
|
||||
// Taken from WP 4.6
|
||||
$path = str_replace('\\', '/', $path);
|
||||
$path = preg_replace('|(?<=.)/+|', '/', $path);
|
||||
if (':' === substr($path, 1, 1)) {
|
||||
$path = ucfirst($path);
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a set of times, find details about the maximum
|
||||
*
|
||||
* @param Array $time_passed - a list of times passed, with numerical indexes
|
||||
* @param Integer $upto - last index to consider
|
||||
* @param Integer $first_run - first index to consider
|
||||
*
|
||||
* @return Array - a list with entries, in order: maximum time, list in string format, how many run times were found
|
||||
*/
|
||||
public static function max_time_passed($time_passed, $upto, $first_run) {
|
||||
$max_time = 0;
|
||||
$timings_string = "";
|
||||
$run_times_known = 0;
|
||||
for ($i = $first_run; $i <= $upto; $i++) {
|
||||
$timings_string .= "$i:";
|
||||
if (isset($time_passed[$i])) {
|
||||
$timings_string .= round($time_passed[$i], 1).' ';
|
||||
$run_times_known++;
|
||||
if ($time_passed[$i] > $max_time) $max_time = round($time_passed[$i]);
|
||||
} else {
|
||||
$timings_string .= '? ';
|
||||
}
|
||||
}
|
||||
return array($max_time, $timings_string, $run_times_known);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a given string ends with a given substring.
|
||||
*
|
||||
* @param string $haystack string
|
||||
* @param string $needle substring which should be checked at the end of the string
|
||||
* @return boolean Whether string ends with the substring or not
|
||||
*/
|
||||
public static function str_ends_with($haystack, $needle) {
|
||||
if (substr($haystack, - strlen($needle)) == $needle) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a random string of given length.
|
||||
*
|
||||
* @param string $length integer
|
||||
* @return string random string
|
||||
*/
|
||||
public static function generate_random_string($length = 2) {
|
||||
$characters = '0123456789abcdefghijklmnopqrstuvwxyz';
|
||||
$characters_length = strlen($characters);
|
||||
$random_string = '';
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$random_string .= $characters[rand(0, $characters_length - 1)];
|
||||
}
|
||||
return $random_string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an anonymized IPv4 or IPv6 address.
|
||||
*
|
||||
* @param string $ip_addr The IPv4 or IPv6 address to be anonymized.
|
||||
* @return string The anonymized IP address.
|
||||
*/
|
||||
public static function wp_privacy_anonymize_ip($ip_addr) {
|
||||
if (empty($ip_addr)) {
|
||||
return '0.0.0.0';
|
||||
}
|
||||
|
||||
$ip_prefix = '';
|
||||
$is_ipv6 = substr_count($ip_addr, ':') > 1;
|
||||
$is_ipv4 = (3 === substr_count($ip_addr, '.'));
|
||||
|
||||
if ($is_ipv6 && $is_ipv4) {
|
||||
$ip_prefix = '::ffff:';
|
||||
$ip_addr = preg_replace('/^\[?[0-9a-f:]*:/i', '', $ip_addr);
|
||||
$ip_addr = str_replace(']', '', $ip_addr);
|
||||
$is_ipv6 = false;
|
||||
}
|
||||
|
||||
if ($is_ipv6) {
|
||||
$percent = strpos($ip_addr, '%');
|
||||
$netmask = 'ffff:ffff:ffff:ffff:0000:0000:0000:0000';
|
||||
|
||||
if (preg_match('#\[(.*)\]#', $ip_addr, $matches)) $ip_addr = $matches[1];
|
||||
|
||||
if (false !== $percent) {
|
||||
$ip_addr = substr($ip_addr, 0, $percent);
|
||||
}
|
||||
|
||||
if (preg_match('/[^0-9a-f:]/i', $ip_addr)) {
|
||||
return '::';
|
||||
}
|
||||
|
||||
if (function_exists('inet_pton') && function_exists('inet_ntop')) {
|
||||
$ip_addr = inet_ntop(inet_pton($ip_addr) & inet_pton($netmask));
|
||||
if (false === $ip_addr) {
|
||||
return '::';
|
||||
}
|
||||
}
|
||||
} elseif ($is_ipv4) {
|
||||
$last_octet_position = strrpos($ip_addr, '.');
|
||||
$ip_addr = substr($ip_addr, 0, $last_octet_position) . '.0';
|
||||
} else {
|
||||
return '0.0.0.0';
|
||||
}
|
||||
|
||||
return $ip_prefix . $ip_addr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns uniform "anonymous" data by type.
|
||||
*
|
||||
* @param string $type The type of data to be anonymized.
|
||||
* @param string $data Optional The data to be anonymized.
|
||||
* @return string The anonymous data for the requested type.
|
||||
*/
|
||||
public static function anonymize_data($type, $data = '') {
|
||||
switch ($type) {
|
||||
case 'email':
|
||||
$anonymous = 'invalid@example.com';
|
||||
break;
|
||||
case 'url':
|
||||
$anonymous = 'https://invalid.example.com';
|
||||
break;
|
||||
case 'ip':
|
||||
$anonymous = self::wp_privacy_anonymize_ip($data);
|
||||
break;
|
||||
case 'date':
|
||||
$anonymous = '0000-00-00 00:00:00';
|
||||
break;
|
||||
case 'text':
|
||||
/* translators: Deleted text. */
|
||||
$anonymous = __('[deleted]', 'updraftplus');
|
||||
break;
|
||||
case 'longtext':
|
||||
/* translators: Deleted long text. */
|
||||
$anonymous = __('This content was deleted in order to anonymize it.', 'updraftplus');
|
||||
break;
|
||||
default:
|
||||
$anonymous = '';
|
||||
break;
|
||||
}
|
||||
return $anonymous;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
<?php
|
||||
|
||||
// https://stackoverflow.com/questions/157318/resumable-downloads-when-using-php-to-send-the-file
|
||||
// User: DaveRandom
|
||||
|
||||
if (!class_exists('UpdraftPlus_NonExistentFileException')):
|
||||
class UpdraftPlus_NonExistentFileException extends RuntimeException {}
|
||||
endif;
|
||||
|
||||
if (!class_exists('UpdraftPlus_UnreadableFileException')):
|
||||
class UpdraftPlus_UnreadableFileException extends RuntimeException {}
|
||||
endif;
|
||||
|
||||
if (!class_exists('UpdraftPlus_UnsatisfiableRangeException')):
|
||||
class UpdraftPlus_UnsatisfiableRangeException extends RuntimeException {}
|
||||
endif;
|
||||
|
||||
if (!class_exists('UpdraftPlus_InvalidRangeHeaderException')):
|
||||
class UpdraftPlus_InvalidRangeHeaderException extends RuntimeException {}
|
||||
endif;
|
||||
|
||||
class UpdraftPlus_RangeHeader
|
||||
{
|
||||
/**
|
||||
* The first byte in the file to send (0-indexed), a null value indicates the last
|
||||
* $end bytes
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
private $firstByte;
|
||||
|
||||
/**
|
||||
* The last byte in the file to send (0-indexed), a null value indicates $start to
|
||||
* EOF
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
private $lastByte;
|
||||
|
||||
/**
|
||||
* Create a new instance from a Range header string
|
||||
*
|
||||
* @param string $header
|
||||
* @return UpdraftPlus_RangeHeader
|
||||
*/
|
||||
public static function createFromHeaderString($header)
|
||||
{
|
||||
if ($header === null) {
|
||||
return null;
|
||||
}
|
||||
if (!preg_match('/^\s*([A-Za-z]+)\s*=\s*(\d*)\s*-\s*(\d*)\s*(?:,|$)/', $header, $info)) {
|
||||
throw new UpdraftPlus_InvalidRangeHeaderException('Invalid header format');
|
||||
} else if (strtolower($info[1]) !== 'bytes') {
|
||||
throw new UpdraftPlus_InvalidRangeHeaderException('Unknown range unit: ' . $info[1]); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error messages should be escaped when caught and printed.
|
||||
}
|
||||
|
||||
return new self(
|
||||
$info[2] === '' ? null : $info[2],
|
||||
$info[3] === '' ? null : $info[3]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $firstByte
|
||||
* @param int|null $lastByte
|
||||
* @throws UpdraftPlus_InvalidRangeHeaderException
|
||||
*/
|
||||
public function __construct($firstByte, $lastByte)
|
||||
{
|
||||
$this->firstByte = $firstByte === null ? $firstByte : (int)$firstByte;
|
||||
$this->lastByte = $lastByte === null ? $lastByte : (int)$lastByte;
|
||||
|
||||
if ($this->firstByte === null && $this->lastByte === null) {
|
||||
throw new UpdraftPlus_InvalidRangeHeaderException(
|
||||
'Both start and end position specifiers empty'
|
||||
);
|
||||
} else if ($this->firstByte < 0 || $this->lastByte < 0) {
|
||||
throw new UpdraftPlus_InvalidRangeHeaderException(
|
||||
'Position specifiers cannot be negative'
|
||||
);
|
||||
} else if ($this->lastByte !== null && $this->lastByte < $this->firstByte) {
|
||||
throw new UpdraftPlus_InvalidRangeHeaderException(
|
||||
'Last byte cannot be less than first byte'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the start position when this range is applied to a file of the specified size
|
||||
*
|
||||
* @param int $fileSize
|
||||
* @return int
|
||||
* @throws UpdraftPlus_UnsatisfiableRangeException
|
||||
*/
|
||||
public function getStartPosition($fileSize)
|
||||
{
|
||||
$size = (int)$fileSize;
|
||||
|
||||
if ($this->firstByte === null) {
|
||||
return ($size - 1) - $this->lastByte;
|
||||
}
|
||||
|
||||
if ($size <= $this->firstByte) {
|
||||
throw new UpdraftPlus_UnsatisfiableRangeException(
|
||||
'Start position is after the end of the file'
|
||||
);
|
||||
}
|
||||
|
||||
return $this->firstByte;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end position when this range is applied to a file of the specified size
|
||||
*
|
||||
* @param int $fileSize
|
||||
* @return int
|
||||
* @throws UpdraftPlus_UnsatisfiableRangeException
|
||||
*/
|
||||
public function getEndPosition($fileSize)
|
||||
{
|
||||
$size = (int)$fileSize;
|
||||
|
||||
if ($this->lastByte === null) {
|
||||
return $size - 1;
|
||||
}
|
||||
|
||||
if ($size <= $this->lastByte) {
|
||||
throw new UpdraftPlus_UnsatisfiableRangeException(
|
||||
'End position is after the end of the file'
|
||||
);
|
||||
}
|
||||
|
||||
return $this->lastByte;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the length when this range is applied to a file of the specified size
|
||||
*
|
||||
* @param int $fileSize
|
||||
* @return int
|
||||
* @throws UpdraftPlus_UnsatisfiableRangeException
|
||||
*/
|
||||
public function getLength($fileSize)
|
||||
{
|
||||
$size = (int)$fileSize;
|
||||
|
||||
return $this->getEndPosition($size) - $this->getStartPosition($size) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Content-Range header corresponding to this Range and the specified file
|
||||
* size
|
||||
*
|
||||
* @param int $fileSize
|
||||
* @return string
|
||||
*/
|
||||
public function getContentRangeHeader($fileSize)
|
||||
{
|
||||
return 'bytes ' . $this->getStartPosition($fileSize) . '-'
|
||||
. $this->getEndPosition($fileSize) . '/' . $fileSize;
|
||||
}
|
||||
}
|
||||
|
||||
class UpdraftPlus_PartialFileServlet
|
||||
{
|
||||
/**
|
||||
* The range header on which the data transmission will be based
|
||||
*
|
||||
* @var UpdraftPlus_RangeHeader|null
|
||||
*/
|
||||
private $range;
|
||||
|
||||
/**
|
||||
* @param UpdraftPlus_RangeHeader $range Range header on which the transmission will be based
|
||||
*/
|
||||
public function __construct($range = null)
|
||||
{
|
||||
$this->range = $range;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send part of the data in a seekable stream resource to the output buffer
|
||||
*
|
||||
* @param resource $fp Stream resource to read data from
|
||||
* @param int $start Position in the stream to start reading
|
||||
* @param int $length Number of bytes to read
|
||||
* @param int $chunkSize Maximum bytes to read from the file in a single operation
|
||||
*/
|
||||
private function sendDataRange($fp, $start, $length, $chunkSize = 2097152)
|
||||
{
|
||||
if ($start > 0) {
|
||||
fseek($fp, $start, SEEK_SET);
|
||||
}
|
||||
|
||||
while ($length) {
|
||||
$read = ($length > $chunkSize) ? $chunkSize : $length;
|
||||
$length -= $read;
|
||||
echo fread($fp, $read); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Raw output intended.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the headers that are included regardless of whether a range was requested
|
||||
*
|
||||
* @param string $fileName
|
||||
* @param int $contentLength
|
||||
* @param string $contentType
|
||||
*/
|
||||
private function sendDownloadHeaders($fileName, $contentLength, $contentType)
|
||||
{
|
||||
header('Content-Type: ' . $contentType);
|
||||
header('Content-Length: ' . $contentLength);
|
||||
header('Content-Disposition: attachment; filename="' . $fileName . '"');
|
||||
header('Accept-Ranges: bytes');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send data from a file based on the current Range header
|
||||
*
|
||||
* @param string $path Local file system path to serve
|
||||
* @param string $contentType MIME type of the data stream
|
||||
*/
|
||||
public function sendFile($path, $contentType = 'application/octet-stream')
|
||||
{
|
||||
// Make sure the file exists and is a file, otherwise we are wasting our time
|
||||
$localPath = realpath($path);
|
||||
if ($localPath === false || !is_file($localPath)) {
|
||||
throw new UpdraftPlus_NonExistentFileException(
|
||||
$path . ' does not exist or is not a file' // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error messages should be escaped when caught and printed.
|
||||
);
|
||||
}
|
||||
|
||||
// Make sure we can open the file for reading
|
||||
if (!$fp = fopen($localPath, 'r')) {
|
||||
throw new UpdraftPlus_UnreadableFileException(
|
||||
'Failed to open ' . $localPath . ' for reading' // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error messages should be escaped when caught and printed.
|
||||
);
|
||||
}
|
||||
|
||||
$fileSize = filesize($localPath);
|
||||
|
||||
if ($this->range == null) {
|
||||
// No range requested, just send the whole file
|
||||
header('HTTP/1.1 200 OK');
|
||||
$this->sendDownloadHeaders(basename($localPath), $fileSize, $contentType);
|
||||
|
||||
fpassthru($fp);
|
||||
} else {
|
||||
// Send the request range
|
||||
header('HTTP/1.1 206 Partial Content');
|
||||
header('Content-Range: ' . $this->range->getContentRangeHeader($fileSize));
|
||||
$this->sendDownloadHeaders(
|
||||
basename($localPath),
|
||||
$this->range->getLength($fileSize),
|
||||
$contentType
|
||||
);
|
||||
|
||||
$this->sendDataRange(
|
||||
$fp,
|
||||
$this->range->getStartPosition($fileSize),
|
||||
$this->range->getLength($fileSize)
|
||||
);
|
||||
}
|
||||
|
||||
fclose($fp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,776 @@
|
||||
<?php
|
||||
|
||||
if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed.');
|
||||
|
||||
abstract class UpdraftPlus_RemoteSend {
|
||||
|
||||
protected $receivers = array();
|
||||
|
||||
protected $php_events = array();
|
||||
|
||||
private $job_id;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action('updraft_migrate_newdestination', array($this, 'updraft_migrate_newdestination'));
|
||||
add_action('updraft_remote_ping_test', array($this, 'updraft_remote_ping_test'));
|
||||
add_action('updraft_migrate_key_create', array($this, 'updraft_migrate_key_create'));
|
||||
add_filter('updraft_migrate_key_create_return', array($this, 'updraft_migrate_key_create_return'), 10, 2);
|
||||
add_action('updraft_migrate_key_delete', array($this, 'updraft_migrate_key_delete'));
|
||||
add_action('updraft_migrate_delete_existingsites', array($this, 'updraft_migrate_delete_existingsites'));
|
||||
add_filter('updraftplus_initial_jobdata', array($this, 'updraftplus_initial_jobdata'), 10, 3);
|
||||
add_filter('updraft_printjob_beforewarnings', array($this, 'updraft_printjob_beforewarnings'), 10, 2);
|
||||
add_action('plugins_loaded', array($this, 'plugins_loaded'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs upon the WP action plugins_loaded; sets up UDRPC listeners for site-to-site migration
|
||||
*/
|
||||
public function plugins_loaded() {
|
||||
|
||||
global $updraftplus;
|
||||
|
||||
// Prevent fatal errors if UD was not loaded (e.g. some CLI method)
|
||||
if (!is_a($updraftplus, 'UpdraftPlus')) return;
|
||||
|
||||
// Create a receiver for each key
|
||||
if (!class_exists('UpdraftPlus_Options')) {
|
||||
error_log("UpdraftPlus_Options class not found: is UpdraftPlus properly installed?");
|
||||
return;
|
||||
}
|
||||
$our_keys = UpdraftPlus_Options::get_updraft_option('updraft_migrator_localkeys');
|
||||
if (is_array($our_keys) && !empty($our_keys)) {
|
||||
foreach ($our_keys as $name_hash => $key) {
|
||||
if (!is_array($key)) return;
|
||||
$ud_rpc = $updraftplus->get_udrpc($name_hash.'.migrator.updraftplus.com');
|
||||
if (!empty($key['sender_public'])) {
|
||||
$ud_rpc->set_message_format(2);
|
||||
$ud_rpc->set_key_local($key['key']);
|
||||
$ud_rpc->set_key_remote($key['sender_public']);
|
||||
} else {
|
||||
$ud_rpc->set_message_format(1);
|
||||
$ud_rpc->set_key_local($key['key']);
|
||||
}
|
||||
$this->receivers[$name_hash] = $ud_rpc;
|
||||
// Create listener (which causes WP actions to be fired when messages are received)
|
||||
$ud_rpc->activate_replay_protection();
|
||||
$ud_rpc->create_listener();
|
||||
}
|
||||
add_filter('udrpc_command_send_chunk', array($this, 'udrpc_command_send_chunk'), 10, 3);
|
||||
add_filter('udrpc_command_get_file_status', array($this, 'udrpc_command_get_file_status'), 10, 3);
|
||||
add_filter('udrpc_command_upload_complete', array($this, 'udrpc_command_upload_complete'), 10, 3);
|
||||
add_filter('udrpc_action', array($this, 'udrpc_action'), 10, 4);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will return a response to the remote site on any action
|
||||
*
|
||||
* @param string $response - a string response
|
||||
* @param string $command - the incoming command
|
||||
* @param array $data - an array of response data
|
||||
* @param string $name_indicator - a string to identify the request
|
||||
*
|
||||
* @return array - the array response
|
||||
*/
|
||||
public function udrpc_action($response, $command, $data, $name_indicator) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Unused parameter is present because the method is used as a WP filter.
|
||||
|
||||
if (is_array($data) && isset($data['sender_public'])) {
|
||||
// Do we already know the sender's public key?
|
||||
$our_keys = UpdraftPlus_Options::get_updraft_option('updraft_migrator_localkeys');
|
||||
if (is_array($our_keys) && preg_match('/^([a-f0-9]+)\.migrator.updraftplus.com$/', $name_indicator, $matches) && !empty($our_keys[$matches[1]]) && empty($our_keys[$matches[1]]['sender_public'])) {
|
||||
// N.B. When the sender sends a public key, that indicates that *all* future communications will use it
|
||||
$our_keys[$matches[1]]['sender_public'] = $data['sender_public'];
|
||||
UpdraftPlus_Options::update_updraft_option('updraft_migrator_localkeys', $our_keys);
|
||||
if (!is_array($response['data'])) $response['data'] = array();
|
||||
$response['data']['got_public'] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
protected function initialise_listener_error_handling($hash) {
|
||||
global $updraftplus;
|
||||
$updraftplus->error_reporting_stop_when_logged = true;
|
||||
$error_levels = version_compare(PHP_VERSION, '8.4.0', '>=') ? E_ALL : E_ALL & ~E_STRICT;
|
||||
set_error_handler(array($updraftplus, 'php_error'), $error_levels);
|
||||
$this->php_events = array();
|
||||
add_filter('updraftplus_logline', array($this, 'updraftplus_logline'), 10, 4);
|
||||
if (!UpdraftPlus_Options::get_updraft_option('updraft_debug_mode')) return;
|
||||
$updraftplus->nonce = $hash;
|
||||
$updraftplus->logfile_open($hash);
|
||||
}
|
||||
|
||||
protected function return_rpc_message($msg) {
|
||||
if (is_array($msg) && isset($msg['response']) && 'error' == $msg['response']) {
|
||||
global $updraftplus;
|
||||
$updraftplus->log('Unexpected response code in remote communications: '.serialize($msg));
|
||||
}
|
||||
if (!empty($this->php_events)) {
|
||||
if (!isset($msg['data'])) $msg['data'] = null;
|
||||
$msg['data'] = array('php_events' => array(), 'previous_data' => $msg['data']);
|
||||
foreach ($this->php_events as $logline) {
|
||||
$msg['data']['php_events'][] = $logline;
|
||||
}
|
||||
}
|
||||
restore_error_handler();
|
||||
|
||||
return $msg;
|
||||
}
|
||||
|
||||
public function updraftplus_logline($line, $nonce, $level, $uniq_id) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Unused parameter is present because the method is used as a WP filter.
|
||||
if ('notice' === $level && 'php_event' === $uniq_id) {
|
||||
$this->php_events[] = $line;
|
||||
}
|
||||
return $line;
|
||||
}
|
||||
|
||||
public function udrpc_command_send_chunk($response, $data, $name_indicator) {
|
||||
|
||||
if (!preg_match('/^([a-f0-9]+)\.migrator.updraftplus.com$/', $name_indicator, $matches)) return $response;
|
||||
$name_hash = $matches[1];
|
||||
|
||||
$this->initialise_listener_error_handling($name_hash);
|
||||
|
||||
global $updraftplus;
|
||||
|
||||
// send_message('send_chunk', array('file' => $file, 'data' => $chunk, 'start' => $upload_start))
|
||||
|
||||
if (!is_array($data)) return $this->return_rpc_message(array('response' => 'error', 'data' => 'invalid_input_expected_array'));
|
||||
|
||||
if (!isset($data['file'])) return $this->return_rpc_message(array('response' => 'error', 'data' => 'invalid_input_no_file'));
|
||||
|
||||
if (!isset($data['data'])) return $this->return_rpc_message(array('response' => 'error', 'data' => 'invalid_input_no_data'));
|
||||
|
||||
if (!isset($data['start'])) return $this->return_rpc_message(array('response' => 'error', 'data' => 'invalid_input_no_start'));
|
||||
|
||||
// Make sure the parameters are valid
|
||||
if (!is_numeric($data['start']) || absint($data['start']) != $data['start']) return $this->return_rpc_message(array('response' => 'error', 'data' => 'invalid_start'));
|
||||
|
||||
// Sanity-check the file name
|
||||
$file = $data['file'];
|
||||
if (!preg_match('/(-db\.gz|-db\.gz\.crypt|-db|\.(sql|sql\.gz|sql\.bz2|zip|tar|tar\.bz2|tar\.gz|txt))/i', $file)) return array('response' => 'error', 'data' => 'illegal_file_name1');
|
||||
if (basename($file) != $file) return $this->return_rpc_message(array('response' => 'error', 'data' => 'invalid_input_illegal_character'));
|
||||
|
||||
$start = $data['start'];
|
||||
|
||||
$is_last_chunk = empty($data['last_chunk']) ? 0 : 1;
|
||||
if (!$is_last_chunk) {
|
||||
} else {
|
||||
$orig_file = $file;
|
||||
if (!empty($data['label'])) $label = $data['label'];
|
||||
}
|
||||
$file .= '.tmp';
|
||||
|
||||
// Intentionally over-write the variable, in case memory is short and in case PHP's garbage collector is this clever
|
||||
$data = base64_decode($data['data']);
|
||||
|
||||
$updraft_dir = $updraftplus->backups_dir_location();
|
||||
$fullpath = $updraft_dir.'/'.$file;
|
||||
|
||||
$existing_size = file_exists($fullpath) ? filesize($fullpath) : 0;
|
||||
|
||||
if ($start > $existing_size) {
|
||||
return $this->return_rpc_message(array('response' => 'error', 'data' => "invalid_start_too_big:start={$start},existing_size={$existing_size}"));
|
||||
}
|
||||
|
||||
if (false == ($fhandle = fopen($fullpath, 'ab'))) {
|
||||
return $this->return_rpc_message(array('response' => 'error', 'data' => 'file_open_failure'));
|
||||
}
|
||||
|
||||
// fseek() returns 0 for success, or -1 for failure
|
||||
if ($start != $existing_size && -1 == fseek($fhandle, $start)) return $this->return_rpc_message(array('response' => 'error', 'data' => 'fseek_failure'));
|
||||
|
||||
$write_status = fwrite($fhandle, $data);
|
||||
|
||||
if (false === $write_status || (false == $write_status && !empty($data))) return $this->return_rpc_message(array('response' => 'error', 'data' => 'fwrite_failure'));
|
||||
|
||||
@fclose($fhandle);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
|
||||
|
||||
$our_keys = UpdraftPlus_Options::get_updraft_option('updraft_migrator_localkeys');
|
||||
if (is_array($our_keys) && isset($our_keys[$name_hash]) && !empty($our_keys[$name_hash]['name'])) $updraftplus->log("Received data chunk on key ".$our_keys[$name_hash]['name']. " ($file, ".$start.", is_last=$is_last_chunk)");
|
||||
|
||||
if ($is_last_chunk) {
|
||||
if (!rename($fullpath, $updraft_dir.'/'.$orig_file)) return $this->return_rpc_message(array('response' => 'error', 'data' => 'rename_failure'));
|
||||
$only_add_this_file = array('file' => $orig_file);
|
||||
if (isset($label)) $only_add_this_file['label'] = $label;
|
||||
UpdraftPlus_Backup_History::rebuild(false, $only_add_this_file);
|
||||
}
|
||||
|
||||
return $this->return_rpc_message(array(
|
||||
'response' => 'file_status',
|
||||
'data' => $this->get_file_status($file)
|
||||
));
|
||||
}
|
||||
|
||||
protected function get_file_status($file) {
|
||||
|
||||
global $updraftplus;
|
||||
$fullpath = $updraftplus->backups_dir_location().'/'.basename($file);
|
||||
|
||||
if (file_exists($fullpath)) {
|
||||
$size = filesize($fullpath);
|
||||
$status = 1;
|
||||
} elseif (file_exists($fullpath.'.tmp')) {
|
||||
$size = filesize($fullpath.'.tmp');
|
||||
$status = 0;
|
||||
} else {
|
||||
$size = 0;
|
||||
$status = 0;
|
||||
}
|
||||
|
||||
return array(
|
||||
'size' => $size,
|
||||
'status' => $status,
|
||||
);
|
||||
}
|
||||
|
||||
public function udrpc_command_get_file_status($response, $data, $name_indicator) {
|
||||
if (!preg_match('/^([a-f0-9]+)\.migrator.updraftplus.com$/', $name_indicator, $matches)) return $response;
|
||||
$name_hash = $matches[1];
|
||||
|
||||
$this->initialise_listener_error_handling($name_hash);
|
||||
|
||||
if (!is_string($data)) return $this->return_rpc_message(array('response' => 'error', 'data' => 'invalid_input_expected_string'));
|
||||
|
||||
if (basename($data) != $data) return $this->return_rpc_message(array('response' => 'error', 'data' => 'invalid_input_illegal_character'));
|
||||
|
||||
return $this->return_rpc_message(array(
|
||||
'response' => 'file_status',
|
||||
'data' => $this->get_file_status($data)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will return a response to the remote site to acknowledge that we have received the upload_complete message and if this is a clone it call the ready_for_restore action
|
||||
*
|
||||
* @param string $response - a string response
|
||||
* @param array $data - an array of data
|
||||
* @param string $name_indicator - a string to identify the request
|
||||
*
|
||||
* @return array - the array response
|
||||
*/
|
||||
public function udrpc_command_upload_complete($response, $data, $name_indicator) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Unused parameter is present because the method is used as a WP filter.
|
||||
if (!preg_match('/^([a-f0-9]+)\.migrator.updraftplus.com$/', $name_indicator, $matches)) return $response;
|
||||
|
||||
if (defined('UPDRAFTPLUS_THIS_IS_CLONE') && UPDRAFTPLUS_THIS_IS_CLONE) {
|
||||
$job_id = (is_array($data) && !empty($data['job_id'])) ? $data['job_id'] : null;
|
||||
|
||||
$signal_ready_for_restore_now = true;
|
||||
|
||||
if (class_exists('UpdraftPlus_Remote_Communications_V2')) {
|
||||
$test_udrpc = new UpdraftPlus_Remote_Communications_V2();
|
||||
if (version_compare($test_udrpc->version, '1.4.21', '>=')) {
|
||||
$signal_ready_for_restore_now = false;
|
||||
$this->job_id = $job_id;
|
||||
add_action('udrpc_action_send_response', array($this, 'udrpc_action_send_response'));
|
||||
}
|
||||
}
|
||||
|
||||
if ($signal_ready_for_restore_now) {
|
||||
do_action('updraftplus_temporary_clone_ready_for_restore', $job_id);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->return_rpc_message(array(
|
||||
'response' => 'file_status',
|
||||
'data' => ''
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* UpdraftPlus_Remote_Communications is going to echo a response and then die. We pre-empt it.
|
||||
*
|
||||
* @param String $response
|
||||
*/
|
||||
public function udrpc_action_send_response($response) {
|
||||
|
||||
global $updraftplus;
|
||||
|
||||
$updraftplus->close_browser_connection($response);
|
||||
|
||||
do_action('updraftplus_temporary_clone_ready_for_restore', $this->job_id);
|
||||
|
||||
die;
|
||||
|
||||
}
|
||||
|
||||
public function updraftplus_initial_jobdata($initial_jobdata, $options, $split_every) {
|
||||
|
||||
if (is_array($options) && !empty($options['extradata']) && !empty($options['extradata']['services']) && preg_match('#remotesend/(\d+)#', $options['extradata']['services'], $matches)) {
|
||||
|
||||
// Load the option now - don't wait until send time
|
||||
$site_id = $matches[1];
|
||||
$remotesites = UpdraftPlus_Options::get_updraft_option('updraft_remotesites');
|
||||
if (!is_array($remotesites)) $remotesites = array();
|
||||
|
||||
if (empty($remotesites[$site_id]) || empty($remotesites[$site_id]['url']) || empty($remotesites[$site_id]['key']) || empty($remotesites[$site_id]['name_indicator'])) {
|
||||
throw new Exception("Remote site id ($site_id) not found - send aborted"); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error messages should be escaped when caught and printed.
|
||||
}
|
||||
|
||||
array_push($initial_jobdata, 'remotesend_info', $remotesites[$site_id]);
|
||||
|
||||
// Reduce to 100MB if it was above. Since the user isn't expected to directly manipulate these zip files, the potentially higher number of zip files doesn't matter.
|
||||
$split_every_key = array_search('split_every', $initial_jobdata) + 1;
|
||||
if ($split_every > 100) $initial_jobdata[$split_every_key] = 100;
|
||||
|
||||
}
|
||||
|
||||
return $initial_jobdata;
|
||||
}
|
||||
|
||||
public function updraft_printjob_beforewarnings($ret, $jobdata) {
|
||||
if (!empty($jobdata['remotesend_info']) && !empty($jobdata['remotesend_info']['url'])) {
|
||||
$ret .= '<p style="padding:0px; margin:2px 0;">'.__('Backup data will be sent to:', 'updraftplus').' '.htmlspecialchars($jobdata['remotesend_info']['url']).'</p>';
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function updraft_remote_ping_test($data) {
|
||||
|
||||
if (!isset($data['id']) || !is_numeric($data['id']) || empty($data['url'])) die;
|
||||
|
||||
$remote_indicator = $data['id'];
|
||||
|
||||
$ping_result = $this->do_ping_test($remote_indicator, $data['url']);
|
||||
|
||||
die(json_encode($ping_result));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Do an RPC ping test
|
||||
*
|
||||
* @param String $remote_indicator
|
||||
* @param String $url
|
||||
*
|
||||
* @return Array - results
|
||||
*/
|
||||
public function do_ping_test($remote_indicator, $url) {
|
||||
|
||||
global $updraftplus;
|
||||
|
||||
$remotesites = UpdraftPlus_Options::get_updraft_option('updraft_remotesites');
|
||||
if (!is_array($remotesites)) $remotesites = array();
|
||||
|
||||
if (empty($remotesites[$remote_indicator]) || $url != $remotesites[$remote_indicator]['url'] || empty($remotesites[$remote_indicator]['key']) || empty($remotesites[$remote_indicator]['name_indicator'])) {
|
||||
return array('e' => 1, 'r' => __('Error:', 'updraftplus').' '.__('site not found', 'updraftplus'));
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
$updraftplus->error_reporting_stop_when_logged = true;
|
||||
$error_levels = version_compare(PHP_VERSION, '8.4.0', '>=') ? E_ALL : E_ALL & ~E_STRICT;
|
||||
set_error_handler(array($updraftplus, 'php_error'), $error_levels);
|
||||
$this->php_events = array();
|
||||
add_filter('updraftplus_logline', array($this, 'updraftplus_logline'), 10, 4);
|
||||
|
||||
$opts = $remotesites[$remote_indicator];
|
||||
$ud_rpc = $updraftplus->get_udrpc($opts['name_indicator']);
|
||||
$send_data = null;
|
||||
|
||||
if (!empty($opts['format_support']) && 2 == $opts['format_support']) {
|
||||
if (empty($opts['remote_got_public'])) {
|
||||
// Can't upgrade to format 2 until we know the other end has our public key
|
||||
$use_format = 1;
|
||||
$send_data = array('sender_public' => $opts['local_public']);
|
||||
} else {
|
||||
$use_format = 2;
|
||||
}
|
||||
} else {
|
||||
$use_format = 1;
|
||||
}
|
||||
|
||||
$ud_rpc->set_message_format($use_format);
|
||||
|
||||
if (2 == $use_format) {
|
||||
$ud_rpc->set_key_remote($opts['key']);
|
||||
$ud_rpc->set_key_local($opts['local_private']);
|
||||
} else {
|
||||
$ud_rpc->set_key_local($opts['key']);
|
||||
}
|
||||
|
||||
$ud_rpc->set_destination_url($url);
|
||||
$ud_rpc->activate_replay_protection();
|
||||
|
||||
do_action('updraftplus_remotesend_udrpc_object_obtained', $ud_rpc, $opts);
|
||||
|
||||
$response = $ud_rpc->send_message('ping', $send_data);
|
||||
|
||||
restore_error_handler();
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
|
||||
$err_msg = __('Error:', 'updraftplus').' '.$response->get_error_message();
|
||||
$err_data = $response->get_error_data();
|
||||
$err_code = $response->get_error_code();
|
||||
|
||||
if (!is_numeric($err_code) && isset($err_data['response']['code'])) {
|
||||
$err_code = $err_data['response']['code'];
|
||||
$err_msg = __('Error:', 'updraftplus').' '.UpdraftPlus_HTTP_Error_Descriptions::get_http_status_code_description($err_code);
|
||||
} elseif (is_string($err_data) && preg_match('/captcha|verify.*human|turnstile/i', $err_data)) {
|
||||
$err_msg = __('Error:', 'updraftplus').' '.__('We are unable to proceed with the process due to a bot verification requirement', 'updraftplus');
|
||||
}
|
||||
} elseif (!is_array($response) || empty($response['response']) || 'pong' != $response['response']) {
|
||||
|
||||
$err_msg = __('Error:', 'updraftplus').' '.sprintf(__('You should check that the remote site is online, not firewalled, bot verification setting is disabled, does not have security modules that may be blocking access, has UpdraftPlus version %s or later active and that the keys have been entered correctly.', 'updraftplus'), '2.10.3');
|
||||
$err_data = $response;
|
||||
$err_code = 'no_pong';
|
||||
|
||||
} elseif (!empty($response['data']['got_public'])) {
|
||||
$remotesites[$remote_indicator]['remote_got_public'] = 1;
|
||||
UpdraftPlus_Options::update_updraft_option('updraft_remotesites', $remotesites);
|
||||
}
|
||||
|
||||
if (isset($err_msg)) {
|
||||
|
||||
$res = array('e' => 1, 'r' => $err_msg);
|
||||
|
||||
if ($this->url_looks_internal($url)) {
|
||||
$res['moreinfo'] = '<p>'.sprintf(__('The site URL you are sending to (%s) looks like a local development website.', 'updraftplus'), htmlspecialchars($url)).' '.__('If you are sending from an external network, it is likely that a firewall will be blocking this.', 'updraftplus').'</p>';
|
||||
}
|
||||
|
||||
// We got several support requests from people who didn't seem to be aware of other methods
|
||||
$msg_try_other_method = '<p>'.__('If sending directly from site to site does not work for you, then there are three other methods - please try one of these instead.', 'updraftplus').' <a href="https://updraftplus.com/faqs/how-do-i-migrate-to-a-new-site-location/#importing" target="_blank">'.__('For longer help, including screenshots, follow this link.', 'updraftplus').'</a></p>';
|
||||
|
||||
$res['moreinfo'] = isset($res['moreinfo']) ? $res['moreinfo'].$msg_try_other_method : $msg_try_other_method;
|
||||
|
||||
if (isset($err_data)) $res['data'] = $err_data;
|
||||
if (isset($err_code)) $res['code'] = $err_code;
|
||||
|
||||
if (!empty($this->php_events)) $res['php_events'] = $this->php_events;
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
$ret = '<p>'.__('Testing connection...', 'updraftplus').' '.__('OK', 'updraftplus').'</p>';
|
||||
|
||||
global $updraftplus_admin;
|
||||
|
||||
$ret .= '<label class="updraft_checkbox" for="remotesend_backupnow_db"><input type="checkbox" checked="checked" id="remotesend_backupnow_db">'.__("Database", 'updraftplus').'(<a href="#" id="backupnow_database_showmoreoptions">...</a>)</label>';
|
||||
$ret .= '<div id="backupnow_database_moreoptions" class="updraft-hidden" style="display:none;">';
|
||||
$ret .= apply_filters('updraftplus_migration_additional_ui', $ret, '');
|
||||
$ret .= '</div>';
|
||||
|
||||
$ret .= $updraftplus_admin->files_selector_widgetry('remotesend_', false, false);
|
||||
|
||||
$service = $updraftplus->just_one(UpdraftPlus_Options::get_updraft_option('updraft_service'));
|
||||
if (is_string($service)) $service = array($service);
|
||||
|
||||
if (is_array($service) && !empty($service) && array('none') !== $service) {
|
||||
$first_one = true;
|
||||
foreach ($service as $s) {
|
||||
if (!$s) continue;
|
||||
if (isset($updraftplus->backup_methods[$s])) {
|
||||
if ($first_one) {
|
||||
$first_one = false;
|
||||
$ret .= '<p>';
|
||||
$ret .= '<input type="checkbox" id="remotesend_backupnow_cloud"> <label for="remotesend_backupnow_cloud">'.__("Also send this backup to the active remote storage locations", 'updraftplus');
|
||||
$ret .= ' (';
|
||||
} else {
|
||||
$ret .= ', ';
|
||||
}
|
||||
$ret .= $updraftplus->backup_methods[$s];
|
||||
}
|
||||
}
|
||||
if (!$first_one) $ret .= ')';
|
||||
$ret .= '</label></p>';
|
||||
}
|
||||
|
||||
$ret .= apply_filters('updraft_backupnow_modal_afteroptions', '', 'remotesend_');
|
||||
$ret .= '<button class="button-primary" style="font-size:16px; margin-left: 3px; width:85px;" id="updraft_migrate_send_button" onclick="updraft_migrate_go_backup();">'.__('Send', 'updraftplus').'</button>';
|
||||
|
||||
return array('success' => 1, 'r' => $ret);
|
||||
} catch (Exception $e) {
|
||||
return array('e' => 1, 'r' => __('Error:', 'updraftplus').' '.$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used only for an advisory warning - does not have to be able to always detect
|
||||
*
|
||||
* @param string $url
|
||||
*/
|
||||
protected function url_looks_internal($url) {
|
||||
$url_host = strtolower(parse_url($url, PHP_URL_HOST));
|
||||
if ('localhost' == $url_host || strpos($url_host, '127.') === 0 || strpos($url_host, '10.') === 0 || '::1' == $url_host || strpos($url_host, 'localhost') !== false || substr($url_host, -4, 4) == '.dev') return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public function updraft_migrate_key_delete($data) {
|
||||
if (empty($data['keyid'])) die;
|
||||
$our_keys = UpdraftPlus_Options::get_updraft_option('updraft_migrator_localkeys');
|
||||
if (!is_array($our_keys)) $our_keys = array();
|
||||
unset($our_keys[$data['keyid']]);
|
||||
UpdraftPlus_Options::update_updraft_option('updraft_migrator_localkeys', $our_keys);
|
||||
echo json_encode(array('ourkeys' => $this->list_our_keys($our_keys)));
|
||||
die;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is a wrapper for updraft_migrate_key_create when being called from WP_CLI it allows us to return the created key rather than echo it, by passing return_instead_of_echo as part of $data.
|
||||
*
|
||||
* @param string $string - empty string to filter on
|
||||
* @param array $data - an array of data needed to create the RSA keypair should also include return_instead_of_echo to return the result
|
||||
*
|
||||
* @return string - the RSA remote key
|
||||
*/
|
||||
public function updraft_migrate_key_create_return($string, $data) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Unused parameter is present because the method is used as a WP filter.
|
||||
return $this->updraft_migrate_key_create($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called upon the WP action updraft_s3_newuser. Dies.
|
||||
*
|
||||
* @param array $data - the posted data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function updraft_migrate_key_create($data) {
|
||||
|
||||
if (empty($data['name'])) die;
|
||||
$name = stripslashes($data['name']);
|
||||
|
||||
$size = (empty($data['size']) || !is_numeric($data['size']) || $data['size'] < 1024) ? 2048 : (int) $data['size'];
|
||||
|
||||
$name_hash = md5($name); // 32 characters
|
||||
$indicator_name = $name_hash.'.migrator.updraftplus.com';
|
||||
|
||||
$our_keys = UpdraftPlus_Options::get_updraft_option('updraft_migrator_localkeys');
|
||||
if (!is_array($our_keys)) $our_keys = array();
|
||||
|
||||
if (isset($our_keys[$name_hash])) {
|
||||
echo json_encode(array('e' => 1, 'r' => __('Error:', 'updraftplus').' '.__('A key with this name already exists; you must use a unique name.', 'updraftplus')));
|
||||
die;
|
||||
}
|
||||
|
||||
global $updraftplus;
|
||||
$ud_rpc = $updraftplus->get_udrpc($indicator_name);
|
||||
|
||||
if (is_object($ud_rpc) && $ud_rpc->generate_new_keypair($size)) {
|
||||
$local_bundle = $ud_rpc->get_portable_bundle('base64_with_count');
|
||||
|
||||
$our_keys[$name_hash] = array('name' => $name, 'key' => $ud_rpc->get_key_local());
|
||||
UpdraftPlus_Options::update_updraft_option('updraft_migrator_localkeys', $our_keys);
|
||||
|
||||
if (isset($data['return_instead_of_echo']) && $data['return_instead_of_echo']) return $local_bundle;
|
||||
|
||||
echo json_encode(array(
|
||||
'bundle' => $local_bundle,
|
||||
'r' => __('Key created successfully.', 'updraftplus').' '.__('You must copy and paste this key on the sending site now - it cannot be shown again.', 'updraftplus'),
|
||||
'selector' => $this->get_remotesites_selector(),
|
||||
'ourkeys' => $this->list_our_keys($our_keys),
|
||||
));
|
||||
die;
|
||||
}
|
||||
|
||||
if (extension_loaded('mbstring')) {
|
||||
// phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.mbstring_func_overloadDeprecated -- Commented out as this flags as not compatible with PHP 5.2
|
||||
if (ini_get('mbstring.func_overload') & 2) {
|
||||
echo json_encode(array('e' => 1, 'r' => __('Error:', 'updraftplus').' '.sprintf(__('The setting %s is turned on in your PHP settings.', 'updraftplus'), 'mbstring.func_overload').' '.__('It is deprecated, causes encryption to malfunction, and should be turned off.', 'updraftplus')));
|
||||
die;
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode(array('e' => 1));
|
||||
die;
|
||||
}
|
||||
|
||||
public function updraft_migrate_newdestination($data) {
|
||||
|
||||
global $updraftplus;
|
||||
$ret = array();
|
||||
|
||||
if (empty($data['key'])) {
|
||||
$ret['e'] = sprintf(__("Failure: No %s was given.", 'updraftplus'), __('key', 'updraftplus'));
|
||||
} else {
|
||||
|
||||
// The indicator isn't really needed - we won't be receiving on it
|
||||
$our_indicator = md5(network_site_url()).'.migrator.updraftplus.com';
|
||||
$ud_rpc = $updraftplus->get_udrpc($our_indicator);
|
||||
|
||||
$ud_rpc->set_can_generate(true);
|
||||
|
||||
// A bundle has these keys: key, name_indicator, url
|
||||
$decode_bundle = $ud_rpc->decode_portable_bundle($data['key'], 'base64_with_count');
|
||||
|
||||
if (!is_array($decode_bundle) || !empty($decode_bundle['code'])) {
|
||||
$ret['e'] = __('Error:', 'updraftplus');
|
||||
if (!empty($decode_bundle['code']) && 'invalid_wrong_length' == $decode_bundle['code']) {
|
||||
$ret['e'] .= ' '.__('The entered key was the wrong length - please try again.', 'updraftplus');
|
||||
} elseif (!empty($decode_bundle['code']) && 'invalid_corrupt' == $decode_bundle['code']) {
|
||||
$ret['e'] .= ' '.__('The entered key was corrupt - please try again.', 'updraftplus').' ('.$decode_bundle['data'].')';
|
||||
} elseif (empty($decode_bundle['key']) || empty($decode_bundle['url'])) {
|
||||
$ret['e'] .= ' '.__('The entered key was corrupt - please try again.', 'updraftplus');
|
||||
$ret['data'] = $decode_bundle;
|
||||
}
|
||||
} elseif (empty($decode_bundle['key']) || empty($decode_bundle['url'])) {
|
||||
$ret['e'] = __('Error:', 'updraftplus').' '.__('The entered key was corrupt - please try again.', 'updraftplus');
|
||||
$ret['data'] = $decode_bundle;
|
||||
} else {
|
||||
|
||||
if (trailingslashit(network_site_url()) == $decode_bundle['url']) {
|
||||
$ret['e'] = __('Error:', 'updraftplus').' '.__('The entered key does not belong to a remote site (it belongs to this one).', 'updraftplus');
|
||||
} else {
|
||||
|
||||
// Store the information
|
||||
$remotesites = UpdraftPlus_Options::get_updraft_option('updraft_remotesites');
|
||||
if (!is_array($remotesites)) $remotesites = array();
|
||||
foreach ($remotesites as $k => $rsite) {
|
||||
if (!is_array($rsite)) continue;
|
||||
if ($rsite['url'] == $decode_bundle['url']) unset($remotesites[$k]);
|
||||
}
|
||||
|
||||
if (false == $ud_rpc->generate_new_keypair()) {
|
||||
$ret['e'] = __('Error:', 'updraftplus').' An error occurred when attempting to generate a new key-pair';
|
||||
} else {
|
||||
|
||||
$decode_bundle['local_private'] = $ud_rpc->get_key_local();
|
||||
$decode_bundle['local_public'] = $ud_rpc->get_key_remote();
|
||||
|
||||
$remotesites[] = $decode_bundle;
|
||||
UpdraftPlus_Options::update_updraft_option('updraft_remotesites', $remotesites);
|
||||
|
||||
$ret['selector'] = $this->get_remotesites_selector($remotesites);
|
||||
|
||||
// Return the new HTML widget to the front end
|
||||
$ret['r'] = __('The key was successfully added.', 'updraftplus').' '.__('It is for sending backups to the following site: ', 'updraftplus').htmlspecialchars($decode_bundle['url']);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
echo json_encode($ret);
|
||||
die;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display or return html for selecting receiving remote site.
|
||||
*
|
||||
* @param bool|array $remotesites Remote sites. Default false for fetching from options.
|
||||
* @param bool $echo_instead_of_return Whether to display html or return it.
|
||||
* @return void|string Display or return HTML for selecting remotesites.
|
||||
*/
|
||||
protected function get_remotesites_selector($remotesites = false, $echo_instead_of_return = false) {
|
||||
|
||||
if (false === $remotesites) {
|
||||
$remotesites = UpdraftPlus_Options::get_updraft_option('updraft_remotesites');
|
||||
if (!is_array($remotesites)) $remotesites = array();
|
||||
}
|
||||
|
||||
if (!$echo_instead_of_return) ob_start();
|
||||
|
||||
if (empty($remotesites)) {
|
||||
?>
|
||||
<p id="updraft_migrate_receivingsites_nonemsg">
|
||||
<em><?php esc_html_e('No receiving sites have yet been added.', 'updraftplus');?></em>
|
||||
</p>
|
||||
<?php
|
||||
} else {
|
||||
?>
|
||||
<p class="updraftplus-remote-sites-selector">
|
||||
<label><?php esc_html_e('Send to site:', 'updraftplus');?></label>
|
||||
<select id="updraft_remotesites_selector">
|
||||
<?php
|
||||
foreach ($remotesites as $k => $rsite) {
|
||||
if (!is_array($rsite) || empty($rsite['url'])) continue;
|
||||
?>
|
||||
<option value="<?php echo esc_attr($k);?>"><?php echo esc_html($rsite['url']);?></option>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<button class="button-primary" id="updraft_migrate_send_button" onclick="updraft_migrate_send_backup_options();"><?php esc_html_e('Send', 'updraftplus');?></button>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="text-link-menu">
|
||||
<a href="#" class="updraft_migrate_add_site--trigger"><span class="dashicons dashicons-plus"></span><?php esc_html_e('Add a site', 'updraftplus');?></a>
|
||||
<a href="#" class="updraft_migrate_clear_sites" <?php empty($remotesites) ? 'style="display: none"' : '';?>
|
||||
onclick="event.preventDefault();updraft_migrate_delete_existingsites('<?php echo esc_js(__('You are about to permanently delete the list of existing sites.', 'updraftplus').' '.__('This action cannot be undone.', 'updraftplus').' '.__('\'Cancel\' to stop, \'OK\' to delete.'));?>');">
|
||||
<span class="dashicons dashicons-trash"></span>
|
||||
<?php esc_html_e('Clear list of existing sites', 'updraftplus');?>
|
||||
</a>
|
||||
</div>
|
||||
<?php
|
||||
if (!$echo_instead_of_return) return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays or returns html for listing keys for migrating sites.
|
||||
*
|
||||
* @param bool|array $our_keys Keys for site migration.
|
||||
* @param bool $echo_instead_of_return Whether to display HTML instead of returning.
|
||||
* @return void|string Display or return html for key selector for migration.
|
||||
*/
|
||||
protected function list_our_keys($our_keys = false, $echo_instead_of_return = false) {
|
||||
if (!$echo_instead_of_return) ob_start();
|
||||
if (false === $our_keys) {
|
||||
$our_keys = UpdraftPlus_Options::get_updraft_option('updraft_migrator_localkeys', array());
|
||||
}
|
||||
|
||||
if (empty($our_keys)) {
|
||||
?>
|
||||
<em><?php esc_html_e('No keys to allow remote sites to send backup data here have yet been created.', 'updraftplus');?></em>
|
||||
<?php
|
||||
} else {
|
||||
$first_one = true;
|
||||
foreach ($our_keys as $k => $key) {
|
||||
if (!is_array($key)) continue;
|
||||
if ($first_one) {
|
||||
$first_one = false;
|
||||
?>
|
||||
<p><strong><?php esc_html_e('Existing keys', 'updraftplus');?></strong><br>
|
||||
<?php
|
||||
}
|
||||
echo esc_html(($key['name'])).' - ';
|
||||
?>
|
||||
<a href="<?php echo esc_url(UpdraftPlus::get_current_clean_url());?>" onclick="updraft_migrate_local_key_delete('<?php echo esc_js($k);?>'); return false;" class="updraft_migrate_local_key_delete" data-keyid="<?php echo esc_attr($k);?>"><?php esc_html_e('Delete', 'updraftplus');?></a>
|
||||
<br>
|
||||
<?php
|
||||
}
|
||||
|
||||
if (!$first_one) echo "</p>"; // Handling the edge case where no <p> tag was opened earlier.
|
||||
}
|
||||
|
||||
if (!$echo_instead_of_return) return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the list of existing remote sites from the database
|
||||
*
|
||||
* @return String The JSON format of the response of the deletion process
|
||||
*/
|
||||
public function updraft_migrate_delete_existingsites() {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$ret = array();
|
||||
|
||||
$old_val = $wpdb->suppress_errors();
|
||||
|
||||
UpdraftPlus_Options::delete_updraft_option('updraft_remotesites');
|
||||
|
||||
$remote_sites = UpdraftPlus_Options::get_updraft_option('updraft_remotesites');
|
||||
|
||||
if (is_array($remote_sites) && !empty($remote_sites)) {
|
||||
$err_msg = __('There was an error while trying to remove the list of existing sites.', 'updraftplus');
|
||||
$err_db = !empty($wpdb->last_error) ? ' ('.$wpdb->last_error.' - '.$wpdb->last_query.')' : '';
|
||||
$ret['error'] = $err_msg.$err_db;
|
||||
} else {
|
||||
$ret['success'] = __('The list of existing sites has been removed', 'updraftplus');
|
||||
$ret['html'] = $this->get_remotesites_selector();
|
||||
}
|
||||
|
||||
$wpdb->suppress_errors($old_val);
|
||||
|
||||
echo json_encode($ret);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,522 @@
|
||||
<?php
|
||||
|
||||
if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed');
|
||||
|
||||
class UpdraftPlus_Search_Replace {
|
||||
|
||||
private $known_incomplete_classes = array();
|
||||
|
||||
private $columns = array();
|
||||
|
||||
private $current_row = 0;
|
||||
|
||||
private $use_wpdb = false;
|
||||
|
||||
private $use_mysqli = false;
|
||||
|
||||
private $wpdb_obj = null;
|
||||
|
||||
private $mysql_dbh = null;
|
||||
|
||||
protected $max_recursion = 0;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action('updraftplus_restore_db_pre', array($this, 'updraftplus_restore_db_pre'));
|
||||
$this->max_recursion = apply_filters('updraftplus_search_replace_max_recursion', 20);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called via the filter updraftplus_restore_db_pre it sets up the search and replace database objects
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function updraftplus_restore_db_pre() {
|
||||
global $wpdb, $updraftplus_restorer;
|
||||
|
||||
$this->use_wpdb = $updraftplus_restorer->use_wpdb();
|
||||
$this->wpdb_obj = $wpdb;
|
||||
|
||||
$mysql_dbh = false;
|
||||
$use_mysqli = false;
|
||||
|
||||
if (!$this->use_wpdb) {
|
||||
// We have our own extension which drops lots of the overhead on the query
|
||||
$wpdb_obj = $updraftplus_restorer->get_db_object();
|
||||
// Was that successful?
|
||||
if (!$wpdb_obj->is_mysql || !$wpdb_obj->ready) {
|
||||
$this->use_wpdb = true;
|
||||
} else {
|
||||
$this->wpdb_obj = $wpdb_obj;
|
||||
$mysql_dbh = $wpdb_obj->updraftplus_get_database_handle();
|
||||
$use_mysqli = $wpdb_obj->updraftplus_use_mysqli();
|
||||
}
|
||||
}
|
||||
|
||||
$this->mysql_dbh = $mysql_dbh;
|
||||
$this->use_mysqli = $use_mysqli;
|
||||
}
|
||||
|
||||
/**
|
||||
* The engine
|
||||
*
|
||||
* @param string|array $search - a string or array of things to search for
|
||||
* @param string|array $replace - a string or array of things to replace the search terms with
|
||||
* @param array $tables - an array of tables
|
||||
* @param integer $page_size - the page size
|
||||
*/
|
||||
public function icit_srdb_replacer($search, $replace, $tables, $page_size) {
|
||||
|
||||
if (!is_array($tables)) return false;
|
||||
|
||||
global $wpdb, $updraftplus;
|
||||
|
||||
$report = array(
|
||||
'tables' => 0,
|
||||
'rows' => 0,
|
||||
'change' => 0,
|
||||
'updates' => 0,
|
||||
'start' => microtime(true),
|
||||
'end' => microtime(true),
|
||||
'errors' => array(),
|
||||
);
|
||||
|
||||
$page_size = (empty($page_size) || !is_numeric($page_size)) ? 5000 : $page_size;
|
||||
|
||||
foreach ($tables as $table => $stripped_table) {
|
||||
|
||||
$report['tables']++;
|
||||
|
||||
if ($search === $replace) {
|
||||
$updraftplus->log("No search/replace required: would-be search and replacement are identical");
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->columns = array();
|
||||
|
||||
$print_line = __('Search and replacing table:', 'updraftplus').' '.$table;
|
||||
|
||||
$updraftplus->check_db_connection($this->wpdb_obj, true);
|
||||
|
||||
// Get a list of columns in this table
|
||||
$fields = $wpdb->get_results('DESCRIBE '.UpdraftPlus_Manipulation_Functions::backquote($table), ARRAY_A);
|
||||
|
||||
$prikey_field = false;
|
||||
foreach ($fields as $column) {
|
||||
$primary_key = ('PRI' == $column['Key']) ? true : false;
|
||||
if ($primary_key) $prikey_field = $column['Field'];
|
||||
if ('posts' == $stripped_table && 'guid' == $column['Field']) {
|
||||
$updraftplus->log('Skipping search/replace on GUID column in posts table');
|
||||
continue;
|
||||
}
|
||||
$this->columns[$column['Field']] = $primary_key;
|
||||
}
|
||||
|
||||
// Count the number of rows we have in the table if large we'll split into blocks, This is a mod from Simon Wheatley
|
||||
|
||||
// InnoDB does not do count(*) quickly. You can use an index for more speed - see: http://www.cloudspace.com/blog/2009/08/06/fast-mysql-innodb-count-really-fast/
|
||||
|
||||
$where = '';
|
||||
// Opportunity to use internal knowledge on tables which may be huge
|
||||
if ('postmeta' == $stripped_table && ((is_array($search) && 0 === strpos($search[0], 'http')) || (is_string($search) && 0 === strpos($search, 'http')))) {
|
||||
$where = " WHERE meta_value LIKE '%http%'";
|
||||
}
|
||||
|
||||
$count_rows_sql = 'SELECT COUNT(*) FROM '.$table;
|
||||
if ($prikey_field) $count_rows_sql .= " USE INDEX (PRIMARY)";
|
||||
$count_rows_sql .= $where;
|
||||
|
||||
$row_countr = $wpdb->get_results($count_rows_sql, ARRAY_N);
|
||||
|
||||
// If that failed, try this
|
||||
if (false !== $prikey_field && $wpdb->last_error) {
|
||||
$row_countr = $wpdb->get_results("SELECT COUNT(*) FROM $table USE INDEX ($prikey_field)".$where, ARRAY_N);
|
||||
if ($wpdb->last_error) $row_countr = $wpdb->get_results("SELECT COUNT(*) FROM $table", ARRAY_N);
|
||||
}
|
||||
|
||||
$row_count = $row_countr[0][0];
|
||||
$print_line .= ': '.sprintf(__('rows: %d', 'updraftplus'), $row_count);
|
||||
$updraftplus->log($print_line, 'notice-restore', 'restoring-table-'.$table);
|
||||
$updraftplus->log('Search and replacing table: '.$table.": rows: ".$row_count);
|
||||
|
||||
if (0 == $row_count) continue;
|
||||
|
||||
for ($on_row = 0; $on_row <= $row_count; $on_row = $on_row+$page_size) {
|
||||
|
||||
$this->current_row = 0;
|
||||
|
||||
if ($on_row>0) $updraftplus->log_e("Searching and replacing reached row: %d", $on_row);
|
||||
|
||||
// Grab the contents of the table
|
||||
list($data, $page_size) = $this->fetch_sql_result($table, $on_row, $page_size, $where);
|
||||
// $sql_line is calculated here only for the purpose of logging errors
|
||||
// $where might contain a %, so don't place it inside the main parameter
|
||||
|
||||
$sql_line = sprintf('SELECT * FROM %s LIMIT %d, %d', $table.$where, $on_row, $on_row+$page_size);
|
||||
|
||||
// Our strategy here is to minimise memory usage if possible; to process one row at a time if we can, rather than reading everything into memory
|
||||
if ($this->use_wpdb) {
|
||||
|
||||
if ($wpdb->last_error) {
|
||||
$report['errors'][] = $this->print_error($sql_line);
|
||||
} else {
|
||||
foreach ($data as $row) {
|
||||
$rowrep = $this->process_row($table, $row, $search, $replace, $stripped_table);
|
||||
$report['rows']++;
|
||||
$report['updates'] += $rowrep['updates'];
|
||||
$report['change'] += $rowrep['change'];
|
||||
foreach ($rowrep['errors'] as $err) $report['errors'][] = $err;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (false === $data) {
|
||||
$report['errors'][] = $this->print_error($sql_line);
|
||||
} elseif (true !== $data && null !== $data) {
|
||||
if ($this->use_mysqli) {
|
||||
while ($row = mysqli_fetch_array($data)) {
|
||||
$rowrep = $this->process_row($table, $row, $search, $replace, $stripped_table);
|
||||
$report['rows']++;
|
||||
$report['updates'] += $rowrep['updates'];
|
||||
$report['change'] += $rowrep['change'];
|
||||
foreach ($rowrep['errors'] as $err) $report['errors'][] = $err;
|
||||
}
|
||||
mysqli_free_result($data);
|
||||
} else {
|
||||
// phpcs:ignore PHPCompatibility.Extensions.RemovedExtensions.mysql_DeprecatedRemoved -- Ignore removed extension compatibility.
|
||||
while ($row = mysql_fetch_array($data)) {
|
||||
$rowrep = $this->process_row($table, $row, $search, $replace, $stripped_table);
|
||||
$report['rows']++;
|
||||
$report['updates'] += $rowrep['updates'];
|
||||
$report['change'] += $rowrep['change'];
|
||||
foreach ($rowrep['errors'] as $err) $report['errors'][] = $err;
|
||||
}
|
||||
@mysql_free_result($data); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged,PHPCompatibility.Extensions.RemovedExtensions.mysql_DeprecatedRemoved -- If an error occurs during mysql free result and it fails to free result, it will not impact anything at all. mysql_* function used in the scenario in which the mysqli extension doesn't exist.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$report['end'] = microtime(true);
|
||||
|
||||
return $report;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will get data from the passed in table ready to be search and replaced
|
||||
*
|
||||
* @param string $table - the table name
|
||||
* @param integer $on_row - the row to start from
|
||||
* @param integer $page_size - the page size
|
||||
* @param string $where - the where condition
|
||||
*
|
||||
* @return array - an array of data or an array with a false value
|
||||
*/
|
||||
private function fetch_sql_result($table, $on_row, $page_size, $where = '') {
|
||||
|
||||
$sql_line = sprintf('SELECT * FROM %s%s LIMIT %d, %d', $table, $where, $on_row, $page_size);
|
||||
|
||||
global $updraftplus;
|
||||
$updraftplus->check_db_connection($this->wpdb_obj, true);
|
||||
|
||||
if ($this->use_wpdb) {
|
||||
global $wpdb;
|
||||
$data = $wpdb->get_results($sql_line, ARRAY_A);
|
||||
if (!$wpdb->last_error) return array($data, $page_size);
|
||||
} else {
|
||||
if ($this->use_mysqli) {
|
||||
$data = mysqli_query($this->mysql_dbh, $sql_line);
|
||||
} else {
|
||||
// phpcs:ignore PHPCompatibility.Extensions.RemovedExtensions.mysql_DeprecatedRemoved -- Ignore removed extension compatibility.
|
||||
$data = mysql_query($sql_line, $this->mysql_dbh);
|
||||
}
|
||||
if (false !== $data) return array($data, $page_size);
|
||||
}
|
||||
|
||||
if (5000 <= $page_size) return $this->fetch_sql_result($table, $on_row, 2000, $where);
|
||||
if (2000 <= $page_size) return $this->fetch_sql_result($table, $on_row, 500, $where);
|
||||
|
||||
// At this point, $page_size should be 500; and that failed
|
||||
return array(false, $page_size);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will process a single row from the database calling recursive_unserialize_replace to search and replace the data found in the search and replace arrays
|
||||
*
|
||||
* @param string $table - the current table we are working on
|
||||
* @param array $row - the current row we are working on
|
||||
* @param array $search - an array of things to search for
|
||||
* @param array $replace - an array of things to replace the search terms with
|
||||
* @param string $stripped_table - the stripped table
|
||||
*
|
||||
* @return array - returns an array report which includes changes made and any errors
|
||||
*/
|
||||
private function process_row($table, $row, $search, $replace, $stripped_table) {
|
||||
|
||||
global $updraftplus, $wpdb, $updraftplus_restorer;
|
||||
|
||||
$report = array('change' => 0, 'errors' => array(), 'updates' => 0);
|
||||
|
||||
$this->current_row++;
|
||||
|
||||
$update_sql = array();
|
||||
$where_sql = array();
|
||||
$upd = false;
|
||||
|
||||
foreach ($this->columns as $column => $primary_key) {
|
||||
|
||||
// Don't search/replace these
|
||||
if (('options' == $stripped_table && 'option_value' == $column && !empty($row['option_name']) && 'updraft_remotesites' == $row['option_name']) || ('sitemeta' == $stripped_table && 'meta_value' == $column && !empty($row['meta_key']) && 'updraftplus_options' == $row['meta_key'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$edited_data = $data_to_fix = $row[$column];
|
||||
$successful = false;
|
||||
|
||||
// We catch errors/exceptions so that they're not fatal. Once saw a fatal ("Cannot access empty property") on "if (is_a($value, '__PHP_Incomplete_Class')) {" (not clear what $value has to be to cause that).
|
||||
try {
|
||||
// Run a search replace on the data that'll respect the serialisation.
|
||||
$edited_data = $this->recursive_unserialize_replace($search, $replace, $data_to_fix);
|
||||
$successful = true;
|
||||
} catch (Exception $e) {
|
||||
$log_message = 'An Exception ('.get_class($e).') occurred during the recursive search/replace. Exception message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
|
||||
$report['errors'][] = $log_message;
|
||||
error_log($log_message);
|
||||
$updraftplus->log($log_message);
|
||||
$updraftplus->log(sprintf(__('A PHP exception (%s) has occurred: %s', 'updraftplus'), get_class($e), $e->getMessage()), 'warning-restore');
|
||||
// @codingStandardsIgnoreLine
|
||||
} catch (Error $e) {
|
||||
$log_message = 'A PHP Fatal error (recoverable, '.get_class($e).') occurred during the recursive search/replace. Exception message: Error message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
|
||||
$report['errors'][] = $log_message;
|
||||
error_log($log_message);
|
||||
$updraftplus->log($log_message);
|
||||
$updraftplus->log(sprintf(__('A PHP fatal error (%s) has occurred: %s', 'updraftplus'), get_class($e), $e->getMessage()), 'warning-restore');
|
||||
}
|
||||
|
||||
// Something was changed
|
||||
if ($successful && $edited_data != $data_to_fix) {
|
||||
$report['change']++;
|
||||
$ed = $edited_data;
|
||||
$wpdb->escape_by_ref($ed);
|
||||
// Undo breakage introduced in WP 4.8.3 core
|
||||
if (is_callable(array($wpdb, 'remove_placeholder_escape'))) $ed = $wpdb->remove_placeholder_escape($ed);
|
||||
$update_sql[] = UpdraftPlus_Manipulation_Functions::backquote($column) . ' = "' . $ed . '"';
|
||||
$upd = true;
|
||||
}
|
||||
|
||||
if ($primary_key) {
|
||||
$df = $data_to_fix;
|
||||
$wpdb->escape_by_ref($df);
|
||||
// Undo breakage introduced in WP 4.8.3 core
|
||||
if (is_callable(array($wpdb, 'remove_placeholder_escape'))) $df = $wpdb->remove_placeholder_escape($df);
|
||||
$where_sql[] = UpdraftPlus_Manipulation_Functions::backquote($column) . ' = "' . $df . '"';
|
||||
}
|
||||
}
|
||||
|
||||
if ($upd && !empty($where_sql)) {
|
||||
$sql = 'UPDATE '.UpdraftPlus_Manipulation_Functions::backquote($table).' SET '.implode(', ', $update_sql).' WHERE '.implode(' AND ', array_filter($where_sql));
|
||||
$result = $updraftplus_restorer->sql_exec($sql, 5, '', false);
|
||||
if (false === $result || is_wp_error($result)) {
|
||||
$last_error = $this->print_error($sql);
|
||||
$report['errors'][] = $last_error;
|
||||
} else {
|
||||
$report['updates']++;
|
||||
}
|
||||
|
||||
} elseif ($upd) {
|
||||
$report['errors'][] = sprintf('"%s" has no primary key, manual change needed on row %s.', $table, $this->current_row);
|
||||
$updraftplus->log(__('Error:', 'updraftplus').' '.sprintf(__('"%s" has no primary key, manual change needed on row %s.', 'updraftplus'), $table, $this->current_row), 'warning-restore');
|
||||
}
|
||||
|
||||
return $report;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspect incomplete class object and make a note in the restoration log if it is a new class
|
||||
*
|
||||
* @param object $data Object expected to be of __PHP_Incomplete_Class_Name
|
||||
*/
|
||||
private function unserialize_log_incomplete_class($data) {
|
||||
global $updraftplus;
|
||||
|
||||
try {
|
||||
$patch_object = new ArrayObject($data);
|
||||
$class_name = $patch_object['__PHP_Incomplete_Class_Name'];
|
||||
} catch (Exception $e) {
|
||||
error_log('unserialize_log_incomplete_class: '.$e->getMessage());
|
||||
// @codingStandardsIgnoreLine
|
||||
} catch (Error $e) {
|
||||
error_log('unserialize_log_incomplete_class: '.$e->getMessage());
|
||||
}
|
||||
|
||||
// Check if this class is known
|
||||
// Have to serialize incomplete class to find original class name
|
||||
if (!in_array($class_name, $this->known_incomplete_classes)) {
|
||||
$this->known_incomplete_classes[] = $class_name;
|
||||
$updraftplus->log('Incomplete object detected in database: '.$class_name.'; Search and replace will be skipped for these entries');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a serialised array and unserialise it replacing elements as needed and
|
||||
* unserialising any subordinate arrays and performing the replace on those too.
|
||||
* N.B. $from and $to can be arrays - they get passed only to str_replace(), which can take an array
|
||||
*
|
||||
* @param string $from String we're looking to replace.
|
||||
* @param string $to What we want it to be replaced with
|
||||
* @param array $data Used to pass any subordinate arrays back to in.
|
||||
* @param bool $serialised Does the array passed via $data need serialising.
|
||||
* @param int $recursion_level Current recursion depth within the original data.
|
||||
* @param array $visited_data Data that has been seen in previous recursion iterations.
|
||||
*
|
||||
* @return array The original array with all elements replaced as needed.
|
||||
*/
|
||||
private function recursive_unserialize_replace($from = '', $to = '', $data = '', $serialised = false, $recursion_level = 0, $visited_data = array()) {
|
||||
|
||||
global $updraftplus;
|
||||
|
||||
static $error_count = 0;
|
||||
|
||||
// some unserialised data cannot be re-serialised eg. SimpleXMLElements
|
||||
try {
|
||||
$case_insensitive = false;
|
||||
|
||||
// If we've reached the maximum recursion level, short circuit
|
||||
if (0 !== $this->max_recursion && $recursion_level >= $this->max_recursion) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
if (is_array($data) || is_object($data)) {
|
||||
// If we've seen this exact object or array before, short circuit
|
||||
if (in_array($data, $visited_data, true)) {
|
||||
return $data; // Avoid infinite recursions when there's a circular reference
|
||||
}
|
||||
// Add this data to the list of
|
||||
$visited_data[] = $data;
|
||||
}
|
||||
|
||||
if (is_array($from) && is_array($to)) {
|
||||
$case_insensitive = preg_match('#^https?:#i', implode($from)) && preg_match('#^https?:#i', implode($to)) ? true : false;
|
||||
} else {
|
||||
$case_insensitive = preg_match('#^https?:#i', $from) && preg_match('#^https?:#i', $to) ? true : false;
|
||||
}
|
||||
|
||||
// O:8:"DateTime":0:{} : see https://bugs.php.net/bug.php?id=62852
|
||||
if (is_serialized($data) && false === strpos($data, 'O:8:"DateTime":0:{}') && false !== ($unserialized = UpdraftPlus::unserialize($data))) {
|
||||
$data = $this->recursive_unserialize_replace($from, $to, $unserialized, true, $recursion_level + 1);
|
||||
} elseif (is_array($data)) {
|
||||
$_tmp = array();
|
||||
foreach ($data as $key => $value) {
|
||||
// Check that we aren't attempting search/replace on an incomplete class
|
||||
// We assume that if $data is an __PHP_Incomplete_Class, it is extremely likely that the original did not contain the domain
|
||||
if (is_a($value, '__PHP_Incomplete_Class')) {
|
||||
// Check if this class is known
|
||||
$this->unserialize_log_incomplete_class($value);
|
||||
|
||||
// return original data
|
||||
$_tmp[$key] = $value;
|
||||
} else {
|
||||
$_tmp[$key] = $this->recursive_unserialize_replace($from, $to, $value, false, $recursion_level + 1, $visited_data);
|
||||
}
|
||||
}
|
||||
|
||||
$data = $_tmp;
|
||||
unset($_tmp);
|
||||
} elseif (is_object($data)) {
|
||||
$_tmp = $data; // new $data_class();
|
||||
// Check that we aren't attempting search/replace on an incomplete class
|
||||
// We assume that if $data is an __PHP_Incomplete_Class, it is extremely likely that the original did not contain the domain
|
||||
if (is_a($data, '__PHP_Incomplete_Class')) {
|
||||
// Check if this class is known
|
||||
$this->unserialize_log_incomplete_class($data);
|
||||
} else {
|
||||
$props = get_object_vars($data);
|
||||
foreach ($props as $key => $value) {
|
||||
$_tmp->$key = $this->recursive_unserialize_replace($from, $to, $value, false, $recursion_level + 1, $visited_data);
|
||||
}
|
||||
}
|
||||
$data = $_tmp;
|
||||
unset($_tmp);
|
||||
} elseif (is_string($data) && (null !== ($_tmp = json_decode($data, true)))) {
|
||||
|
||||
if (is_array($_tmp)) {
|
||||
foreach ($_tmp as $key => $value) {
|
||||
// Check that we aren't attempting search/replace on an incomplete class
|
||||
// We assume that if $data is an __PHP_Incomplete_Class, it is extremely likely that the original did not contain the domain
|
||||
if (is_a($value, '__PHP_Incomplete_Class')) {
|
||||
// Check if this class is known
|
||||
$this->unserialize_log_incomplete_class($value);
|
||||
|
||||
// return original data
|
||||
$_tmp[$key] = $value;
|
||||
} else {
|
||||
$_tmp[$key] = $this->recursive_unserialize_replace($from, $to, $value, false, $recursion_level + 1, $visited_data);
|
||||
}
|
||||
}
|
||||
|
||||
$data = json_encode($_tmp);
|
||||
unset($_tmp);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (is_string($data)) {
|
||||
if ($case_insensitive) {
|
||||
$data = str_ireplace($from, $to, $data);
|
||||
} else {
|
||||
$data = str_replace($from, $to, $data);
|
||||
}
|
||||
// Below is the wrong approach. In fact, in the problematic case, the resolution is an extra search/replace to undo unnecessary ones
|
||||
// if (is_string($from)) {
|
||||
// $data = str_replace($from, $to, $data);
|
||||
// } else {
|
||||
// # Array. We only want a maximum of one replacement to take place. This is only an issue in non-default setups, but in those situations, carrying out all the search/replaces can be wrong. This is also why the most specific URL should be done first.
|
||||
// foreach ($from as $i => $f) {
|
||||
// $ndata = str_replace($f, $to[$i], $data);
|
||||
// if ($ndata != $data) {
|
||||
// $data = $ndata;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
if ($serialised)
|
||||
return serialize($data);
|
||||
|
||||
} catch (Exception $error) {
|
||||
if (3 > $error_count) {
|
||||
$log_message = 'PHP Fatal Exception error ('.get_class($error).') has occurred during recursive_unserialize_replace. Error Message: '.$error->getMessage().' (Code: '.$error->getCode().', line '.$error->getLine().' in '.$error->getFile().')';
|
||||
$updraftplus->log($log_message, 'warning-restore');
|
||||
$error_count++;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will get the last database error and log it
|
||||
*
|
||||
* @param string $sql_line - the sql line that caused the error
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function print_error($sql_line) {
|
||||
global $wpdb, $updraftplus;
|
||||
if ($this->use_wpdb) {
|
||||
$last_error = $wpdb->last_error;
|
||||
} else {
|
||||
// phpcs:ignore PHPCompatibility.Extensions.RemovedExtensions.mysql_DeprecatedRemoved -- Ignore removed extension compatibility.
|
||||
$last_error = ($this->use_mysqli) ? mysqli_error($this->mysql_dbh) : mysql_error($this->mysql_dbh);
|
||||
}
|
||||
$updraftplus->log(__('Error:', 'updraftplus')." ".$last_error." - ".__('the database query being run was:', 'updraftplus').' '.$sql_line, 'warning-restore');
|
||||
return $last_error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
<?php
|
||||
/**
|
||||
* Semaphore Lock Management
|
||||
* Adapted from WP Social under the GPL - thanks to Alex King (https://github.com/crowdfavorite/wp-social)
|
||||
*/
|
||||
class UpdraftPlus_Semaphore {
|
||||
|
||||
/**
|
||||
* Initializes the semaphore object.
|
||||
*
|
||||
* @static
|
||||
* @return UpdraftPlus_Semaphore
|
||||
*/
|
||||
public static function factory() {
|
||||
return new self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock Broke
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $lock_broke = false;
|
||||
|
||||
public $lock_name = 'lock';
|
||||
|
||||
/**
|
||||
* Attempts to start the lock. If the rename works, the lock is started.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function lock() {
|
||||
global $wpdb, $updraftplus;
|
||||
|
||||
// Attempt to set the lock
|
||||
$affected = $wpdb->query("
|
||||
UPDATE $wpdb->options
|
||||
SET option_name = 'updraftplus_locked_".$this->lock_name."'
|
||||
WHERE option_name = 'updraftplus_unlocked_".$this->lock_name."'
|
||||
");
|
||||
|
||||
if ('0' == $affected && !$this->stuck_check()) {
|
||||
$updraftplus->log('Semaphore lock ('.$this->lock_name.', '.$wpdb->options.') failed (line '.__LINE__.')');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check to see if all processes are complete
|
||||
$affected = $wpdb->query("
|
||||
UPDATE $wpdb->options
|
||||
SET option_value = CAST(option_value AS UNSIGNED) + 1
|
||||
WHERE option_name = 'updraftplus_semaphore_".$this->lock_name."'
|
||||
AND option_value = '0'
|
||||
");
|
||||
if ('1' != $affected) {
|
||||
if (!$this->stuck_check()) {
|
||||
$updraftplus->log('Semaphore lock ('.$this->lock_name.', '.$wpdb->options.') failed (line '.__LINE__.')');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reset the semaphore to 1
|
||||
$wpdb->query("
|
||||
UPDATE $wpdb->options
|
||||
SET option_value = '1'
|
||||
WHERE option_name = 'updraftplus_semaphore_".$this->lock_name."'
|
||||
");
|
||||
|
||||
$updraftplus->log('Semaphore ('.$this->lock_name.', '.$wpdb->options.') reset to 1');
|
||||
}
|
||||
|
||||
// Set the lock time
|
||||
$wpdb->query($wpdb->prepare("
|
||||
UPDATE $wpdb->options
|
||||
SET option_value = %s
|
||||
WHERE option_name = 'updraftplus_last_lock_time_".$this->lock_name."'
|
||||
", current_time('mysql', 1)));
|
||||
$updraftplus->log('Set semaphore last lock ('.$this->lock_name.') time to '.current_time('mysql', 1));
|
||||
|
||||
$updraftplus->log('Semaphore lock ('.$this->lock_name.') complete');
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function ensure_semaphore_exists($semaphore) {
|
||||
// Make sure the options for semaphores exist
|
||||
global $wpdb, $updraftplus;
|
||||
$results = $wpdb->get_results("
|
||||
SELECT option_id
|
||||
FROM $wpdb->options
|
||||
WHERE option_name IN ('updraftplus_locked_$semaphore', 'updraftplus_unlocked_$semaphore', 'updraftplus_last_lock_time_$semaphore', 'updraftplus_semaphore_$semaphore')
|
||||
");
|
||||
|
||||
if (!is_array($results) || count($results) < 3) {
|
||||
|
||||
if (is_array($results) && count($results) > 0) {
|
||||
$updraftplus->log("Semaphore ($semaphore, ".$wpdb->options.") in an impossible/broken state - fixing (".count($results).")");
|
||||
} else {
|
||||
$updraftplus->log("Semaphore ($semaphore, ".$wpdb->options.") being initialised");
|
||||
}
|
||||
|
||||
$wpdb->query("
|
||||
DELETE FROM $wpdb->options
|
||||
WHERE option_name IN ('updraftplus_locked_$semaphore', 'updraftplus_unlocked_$semaphore', 'updraftplus_last_lock_time_$semaphore', 'updraftplus_semaphore_$semaphore')
|
||||
");
|
||||
|
||||
$wpdb->query($wpdb->prepare("
|
||||
INSERT INTO $wpdb->options (option_name, option_value, autoload)
|
||||
VALUES
|
||||
('updraftplus_unlocked_$semaphore', '1', 'no'),
|
||||
('updraftplus_last_lock_time_$semaphore', '%s', 'no'),
|
||||
('updraftplus_semaphore_$semaphore', '0', 'no')
|
||||
", current_time('mysql', 1)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the semaphore.
|
||||
*
|
||||
* @param array $filters
|
||||
* @return Updraft_Semaphore
|
||||
*/
|
||||
public function increment(array $filters = array()) {
|
||||
global $wpdb, $updraftplus;
|
||||
|
||||
if (count($filters)) {
|
||||
// Loop through all of the filters and increment the semaphore
|
||||
foreach ($filters as $priority) {
|
||||
for ($i = 0, $j = count($priority); $i < $j; ++$i) {
|
||||
$this->increment();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$wpdb->query("
|
||||
UPDATE $wpdb->options
|
||||
SET option_value = CAST(option_value AS UNSIGNED) + 1
|
||||
WHERE option_name = 'updraftplus_semaphore_".$this->lock_name."'
|
||||
");
|
||||
$updraftplus->log('Incremented the semaphore ('.$this->lock_name.') by 1');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrements the semaphore.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function decrement() {
|
||||
global $wpdb, $updraftplus;
|
||||
|
||||
$wpdb->query("
|
||||
UPDATE $wpdb->options
|
||||
SET option_value = CAST(option_value AS UNSIGNED) - 1
|
||||
WHERE option_name = 'updraftplus_semaphore_".$this->lock_name."'
|
||||
AND CAST(option_value AS UNSIGNED) > 0
|
||||
");
|
||||
$updraftplus->log('Decremented the semaphore ('.$this->lock_name.') by 1');
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlocks the process.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function unlock() {
|
||||
global $wpdb, $updraftplus;
|
||||
|
||||
// Decrement for the master process.
|
||||
$this->decrement();
|
||||
|
||||
$result = $wpdb->query("
|
||||
UPDATE $wpdb->options
|
||||
SET option_name = 'updraftplus_unlocked_".$this->lock_name."'
|
||||
WHERE option_name = 'updraftplus_locked_".$this->lock_name."'
|
||||
");
|
||||
|
||||
if ('1' == $result) {
|
||||
$updraftplus->log('Semaphore ('.$this->lock_name.') unlocked');
|
||||
return true;
|
||||
}
|
||||
|
||||
$updraftplus->log('Semaphore ('.$this->lock_name.', '.$wpdb->options.') still locked ('.$result.')');
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to jiggle the stuck lock loose.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function stuck_check() {
|
||||
global $wpdb, $updraftplus;
|
||||
|
||||
// Check to see if we already broke the lock.
|
||||
if ($this->lock_broke) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$current_time = current_time('mysql', 1);
|
||||
$three_minutes_before = gmdate('Y-m-d H:i:s', time()-(defined('UPDRAFTPLUS_SEMAPHORE_LOCK_WAIT') ? UPDRAFTPLUS_SEMAPHORE_LOCK_WAIT : 180));
|
||||
|
||||
$affected = $wpdb->query($wpdb->prepare("
|
||||
UPDATE $wpdb->options
|
||||
SET option_value = %s
|
||||
WHERE option_name = 'updraftplus_last_lock_time_".$this->lock_name."'
|
||||
AND option_value <= %s
|
||||
", $current_time, $three_minutes_before));
|
||||
|
||||
if ('1' == $affected) {
|
||||
$updraftplus->log('Semaphore ('.$this->lock_name.', '.$wpdb->options.') was stuck, set lock time to '.$current_time);
|
||||
$this->lock_broke = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if lock is greater that 24 hours
|
||||
$last_lock_time = strtotime(UpdraftPlus_Options::get_updraft_option('updraftplus_last_lock_time_'.$this->lock_name, $current_time));
|
||||
$next_day = strtotime($current_time.' +1 day');
|
||||
if ($last_lock_time > $next_day) {
|
||||
$this->lock_broke = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
} // End UpdraftPlus_Semaphore
|
||||
@@ -0,0 +1,431 @@
|
||||
<?php
|
||||
|
||||
if (!defined('UPDRAFTPLUS_DIR')) die('No access.');
|
||||
|
||||
/**
|
||||
* A class for interfacing with storage methods.
|
||||
* N.B. This class began life Sep 2018; it is not guaranteed that there are not many places that bypass it that could be ported over to use it.
|
||||
*/
|
||||
class UpdraftPlus_Storage_Methods_Interface {
|
||||
|
||||
/**
|
||||
* Instantiate a remote storage object. If one of the same type has previously been fetched, then it will be returned.
|
||||
*
|
||||
* @param String $method - the storage method (e.g. 'dropbox', 's3', etc.)
|
||||
*
|
||||
* @return Object|WP_Error - an instance of UpdraftPlus_BackupModule, or an error
|
||||
*/
|
||||
public static function get_storage_object($method) {
|
||||
|
||||
if (!preg_match('/^[\-a-z0-9]+$/i', $method)) return new WP_Error('no_such_storage_class', "The specified storage method ($method) was not found");
|
||||
|
||||
static $objects = array();
|
||||
|
||||
if (!empty($objects[$method])) return $objects[$method];
|
||||
|
||||
$method_class = 'UpdraftPlus_BackupModule_'.$method;
|
||||
|
||||
if (!class_exists($method_class)) updraft_try_include_file('methods/'.$method.'.php', 'include_once');
|
||||
|
||||
if (!class_exists($method_class)) return new WP_Error('no_such_storage_class', "The specified storage method ($method) was not found");
|
||||
|
||||
$objects[$method] = new $method_class;
|
||||
|
||||
return $objects[$method];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will return an array of remote storage options and storage_templates.
|
||||
*
|
||||
* @return Array - returns an array which consists of storage options and storage_templates multidimensional array
|
||||
*/
|
||||
public static function get_remote_storage_options_and_templates() {
|
||||
|
||||
global $updraftplus;
|
||||
|
||||
$storage_objects_and_ids = self::get_storage_objects_and_ids(array_keys($updraftplus->backup_methods));
|
||||
$options = array();
|
||||
$templates = $partial_templates = array();
|
||||
|
||||
foreach ($storage_objects_and_ids as $method => $method_info) {
|
||||
|
||||
$object = $method_info['object'];
|
||||
|
||||
if (!$object->supports_feature('multi_options')) {
|
||||
ob_start();
|
||||
do_action('updraftplus_config_print_before_storage', $method, null);
|
||||
do_action('updraftplus_config_print_add_conditional_logic', $method, $object);
|
||||
$object->config_print();
|
||||
$templates[$method] = ob_get_clean();
|
||||
} else {
|
||||
$templates[$method] = $object->get_template();
|
||||
}
|
||||
|
||||
if (is_callable(array($object, 'get_partial_templates'))) $partial_templates[$method] = $object->get_partial_templates();
|
||||
|
||||
if (isset($method_info['instance_settings'])) {
|
||||
// Add the methods default settings so that we can add new instances
|
||||
$method_info['instance_settings']['default'] = $object->get_default_options();
|
||||
|
||||
foreach ($method_info['instance_settings'] as $instance_id => $instance_options) {
|
||||
|
||||
$opts_without_transform = $instance_options;
|
||||
|
||||
if ($object->supports_feature('multi_options')) {
|
||||
$opts_without_transform['instance_id'] = $instance_id;
|
||||
}
|
||||
|
||||
$opts = $object->transform_options_for_template($opts_without_transform);
|
||||
|
||||
foreach ($object->filter_frontend_settings_keys() as $filter_frontend_settings_key) {
|
||||
unset($opts[$filter_frontend_settings_key]);
|
||||
}
|
||||
|
||||
$options[$method][$instance_id] = $opts;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the list of template properties from the predefined storage method
|
||||
$options[$method]['template_properties'] = $object->get_template_properties();
|
||||
}
|
||||
|
||||
return array(
|
||||
'options' => $options,
|
||||
'templates' => $templates,
|
||||
'partial_templates' => $partial_templates,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will return an array of remote storage objects and instance settings of the currently connected remote storage services.
|
||||
*
|
||||
* @param Array $services - an list of service identifiers (e.g. ['dropbox', 's3'])
|
||||
*
|
||||
* @uses self::get_storage_object()
|
||||
*
|
||||
* @return Array - returns an array, with a key equal to each member of the $services list passed in. The corresponding value is then an array with keys 'object', 'instance_settings'. The value for 'object' is an UpdraftPlus_BackupModule instance. The value for 'instance_settings' is an array keyed by associated instance IDs, with the values being the associated settings for the instance ID.
|
||||
*/
|
||||
public static function get_storage_objects_and_ids($services) {
|
||||
|
||||
$storage_objects_and_ids = array();
|
||||
|
||||
// N.B. The $services can return any type of values (null, false, etc.) as mentioned from one of the comment found
|
||||
// in the "save_backup_to_history" function above especially if upgrading from (very) old versions. Thus,
|
||||
// here we're adding some check to make sure that we're receiving a non-empty array before iterating through
|
||||
// all the backup services that the user has in store.
|
||||
if (empty($services) || !is_array($services)) return $storage_objects_and_ids;
|
||||
|
||||
foreach ($services as $method) {
|
||||
|
||||
if ('none' === $method || '' == $method) continue;
|
||||
|
||||
$remote_storage = self::get_storage_object($method);
|
||||
|
||||
if (is_a($remote_storage, 'UpdraftPlus_BackupModule')) {
|
||||
|
||||
if (empty($storage_objects_and_ids[$method])) $storage_objects_and_ids[$method] = array();
|
||||
|
||||
$storage_objects_and_ids[$method]['object'] = $remote_storage;
|
||||
|
||||
if ($remote_storage->supports_feature('multi_options')) {
|
||||
|
||||
$settings_from_db = UpdraftPlus_Options::get_updraft_option('updraft_'.$method);
|
||||
|
||||
$settings = is_array($settings_from_db) ? $settings_from_db : array();
|
||||
|
||||
if (!isset($settings['version'])) $settings = self::update_remote_storage_options_format($method);
|
||||
|
||||
if (is_wp_error($settings)) {
|
||||
if (!empty($settings_from_db)) error_log("UpdraftPlus: failed to convert storage options format: $method");
|
||||
$settings = array('settings' => array());
|
||||
}
|
||||
|
||||
if (empty($settings['settings'])) {
|
||||
|
||||
// Try to recover by getting a default set of options for display
|
||||
if (is_callable(array($remote_storage, 'get_default_options'))) {
|
||||
$uuid = 's-'.md5(rand().uniqid().microtime(true));
|
||||
$settings['settings'] = array($uuid => $remote_storage->get_default_options());
|
||||
}
|
||||
|
||||
// See: https://wordpress.org/support/topic/cannot-setup-connectionauthenticate-with-dropbox/
|
||||
if (empty($settings['settings'])) {
|
||||
// This can get sent to the browser, and break the page, if the user has configured that. However, it should now (1.13.6+) be impossible for this condition to occur, now that we only log it after getting some default options.
|
||||
error_log("UpdraftPlus: Warning: settings for $method are empty. A dummy field is usually needed so that something is saved.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!empty($settings['settings'])) {
|
||||
|
||||
if (!isset($storage_objects_and_ids[$method]['instance_settings'])) $storage_objects_and_ids[$method]['instance_settings'] = array();
|
||||
|
||||
foreach ($settings['settings'] as $instance_id => $storage_options) {
|
||||
$storage_objects_and_ids[$method]['instance_settings'][$instance_id] = $storage_options;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!isset($storage_objects_and_ids[$method]['instance_settings'])) $storage_objects_and_ids[$method]['instance_settings'] = $remote_storage->get_default_options();
|
||||
}
|
||||
|
||||
} else {
|
||||
error_log("UpdraftPlus: storage method not found: $method");
|
||||
}
|
||||
}
|
||||
|
||||
return $storage_objects_and_ids;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This converts array-style options (i.e. late 2013-onwards) to
|
||||
* 2017-style multi-array-style options.
|
||||
*
|
||||
* N.B. Don't actually call this on any particular method's options
|
||||
* until the functions which read the options can cope!
|
||||
*
|
||||
* Don't call for settings that aren't array-style. You may lose
|
||||
* the settings if you do.
|
||||
*
|
||||
* It is safe to call this if you are not sure if the options are
|
||||
* already updated.
|
||||
*
|
||||
* @param String $method - the method identifier
|
||||
*
|
||||
* @returns Array|WP_Error - returns the new options, or a WP_Error if it failed
|
||||
*/
|
||||
public static function update_remote_storage_options_format($method) {
|
||||
|
||||
global $updraftplus;
|
||||
|
||||
// Prevent recursion
|
||||
static $already_active = false;
|
||||
|
||||
if ($already_active) return new WP_Error('recursion', 'self::update_remote_storage_options_format() was called in a loop. This is usually caused by an options filter failing to correctly process a "recursion" error code');
|
||||
|
||||
if (!file_exists(UPDRAFTPLUS_DIR.'/methods/'.$method.'.php')) return new WP_Error('no_such_method', 'Remote storage method not found', $method);
|
||||
|
||||
// Sanity/inconsistency check
|
||||
$settings_keys = $updraftplus->get_settings_keys();
|
||||
|
||||
$method_key = 'updraft_'.$method;
|
||||
|
||||
if (!in_array($method_key, $settings_keys)) return new WP_Error('no_such_setting', 'Setting not found for this method', $method);
|
||||
|
||||
$current_setting = UpdraftPlus_Options::get_updraft_option($method_key, array());
|
||||
if ('' == $current_setting) $current_setting = array();
|
||||
|
||||
if (!is_array($current_setting) && false !== $current_setting) return new WP_Error('format_unrecognised', 'Settings format not recognised', array('method' => $method, 'current_setting' => $current_setting));
|
||||
|
||||
// Already converted?
|
||||
if (isset($current_setting['version'])) return $current_setting;
|
||||
if (empty($current_setting)) {
|
||||
$remote_storage = self::get_storage_object($method);
|
||||
$current_setting = $remote_storage->get_default_options();
|
||||
}
|
||||
$new_setting = self::wrap_remote_storage_options($current_setting);
|
||||
|
||||
$already_active = true;
|
||||
$updated = UpdraftPlus_Options::update_updraft_option($method_key, $new_setting);
|
||||
$already_active = false;
|
||||
|
||||
if ($updated) {
|
||||
return $new_setting;
|
||||
} else {
|
||||
return new WP_Error('save_failed', 'Saving the options in the new format failed', array('method' => $method, 'current_setting' => $new_setting));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will return an array of enabled remote storage objects and instance settings of the currently connected remote storage services.
|
||||
*
|
||||
* @param Array $services - an list of service identifiers (e.g. ['dropbox', 's3'])
|
||||
* @param Array $remote_storage_instances - a list of remote storage instances the user wants to backup to, if empty we use the saved options
|
||||
*
|
||||
* @uses self::get_storage_objects_and_ids()
|
||||
*
|
||||
* @return Array - returns an array, with a key equal to only enabled service member of the $services list passed in. The corresponding value is then an array with keys 'object', 'instance_settings'. The value for 'object' is an UpdraftPlus_BackupModule instance. The value for 'instance_settings' is an array keyed by associated enabled instance IDs, with the values being the associated settings for the enabled instance ID.
|
||||
*/
|
||||
public static function get_enabled_storage_objects_and_ids($services, $remote_storage_instances = array()) {
|
||||
|
||||
$storage_objects_and_ids = self::get_storage_objects_and_ids($services);
|
||||
|
||||
foreach ($storage_objects_and_ids as $method => $method_information) {
|
||||
|
||||
if (!$method_information['object']->supports_feature('multi_options')) continue;
|
||||
|
||||
foreach ($method_information['instance_settings'] as $instance_id => $instance_information) {
|
||||
if (!isset($instance_information['instance_enabled'])) $instance_information['instance_enabled'] = 1;
|
||||
if (!empty($remote_storage_instances) && isset($remote_storage_instances[$method]) && !in_array($instance_id, $remote_storage_instances[$method])) {
|
||||
unset($storage_objects_and_ids[$method]['instance_settings'][$instance_id]);
|
||||
} elseif (empty($remote_storage_instances) && empty($instance_information['instance_enabled'])) {
|
||||
unset($storage_objects_and_ids[$method]['instance_settings'][$instance_id]);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($storage_objects_and_ids[$method]['instance_settings'])) unset($storage_objects_and_ids[$method]);
|
||||
}
|
||||
|
||||
return $storage_objects_and_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method gets the remote storage information and objects and loops over each of them until we get a successful download of the passed in file.
|
||||
*
|
||||
* @param Array $services - a list of connected service identifiers (e.g. 'dropbox', 's3', etc.)
|
||||
* @param String $file - the name of the file
|
||||
* @param Integer $timestamp - the backup timestamp
|
||||
* @param Boolean $restore - a boolean to indicate if the caller of this method is a restore or not; if so, different messages are logged
|
||||
*/
|
||||
public static function get_remote_file($services, $file, $timestamp, $restore = false) {
|
||||
|
||||
global $updraftplus;
|
||||
|
||||
$backup_history = UpdraftPlus_Backup_History::get_history();
|
||||
|
||||
$fullpath = $updraftplus->backups_dir_location().'/'.$file;
|
||||
|
||||
$storage_objects_and_ids = self::get_storage_objects_and_ids($services);
|
||||
|
||||
$is_downloaded = false;
|
||||
|
||||
$updraftplus->register_wp_http_option_hooks();
|
||||
|
||||
foreach ($services as $service) {
|
||||
|
||||
if (empty($service) || 'none' == $service || $is_downloaded) continue;
|
||||
|
||||
if ($restore) {
|
||||
$service_description = empty($updraftplus->backup_methods[$service]) ? $service : $updraftplus->backup_methods[$service];
|
||||
$updraftplus->log(__("File is not locally present - needs retrieving from remote storage", 'updraftplus')." ($service_description)", 'notice-restore');
|
||||
}
|
||||
|
||||
$object = $storage_objects_and_ids[$service]['object'];
|
||||
|
||||
if (!$object->supports_feature('multi_options')) {
|
||||
error_log("UpdraftPlus_Storage_Methods_Interface::get_remote_file(): Multi-options not supported by: ".$service);
|
||||
continue;
|
||||
}
|
||||
|
||||
$instance_ids = $storage_objects_and_ids[$service]['instance_settings'];
|
||||
$backups_instance_ids = isset($backup_history[$timestamp]['service_instance_ids'][$service]) ? $backup_history[$timestamp]['service_instance_ids'][$service] : array(false);
|
||||
|
||||
foreach ($backups_instance_ids as $instance_id) {
|
||||
|
||||
if (isset($instance_ids[$instance_id])) {
|
||||
$options = $instance_ids[$instance_id];
|
||||
} else {
|
||||
// If we didn't find a instance id match, it could be a new UpdraftPlus upgrade or a wipe settings with the same details entered so try the default options saved.
|
||||
$options = $object->get_options();
|
||||
}
|
||||
|
||||
$object->set_options($options, false, $instance_id);
|
||||
|
||||
$download = self::download_file($file, $object);
|
||||
|
||||
if (is_readable($fullpath) && false !== $download) {
|
||||
if ($restore) {
|
||||
$updraftplus->log(__('OK', 'updraftplus'), 'notice-restore');
|
||||
} else {
|
||||
clearstatcache();
|
||||
$updraftplus->log('Remote fetch was successful (file size: '.round(filesize($fullpath)/1024, 1).' KB)');
|
||||
$is_downloaded = true;
|
||||
}
|
||||
break 2;
|
||||
} else {
|
||||
if ($restore) {
|
||||
$updraftplus->log(__('Error', 'updraftplus'), 'notice-restore');
|
||||
} else {
|
||||
clearstatcache();
|
||||
if (0 === @filesize($fullpath)) @unlink($fullpath);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise if the file doesn't exist.
|
||||
$updraftplus->log('Remote fetch failed');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$updraftplus->register_wp_http_option_hooks(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a specified file into UD's directory
|
||||
*
|
||||
* @param String $file The basename of the file
|
||||
* @param UpdraftPlus_BackupModule $service_object The object of the service to use to download with.
|
||||
*
|
||||
* @return Boolean - Whether the operation succeeded. Inherited from the storage module's download() method. N.B. At the time of writing it looks like not all modules necessarily return true upon success; but false can be relied upon for detecting failure.
|
||||
*/
|
||||
private static function download_file($file, $service_object) {
|
||||
|
||||
global $updraftplus;
|
||||
|
||||
if (function_exists('set_time_limit')) @set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
|
||||
|
||||
$service = $service_object->get_id();
|
||||
|
||||
$updraftplus->log("Requested file from remote service: $service: $file");
|
||||
|
||||
if (method_exists($service_object, 'download')) {
|
||||
|
||||
try {
|
||||
return $service_object->download($file);
|
||||
} catch (Exception $e) {
|
||||
$log_message = 'Exception ('.get_class($e).') occurred during download: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
|
||||
error_log($log_message);
|
||||
// @codingStandardsIgnoreLine
|
||||
$log_message .= ' Backtrace: '.str_replace(array(ABSPATH, "\n"), array('', ', '), $e->getTraceAsString());
|
||||
$updraftplus->log($log_message);
|
||||
$updraftplus->log(sprintf(__('A PHP exception (%s) has occurred: %s', 'updraftplus'), get_class($e), $e->getMessage()), 'error');
|
||||
return false;
|
||||
// @codingStandardsIgnoreLine
|
||||
} catch (Error $e) {
|
||||
$log_message = 'PHP Fatal error ('.get_class($e).') has occurred during download. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
|
||||
error_log($log_message);
|
||||
// @codingStandardsIgnoreLine
|
||||
$log_message .= ' Backtrace: '.str_replace(array(ABSPATH, "\n"), array('', ', '), $e->getTraceAsString());
|
||||
$updraftplus->log($log_message);
|
||||
$updraftplus->log(sprintf(__('A PHP fatal error (%s) has occurred: %s', 'updraftplus'), get_class($e), $e->getMessage()), 'error');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$updraftplus->log("Automatic backup restoration is not available with the method: $service.");
|
||||
$updraftplus->log("$file: ".__('The backup archive for this file could not be found.', 'updraftplus').' '.sprintf(__('The remote storage method in use (%s) does not allow us to retrieve files.', 'updraftplus'), $service).' '.__("To perform any restoration using UpdraftPlus, you will need to obtain a copy of this file and place it inside UpdraftPlus's working folder", 'updraftplus')." (".UpdraftPlus_Manipulation_Functions::prune_updraft_dir_prefix($updraftplus->backups_dir_location()).")", 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will update the old style remote storage options to the new style (Apr 2017) if the user has imported a old style version of settings
|
||||
*
|
||||
* @param Array $options - The remote storage options settings array
|
||||
* @return Array - The updated remote storage options settings array
|
||||
*/
|
||||
public static function wrap_remote_storage_options($options) {
|
||||
// Already converted?
|
||||
if (isset($options['version'])) return $options;
|
||||
|
||||
// Generate an instance id
|
||||
$uuid = self::generate_instance_id();
|
||||
|
||||
$new_setting = array(
|
||||
'version' => 1,
|
||||
);
|
||||
|
||||
if (!is_array($options)) $options = array();
|
||||
|
||||
$new_setting['settings'] = array($uuid => $options);
|
||||
|
||||
return $new_setting;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will return a random instance id string
|
||||
*
|
||||
* @return String - a random instance id
|
||||
*/
|
||||
private static function generate_instance_id() {
|
||||
// Cryptographic randomness not required. The prefix helps avoid potential for type-juggling issues.
|
||||
return 's-'.md5(rand().uniqid().microtime(true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) die('No direct access allowed');
|
||||
|
||||
if (!class_exists('Updraft_Dashboard_News')) :
|
||||
/**
|
||||
* Handles all stuffs related to Dashboard News
|
||||
*/
|
||||
class Updraft_Dashboard_News {
|
||||
|
||||
/**
|
||||
* dashboard news feed URL
|
||||
*
|
||||
* @var String
|
||||
*/
|
||||
protected $feed_url;
|
||||
|
||||
/**
|
||||
* news page URL
|
||||
*
|
||||
* @var String
|
||||
*/
|
||||
protected $link;
|
||||
|
||||
/**
|
||||
* various translations to use in the UI
|
||||
*
|
||||
* @var Array
|
||||
*/
|
||||
protected $translations;
|
||||
|
||||
/**
|
||||
* slug to use, where needed
|
||||
*
|
||||
* @var String
|
||||
*/
|
||||
protected $slug;
|
||||
|
||||
/**
|
||||
* Valid ajax callback pages
|
||||
*
|
||||
* @var Array
|
||||
*/
|
||||
protected $valid_callback_pages;
|
||||
|
||||
/**
|
||||
* constructor of class Updraft_Dashboard_News
|
||||
*
|
||||
* @param String $feed_url - dashboard news feed URL
|
||||
* @param String $link - web page URL
|
||||
* @param Array $translations - an array of translations, with keys: product_title, item_prefix, item_description, dismiss_confirm
|
||||
*/
|
||||
public function __construct($feed_url, $link, $translations) {
|
||||
|
||||
$this->feed_url = $feed_url;
|
||||
$this->link = $link;
|
||||
$this->translations = $translations;
|
||||
$this->slug = sanitize_title($translations['product_title']);
|
||||
|
||||
$dashboard_news_transient_name = $this->get_transient_name();
|
||||
add_filter('pre_set_transient_'.$dashboard_news_transient_name, array($this, 'pre_set_transient_for_dashboard_news'), 10);
|
||||
add_filter('transient_'.$dashboard_news_transient_name, array($this, 'transient_for_dashboard_news'), 10);
|
||||
|
||||
add_action('wp_ajax_'.$this->slug.'_ajax_dismiss_dashboard_news', array($this, 'dismiss_dashboard_news'));
|
||||
|
||||
if ('index.php' == $GLOBALS['pagenow'] && !get_user_meta(get_current_user_id(), $this->slug.'_dismiss_dashboard_news', true)) {
|
||||
add_action('admin_print_footer_scripts', array($this, 'admin_print_footer_scripts'));
|
||||
}
|
||||
add_action('wp_ajax_dashboard-widgets', array($this, 'wp_ajax_dashboard_widgets_low_priority'), 1);
|
||||
add_action('wp_ajax_dashboard-widgets', array($this, 'wp_ajax_dashboard_widgets_high_priority'), 20);
|
||||
|
||||
$this->valid_callback_pages = array(
|
||||
'dashboard-user',
|
||||
'dashboard-network',
|
||||
'dashboard',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the transient name
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
private function get_transient_name() {
|
||||
$locale = function_exists('get_user_locale') ? get_user_locale() : get_locale();
|
||||
global $wp_version;
|
||||
@include(ABSPATH.WPINC.'/version.php');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
|
||||
$dash_prefix = version_compare($wp_version, '4.8', '>=') ? 'dash_v2_' : 'dash_';
|
||||
return version_compare($wp_version, '4.3', '>=') ? $dash_prefix.md5('dashboard_primary_'.$locale) : 'dash_'.md5('dashboard_primary');
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters a transient for dashboard news before its value is set
|
||||
*
|
||||
* @param String $value - New value of transient
|
||||
* @return String HTML of Wordpress News & Events same as $transient param
|
||||
*/
|
||||
public function pre_set_transient_for_dashboard_news($value) {
|
||||
if (!function_exists('wp_dashboard_primary_output')) return $value;
|
||||
// Not needed first if condition, because filter hook name have already transient name. It is for better checking
|
||||
if (!get_user_meta(get_current_user_id(), $this->slug.'_dismiss_dashboard_news', true)) {
|
||||
// Gets the news, when fetching WP news first time (transient cache does not exist)
|
||||
$this->get_dashboard_news_html();
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* wp_ajax_dashboard-widgets ajax action handler with low priority
|
||||
*/
|
||||
public function wp_ajax_dashboard_widgets_low_priority() {
|
||||
|
||||
if (!$this->do_ajax_dashboard_news()) return;
|
||||
|
||||
add_filter('wp_die_ajax_handler', array($this, 'wp_die_ajax_handler'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Dummy wp die handler
|
||||
*
|
||||
* @param String $callback_function Callable $function Callback function name
|
||||
* @return String callable $function Callback function name
|
||||
*/
|
||||
public function wp_die_ajax_handler($callback_function) {
|
||||
// this condition is not required, but always better to double confirm
|
||||
if (!$this->do_ajax_dashboard_news()) return $callback_function;
|
||||
// Here, We can use __return_empty_string function name, but __return_empty_string is available since WP 3.7. Whereas __return_true function name available since WP 3.0
|
||||
return '__return_true';
|
||||
}
|
||||
|
||||
/**
|
||||
* wp_ajax_dashboard-widgets ajax action handler with high priority
|
||||
*/
|
||||
public function wp_ajax_dashboard_widgets_high_priority() {
|
||||
|
||||
if (!$this->do_ajax_dashboard_news()) return;
|
||||
|
||||
remove_filter('wp_die_ajax_handler', array($this, 'wp_die_ajax_handler'));
|
||||
echo wp_kses_post($this->get_dashboard_news_html());
|
||||
wp_die();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether valid ajax for dashboard news or not
|
||||
*
|
||||
* @return Boolean True if an ajax for the WP dashboard news
|
||||
*/
|
||||
protected function do_ajax_dashboard_news() {
|
||||
$ajax_callback_page = !empty($_GET['pagenow']) ? $_GET['pagenow'] : '';
|
||||
return (in_array($ajax_callback_page, $this->valid_callback_pages) && !empty($_GET['widget']) && 'dashboard_primary' == $_GET['widget']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters a transient for dashboard news when getting transient value
|
||||
*
|
||||
* @param String $value - New value of transient
|
||||
* @return String - HTML of Wordpress News & Events
|
||||
*/
|
||||
public function transient_for_dashboard_news($value) {
|
||||
if (!function_exists('wp_dashboard_primary_output')) return $value;
|
||||
// Not needed first if condition, because filter hook name have already transient name. It is for better checking
|
||||
if (!get_user_meta(get_current_user_id(), $this->slug.'_dismiss_dashboard_news', true) && !empty($value)) {
|
||||
return $value.$this->get_dashboard_news_html();
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* get dashboard news html
|
||||
*
|
||||
* @return String - the resulting message
|
||||
*/
|
||||
protected function get_dashboard_news_html() {
|
||||
|
||||
$cache_key = $this->slug.'_dashboard_news';
|
||||
if (false !== ($output = get_transient($cache_key))) return $output;
|
||||
|
||||
$feeds = array(
|
||||
$this->slug => array(
|
||||
'link' => $this->link,
|
||||
'url' => $this->feed_url,
|
||||
'title' => $this->translations['product_title'],
|
||||
'items' => apply_filters($this->slug.'_dashboard_news_items_count', 2),
|
||||
'show_summary' => 0,
|
||||
'show_author' => 0,
|
||||
'show_date' => 0,
|
||||
)
|
||||
);
|
||||
ob_start();
|
||||
wp_dashboard_primary_output('dashboard_primary', $feeds);
|
||||
$original_formatted_news = ob_get_clean();
|
||||
$formatted_news = preg_replace('/<a(.+?)>(.+?)<\/a>/i', "<a$1>".esc_html($this->translations['item_prefix']).": $2</a>", $original_formatted_news);
|
||||
$formatted_news = str_replace('<li>', '<li class="'.esc_attr($this->slug).'_dashboard_news_item">'.'<a href="'.esc_url(UpdraftPlus::get_current_clean_url()).'" class="dashicons dashicons-no-alt" title="'.esc_attr($this->translations['dismiss_tooltip']).'" onClick="'.esc_js($this->slug).'_dismiss_dashboard_news(); return false;" style="float: right; box-shadow: none; margin-left: 5px;"></a>', $formatted_news);
|
||||
set_transient($this->slug.'_dashboard_news', $formatted_news, 43200); // 12 hours
|
||||
|
||||
return $formatted_news;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints javascripts in admin footer
|
||||
*/
|
||||
public function admin_print_footer_scripts() {
|
||||
?>
|
||||
<script>
|
||||
function <?php echo esc_js($this->slug); ?>_dismiss_dashboard_news() {
|
||||
if (confirm("<?php echo esc_js($this->translations['dismiss_confirm']); ?>")) {
|
||||
jQuery.ajax({
|
||||
url: '<?php echo esc_url(admin_url('admin-ajax.php'));?>',
|
||||
data : {
|
||||
action: '<?php echo esc_js($this->slug); ?>_ajax_dismiss_dashboard_news',
|
||||
nonce : '<?php echo esc_js(wp_create_nonce($this->slug.'-dismiss-news-nonce'));?>'
|
||||
},
|
||||
success: function(response) {
|
||||
jQuery('.<?php echo esc_js($this->slug); ?>_dashboard_news_item').slideUp('slow');
|
||||
},
|
||||
error: function(response, status, error_code) {
|
||||
console.log("<?php echo esc_js($this->slug); ?>_dismiss_dashboard_news: error: "+status+" ("+error_code+")");
|
||||
console.log(response);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss dashboard news
|
||||
*/
|
||||
public function dismiss_dashboard_news() {
|
||||
$nonce = empty($_REQUEST['nonce']) ? '' : $_REQUEST['nonce'];
|
||||
if (!wp_verify_nonce($nonce, $this->slug.'-dismiss-news-nonce')) die('Security check.');
|
||||
|
||||
update_user_meta(get_current_user_id(), $this->slug.'_dismiss_dashboard_news', true);
|
||||
die();
|
||||
}
|
||||
}
|
||||
endif;
|
||||
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
|
||||
if (!defined('ABSPATH')) die('No direct access.');
|
||||
|
||||
/**
|
||||
* Class Updraft_Semaphore_3_0
|
||||
*
|
||||
* This class is much simpler to use than the the previous series, as it has dropped support for complicated cases that were not being used. It also now only uses a single row in the options database, and takes care of creating it itself internally.
|
||||
*
|
||||
* Logging, though, may be noisier, unless your loggers are taking note of the log level and only registering what is required.
|
||||
*
|
||||
* Example of use (a lock that will expire if not released within 300 seconds)
|
||||
*
|
||||
* See test.php for a longer example (including logging).
|
||||
*
|
||||
* $my_lock = new Updraft_Semaphore_3_0('my_lock_name', 300);
|
||||
* // If getting the lock does not succeed first time, try again up to twice
|
||||
* if ($my_lock->lock(2)) {
|
||||
* try {
|
||||
* // do stuff ...
|
||||
* } catch (Exception $e) {
|
||||
* // We are making sure we release the lock in case of an error
|
||||
* } catch (Error $e) {
|
||||
* // We are making sure we release the lock in case of an error
|
||||
* }
|
||||
* $my_lock->release();
|
||||
* } else {
|
||||
* error_log("Sorry, could not get the lock");
|
||||
* }
|
||||
*/
|
||||
class Updraft_Semaphore_3_0 {
|
||||
|
||||
// Time after which the lock will expire (in seconds)
|
||||
protected $locked_for;
|
||||
|
||||
// Name for the lock in the WP options table
|
||||
protected $option_name;
|
||||
|
||||
// Lock status - a boolean
|
||||
protected $acquired = false;
|
||||
|
||||
// An array of loggers
|
||||
protected $loggers = array();
|
||||
|
||||
/**
|
||||
* Constructor. Instantiating does not lock anything, but sets up the details for future operations.
|
||||
*
|
||||
* @param String $name - a unique (across the WP site) name for the lock. Should be no more than 51 characters in length (because of the use of the WP options table, with some further characters used internally)
|
||||
* @param Integer $locked_for - time (in seconds) after which the lock will expire if not released. This needs to be positive if you don't want bad things to happen.
|
||||
* @param Array $loggers - an array of loggers
|
||||
*/
|
||||
public function __construct($name, $locked_for = 300, $loggers = array()) {
|
||||
$this->option_name = 'updraft_lock_'.$name;
|
||||
$this->locked_for = $locked_for;
|
||||
$this->loggers = $loggers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function to make sure that the lock is set up in the database
|
||||
*
|
||||
* @return Integer - 0 means 'failed' (which could include that someone else concurrently created it); 1 means 'already existed'; 2 means 'exists, because we created it). The intention is that non-zero results mean that the lock exists.
|
||||
*/
|
||||
private function ensure_database_initialised() {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$sql = $wpdb->prepare("SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name = %s", $this->option_name);
|
||||
|
||||
if (1 === (int) $wpdb->get_var($sql)) {
|
||||
$this->log('Lock option ('.$this->option_name.', '.$wpdb->options.') already existed in the database', 'debug');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$sql = $wpdb->prepare("INSERT INTO {$wpdb->options} (option_name, option_value, autoload) VALUES(%s, '0', 'no');", $this->option_name);
|
||||
|
||||
$rows_affected = $wpdb->query($sql);
|
||||
|
||||
if ($rows_affected > 0) {
|
||||
$this->log('Lock option ('.$this->option_name.', '.$wpdb->options.') was created in the database', 'debug');
|
||||
} else {
|
||||
$this->log('Lock option ('.$this->option_name.', '.$wpdb->options.') failed to be created in the database (could already exist)', 'notice');
|
||||
}
|
||||
|
||||
return ($rows_affected > 0) ? 2 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to acquire the lock. If it was already acquired, then nothing extra will be done (the method will be a no-op).
|
||||
*
|
||||
* @param Integer $retries - how many times to retry (after a 1 second sleep each time)
|
||||
*
|
||||
* @return Boolean - whether the lock was successfully acquired or not
|
||||
*/
|
||||
public function lock($retries = 0) {
|
||||
|
||||
if ($this->acquired) return true;
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$time_now = time();
|
||||
$acquire_until = $time_now + $this->locked_for;
|
||||
|
||||
$sql = $wpdb->prepare("UPDATE {$wpdb->options} SET option_value = %s WHERE option_name = %s AND option_value < %d", $acquire_until, $this->option_name, $time_now);
|
||||
|
||||
if (1 === $wpdb->query($sql)) {
|
||||
$this->log('Lock ('.$this->option_name.', '.$wpdb->options.') acquired', 'info');
|
||||
$this->acquired = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// See if the failure was caused by the row not existing (we check this only after failure, because it should only occur once on the site)
|
||||
if (!$this->ensure_database_initialised()) return false;
|
||||
|
||||
do {
|
||||
// Now that the row has been created, try again
|
||||
if (1 === $wpdb->query($sql)) {
|
||||
$this->log('Lock ('.$this->option_name.', '.$wpdb->options.') acquired after initialising the database', 'info');
|
||||
$this->acquired = true;
|
||||
return true;
|
||||
}
|
||||
$retries--;
|
||||
if ($retries >=0) {
|
||||
$this->log('Lock ('.$this->option_name.', '.$wpdb->options.') not yet acquired; sleeping', 'debug');
|
||||
sleep(1);
|
||||
// As a second has passed, update the time we are aiming for
|
||||
$time_now = time();
|
||||
$acquire_until = $time_now + $this->locked_for;
|
||||
$sql = $wpdb->prepare("UPDATE {$wpdb->options} SET option_value = %s WHERE option_name = %s AND option_value < %d", $acquire_until, $this->option_name, $time_now);
|
||||
}
|
||||
} while ($retries >= 0);
|
||||
|
||||
$this->log('Lock ('.$this->option_name.', '.$wpdb->options.') could not be acquired (it is locked)', 'info');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the lock
|
||||
*
|
||||
* N.B. We don't attempt to unlock it unless we locked it. i.e. Lost locks are left to expire rather than being forced. (If we want to force them, we'll need to introduce a new parameter).
|
||||
*
|
||||
* @return Boolean - if it returns false, then the lock was apparently not locked by us (and the caller will most likely therefore ignore the result, whatever it is).
|
||||
*/
|
||||
public function release() {
|
||||
if (!$this->acquired) return false;
|
||||
global $wpdb;
|
||||
$sql = $wpdb->prepare("UPDATE {$wpdb->options} SET option_value = '0' WHERE option_name = %s", $this->option_name);
|
||||
|
||||
$this->log('Lock option ('.$this->option_name.', '.$wpdb->options.') released', 'info');
|
||||
|
||||
$result = (int) $wpdb->query($sql) === 1;
|
||||
|
||||
$this->acquired = false;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the DB of any residual data. This should not be used as part of ordinary unlocking; only as part of deinstalling, or if you otherwise know that the lock will not be used again. If calling this, it's redundant to first unlock (and a no-op to attempt to do so afterwards).
|
||||
*/
|
||||
public function delete() {
|
||||
$this->acquired = false;
|
||||
|
||||
global $wpdb;
|
||||
$wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->options} WHERE option_name = %s", $this->option_name));
|
||||
|
||||
$this->log('Lock option ('.$this->option_name.', '.$wpdb->options.') was deleted from the database');
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures and logs any given messages
|
||||
*
|
||||
* @param String $message - the error message
|
||||
* @param String $level - the message level (debug, notice, info, warning, error)
|
||||
*/
|
||||
public function log($message, $level = 'info') {
|
||||
if (isset($this->loggers)) {
|
||||
foreach ($this->loggers as $logger) {
|
||||
$logger->log($message, $level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of loggers for this instance (removing any others).
|
||||
*
|
||||
* @param Array $loggers - the loggers for this task
|
||||
*/
|
||||
public function set_loggers($loggers) {
|
||||
$this->loggers = array();
|
||||
foreach ($loggers as $logger) {
|
||||
$this->add_logger($logger);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a logger to loggers list
|
||||
*
|
||||
* @param Callable $logger - a logger (a method with a callable function 'log', taking string parameters $level $message)
|
||||
*/
|
||||
public function add_logger($logger) {
|
||||
$this->loggers[] = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current list of loggers
|
||||
*
|
||||
* @return Array
|
||||
*/
|
||||
public function get_loggers() {
|
||||
return $this->loggers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed');
|
||||
|
||||
/*
|
||||
This is a small glue class, which makes available all the commands in UpdraftPlus_Commands, and translates the response from UpdraftPlus_Commands (which is either data to return, or a WP_Error) into the format used by UpdraftCentral.
|
||||
*/
|
||||
|
||||
if (class_exists('UpdraftCentral_UpdraftPlus_Commands')) return;
|
||||
|
||||
class UpdraftCentral_UpdraftPlus_Commands extends UpdraftCentral_Commands {
|
||||
|
||||
private $commands;
|
||||
|
||||
public function __construct($rc) {
|
||||
|
||||
parent::__construct($rc);
|
||||
|
||||
if (!class_exists('UpdraftPlus_Commands')) updraft_try_include_file('includes/class-commands.php', 'include_once');
|
||||
$this->commands = new UpdraftPlus_Commands($this);
|
||||
|
||||
}
|
||||
|
||||
public function __call($name, $arguments) {
|
||||
|
||||
if ('_' == substr($name, 0, 1) || !method_exists($this->commands, $name)) return $this->_generic_error_response('unknown_rpc_command', array(
|
||||
'prefix' => 'updraftplus',
|
||||
'command' => $name,
|
||||
'class' => 'UpdraftCentral_UpdraftPlus_Commands'
|
||||
));
|
||||
|
||||
$result = call_user_func_array(array($this->commands, $name), $arguments);
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
|
||||
return $this->_generic_error_response($result->get_error_code(), $result->get_error_data());
|
||||
|
||||
} else {
|
||||
|
||||
return $this->_response($result);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function _updraftplus_background_operation_started($msg) {
|
||||
|
||||
// Under-the-hood hackery to allow the browser connection to be closed, and the backup/download to continue
|
||||
|
||||
$rpc_response = $this->rc->return_rpc_message($this->_response($msg));
|
||||
|
||||
$data = isset($rpc_response['data']) ? $rpc_response['data'] : null;
|
||||
|
||||
$ud_rpc = $this->rc->get_current_udrpc();
|
||||
|
||||
$encoded = json_encode($ud_rpc->create_message($rpc_response['response'], $data, true));
|
||||
|
||||
global $updraftplus;
|
||||
$updraftplus->close_browser_connection($encoded);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
<?php
|
||||
|
||||
if (!defined('ABSPATH')) die('No direct access.');
|
||||
|
||||
class UpdraftPlus_Encryption {
|
||||
|
||||
/**
|
||||
* This will decrypt an encrypted file
|
||||
*
|
||||
* @param String $fullpath This is the full filesystem path to the encrypted file location
|
||||
* @param String $key This is the key to be used when decrypting
|
||||
* @param Boolean $to_temporary_file Use if the resulting file is not intended to be kept
|
||||
*
|
||||
* @return Boolean|Array -An array with info on the decryption; or false for failure
|
||||
*/
|
||||
public static function decrypt($fullpath, $key, $to_temporary_file = false) {
|
||||
|
||||
global $updraftplus;
|
||||
|
||||
$ensure_phpseclib = $updraftplus->ensure_phpseclib();
|
||||
|
||||
if (is_wp_error($ensure_phpseclib)) {
|
||||
$updraftplus->log("Failed to load phpseclib classes (".$ensure_phpseclib->get_error_code()."): ".$ensure_phpseclib->get_error_message());
|
||||
$updraftplus->log("Failed to load phpseclib classes (".$ensure_phpseclib->get_error_code()."): ".$ensure_phpseclib->get_error_message(), 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
// open file to read
|
||||
if (false === ($file_handle = fopen($fullpath, 'rb'))) return false;
|
||||
|
||||
$decrypted_path = dirname($fullpath).'/decrypt_'.basename($fullpath).'.tmp';
|
||||
// open new file from new path
|
||||
if (false === ($decrypted_handle = fopen($decrypted_path, 'wb+'))) return false;
|
||||
|
||||
// setup encryption
|
||||
$rijndael = new phpseclib_Crypt_Rijndael();
|
||||
$rijndael->setKey($key);
|
||||
$rijndael->disablePadding();
|
||||
$rijndael->enableContinuousBuffer();
|
||||
|
||||
if (defined('UPDRAFTPLUS_DECRYPTION_ENGINE')) {
|
||||
if ('openssl' == UPDRAFTPLUS_DECRYPTION_ENGINE) {
|
||||
$rijndael->setPreferredEngine(CRYPT_ENGINE_OPENSSL);
|
||||
} elseif ('mcrypt' == UPDRAFTPLUS_DECRYPTION_ENGINE) {
|
||||
$rijndael->setPreferredEngine(CRYPT_ENGINE_MCRYPT);
|
||||
} elseif ('internal' == UPDRAFTPLUS_DECRYPTION_ENGINE) {
|
||||
$rijndael->setPreferredEngine(CRYPT_ENGINE_INTERNAL);
|
||||
}
|
||||
}
|
||||
|
||||
$file_size = filesize($fullpath);
|
||||
$bytes_decrypted = 0;
|
||||
$buffer_size = defined('UPDRAFTPLUS_CRYPT_BUFFER_SIZE') ? UPDRAFTPLUS_CRYPT_BUFFER_SIZE : 2097152;
|
||||
|
||||
// loop around the file
|
||||
while ($bytes_decrypted < $file_size) {
|
||||
// read buffer sized amount from file
|
||||
if (false === ($file_part = fread($file_handle, $buffer_size))) return false;
|
||||
// check to ensure padding is needed before decryption
|
||||
$length = strlen($file_part);
|
||||
if (0 != $length % 16) {
|
||||
$pad = 16 - ($length % 16);
|
||||
$file_part = str_pad($file_part, $length + $pad, chr($pad));
|
||||
}
|
||||
|
||||
$decrypted_data = $rijndael->decrypt($file_part);
|
||||
|
||||
if (0 == $bytes_decrypted) {
|
||||
if (UpdraftPlus_Manipulation_Functions::str_ends_with($fullpath, '.gz.crypt')) {
|
||||
$first_two_chars = unpack('C*', substr($decrypted_data, 0, 2));
|
||||
// The first two decrypted bytes of the .gz file should always be 1f 8b
|
||||
if (31 != $first_two_chars[1] || 139 != $first_two_chars[2]) {
|
||||
return false;
|
||||
}
|
||||
} elseif (UpdraftPlus_Manipulation_Functions::str_ends_with($fullpath, '.zip.crypt')) {
|
||||
$first_four_chars = unpack('C*', substr($decrypted_data, 0, 2));
|
||||
// The first four decrypted bytes of the .zip file should always be 50 4B 03 04 or 50 4B 05 06 or 50 4B 07 08
|
||||
if (80 != $first_four_chars[1] || 75 != $first_four_chars[2] || !in_array($first_four_chars[3], array(3, 5, 7)) || !in_array($first_four_chars[3], array(4, 6, 8))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$is_last_block = ($bytes_decrypted + strlen($decrypted_data) >= $file_size);
|
||||
|
||||
$write_bytes = min($file_size - $bytes_decrypted, strlen($decrypted_data));
|
||||
if ($is_last_block) {
|
||||
$is_padding = false;
|
||||
$last_byte = ord(substr($decrypted_data, -1, 1));
|
||||
if ($last_byte < 16) {
|
||||
$is_padding = true;
|
||||
for ($j = 1; $j<=$last_byte; $j++) {
|
||||
if (substr($decrypted_data, -$j, 1) != chr($last_byte)) $is_padding = false;
|
||||
}
|
||||
}
|
||||
if ($is_padding) {
|
||||
$write_bytes -= $last_byte;
|
||||
}
|
||||
}
|
||||
|
||||
if (false === fwrite($decrypted_handle, $decrypted_data, $write_bytes)) return false;
|
||||
$bytes_decrypted += $buffer_size;
|
||||
}
|
||||
|
||||
// close the main file handle
|
||||
fclose($decrypted_handle);
|
||||
// close original file
|
||||
fclose($file_handle);
|
||||
|
||||
// remove the crypt extension from the end as this causes issues when opening
|
||||
$fullpath_new = preg_replace('/\.crypt$/', '', $fullpath, 1);
|
||||
// //need to replace original file with tmp file
|
||||
|
||||
$fullpath_basename = basename($fullpath_new);
|
||||
|
||||
if ($to_temporary_file) {
|
||||
return array(
|
||||
'fullpath' => $decrypted_path,
|
||||
'basename' => $fullpath_basename
|
||||
);
|
||||
}
|
||||
|
||||
if (false === rename($decrypted_path, $fullpath_new)) return false;
|
||||
|
||||
// need to send back the new decrypted path
|
||||
$decrypt_return = array(
|
||||
'fullpath' => $fullpath_new,
|
||||
'basename' => $fullpath_basename
|
||||
);
|
||||
|
||||
return $decrypt_return;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the encryption process when encrypting a file
|
||||
*
|
||||
* @param String $fullpath This is the full path to the DB file that needs ecrypting
|
||||
* @param String $key This is the key (salting) to be used when encrypting
|
||||
*
|
||||
* @return String|Boolean - Return the full path of the encrypted file, or false for an error
|
||||
*/
|
||||
public static function encrypt($fullpath, $key) {
|
||||
|
||||
global $updraftplus;
|
||||
|
||||
if (!function_exists('mcrypt_encrypt') && !extension_loaded('openssl')) {
|
||||
$updraftplus->log(sprintf(__('Your web-server does not have the %s module installed.', 'updraftplus'), 'PHP/mcrypt / PHP/OpenSSL').' '.__('Without it, encryption will be a lot slower.', 'updraftplus'), 'warning', 'nocrypt');
|
||||
}
|
||||
|
||||
// include Rijndael library from phpseclib
|
||||
$ensure_phpseclib = $updraftplus->ensure_phpseclib();
|
||||
|
||||
if (is_wp_error($ensure_phpseclib)) {
|
||||
$updraftplus->log("Failed to load phpseclib classes (".$ensure_phpseclib->get_error_code()."): ".$ensure_phpseclib->get_error_message());
|
||||
return false;
|
||||
}
|
||||
|
||||
// open file to read
|
||||
if (false === ($file_handle = fopen($fullpath, 'rb'))) {
|
||||
$updraftplus->log("Failed to open file for read access: $fullpath");
|
||||
return false;
|
||||
}
|
||||
|
||||
// encrypted path name. The trailing .tmp ensures that it will be cleaned up by the temporary file reaper eventually, if needs be.
|
||||
$encrypted_path = dirname($fullpath).'/encrypt_'.basename($fullpath).'.tmp';
|
||||
|
||||
$data_encrypted = 0;
|
||||
$buffer_size = defined('UPDRAFTPLUS_CRYPT_BUFFER_SIZE') ? UPDRAFTPLUS_CRYPT_BUFFER_SIZE : 2097152;
|
||||
|
||||
$time_last_logged = microtime(true);
|
||||
|
||||
$file_size = filesize($fullpath);
|
||||
|
||||
// Set initial value to false so we can check it later and decide what to do
|
||||
$resumption = false;
|
||||
|
||||
// setup encryption
|
||||
$rijndael = new phpseclib_Crypt_Rijndael();
|
||||
$rijndael->setKey($key);
|
||||
$rijndael->disablePadding();
|
||||
$rijndael->enableContinuousBuffer();
|
||||
|
||||
// First we need to get the block length, this method returns the length in bits we need to change this back to bytes in order to use it with the file operation methods.
|
||||
$block_length = $rijndael->getBlockLength() >> 3;
|
||||
|
||||
// Check if the path already exists as this could be a resumption
|
||||
if (file_exists($encrypted_path)) {
|
||||
|
||||
$updraftplus->log("Temporary encryption file found, will try to resume the encryption");
|
||||
|
||||
// The temp file exists so set resumption to true
|
||||
$resumption = true;
|
||||
|
||||
// Get the file size as this is needed to help resume the encryption
|
||||
$data_encrypted = filesize($encrypted_path);
|
||||
// Get the true file size e.g without padding used for various resumption paths
|
||||
$true_data_encrypted = $data_encrypted - ($data_encrypted % $buffer_size);
|
||||
|
||||
if ($data_encrypted >= $block_length) {
|
||||
|
||||
// Open existing file from the path
|
||||
if (false === ($encrypted_handle = fopen($encrypted_path, 'rb+'))) {
|
||||
$updraftplus->log("Failed to open file for write access on resumption: $encrypted_path");
|
||||
$resumption = false;
|
||||
}
|
||||
|
||||
// First check if our buffer size needs padding if it does increase buffer size to length that doesn't need padding
|
||||
if (0 != $buffer_size % 16) {
|
||||
$pad = 16 - ($buffer_size % 16);
|
||||
$true_buffer_size = $buffer_size + $pad;
|
||||
} else {
|
||||
$true_buffer_size = $buffer_size;
|
||||
}
|
||||
|
||||
// Now check if using modulo on data encrypted and buffer size returns 0 if it doesn't then the last block was a partial write and we need to discard that and get the last useable IV by adding this value to the block length
|
||||
$partial_data_size = $data_encrypted % $true_buffer_size;
|
||||
|
||||
// We need to reconstruct the IV from the previous run in order for encryption to resume
|
||||
if (-1 === (fseek($encrypted_handle, $data_encrypted - ($block_length + $partial_data_size)))) {
|
||||
$updraftplus->log("Failed to move file pointer to correct position to get IV: $encrypted_path");
|
||||
$resumption = false;
|
||||
}
|
||||
|
||||
// Read previous block length from file
|
||||
if (false === ($iv = fread($encrypted_handle, $block_length))) {
|
||||
$updraftplus->log("Failed to read from file to get IV: $encrypted_path");
|
||||
$resumption = false;
|
||||
}
|
||||
|
||||
$rijndael->setIV($iv);
|
||||
|
||||
// Now we need to set the file pointer for the original file to the correct position and take into account the padding added, this padding needs to be removed to get the true amount of bytes read from the original file
|
||||
if (-1 === (fseek($file_handle, $true_data_encrypted))) {
|
||||
$updraftplus->log("Failed to move file pointer to correct position to resume encryption: $fullpath");
|
||||
$resumption = false;
|
||||
}
|
||||
|
||||
} else {
|
||||
// If we enter here then the temp file exists but it is either empty or has one incomplete block we may as well start again
|
||||
$resumption = false;
|
||||
}
|
||||
|
||||
if (!$resumption) {
|
||||
$updraftplus->log("Could not resume the encryption will now try to start again");
|
||||
// remove the existing encrypted file as it's no good to us now
|
||||
@unlink($encrypted_path);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise if the file doesn't exist.
|
||||
// reset the data encrypted so that the loop can be entered
|
||||
$data_encrypted = 0;
|
||||
// setup encryption to reset the IV
|
||||
$rijndael = new phpseclib_Crypt_Rijndael();
|
||||
$rijndael->setKey($key);
|
||||
$rijndael->disablePadding();
|
||||
$rijndael->enableContinuousBuffer();
|
||||
// reset the file pointer and then we should be able to start from fresh
|
||||
if (-1 === (fseek($file_handle, 0))) {
|
||||
$updraftplus->log("Failed to move file pointer to start position to restart encryption: $fullpath");
|
||||
$resumption = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$resumption) {
|
||||
// open new file from new path
|
||||
if (false === ($encrypted_handle = fopen($encrypted_path, 'wb+'))) {
|
||||
$updraftplus->log("Failed to open file for write access: $encrypted_path");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// loop around the file
|
||||
while ($data_encrypted < $file_size) {
|
||||
|
||||
// read buffer-sized amount from file
|
||||
if (false === ($file_part = fread($file_handle, $buffer_size))) {
|
||||
$updraftplus->log("Failed to read from file: $fullpath");
|
||||
return false;
|
||||
}
|
||||
|
||||
// check to ensure padding is needed before encryption
|
||||
$length = strlen($file_part);
|
||||
if (0 != $length % 16) {
|
||||
$pad = 16 - ($length % 16);
|
||||
$file_part = str_pad($file_part, $length + $pad, chr($pad));
|
||||
}
|
||||
|
||||
$encrypted_data = $rijndael->encrypt($file_part);
|
||||
|
||||
if (false === fwrite($encrypted_handle, $encrypted_data)) {
|
||||
$updraftplus->log("Failed to write to file: $encrypted_path");
|
||||
return false;
|
||||
}
|
||||
|
||||
$data_encrypted += $buffer_size;
|
||||
|
||||
$time_since_last_logged = microtime(true) - $time_last_logged;
|
||||
if ($time_since_last_logged > 5) {
|
||||
$time_since_last_logged = microtime(true);
|
||||
$updraftplus->log("Encrypting file: completed $data_encrypted bytes");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// close the main file handle
|
||||
fclose($encrypted_handle);
|
||||
fclose($file_handle);
|
||||
|
||||
// encrypted path
|
||||
$result_path = $fullpath.'.crypt';
|
||||
|
||||
// need to replace original file with tmp file
|
||||
if (false === rename($encrypted_path, $result_path)) {
|
||||
$updraftplus->log("File rename failed: $encrypted_path -> $result_path");
|
||||
return false;
|
||||
}
|
||||
|
||||
return $result_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function spools the decrypted contents of a file to the browser
|
||||
*
|
||||
* @param String $fullpath This is the full path to the encrypted file
|
||||
* @param String $encryption This is the key used to decrypt the file
|
||||
*
|
||||
* @uses header()
|
||||
*/
|
||||
public static function spool_crypted_file($fullpath, $encryption) {
|
||||
|
||||
global $updraftplus;
|
||||
|
||||
if ('' == $encryption) $encryption = UpdraftPlus_Options::get_updraft_option('updraft_encryptionphrase');
|
||||
|
||||
if ('' == $encryption) {
|
||||
header('Content-type: text/plain');
|
||||
echo esc_html(__('Decryption failed.', 'updraftplus').' '.__('The database file is encrypted, but you have no encryption key entered.', 'updraftplus'));
|
||||
$updraftplus->log('Decryption of database failed: the database file is encrypted, but you have no encryption key entered.', 'error');
|
||||
} else {
|
||||
|
||||
// now decrypt the file and return array
|
||||
$decrypted_file = self::decrypt($fullpath, $encryption, true);
|
||||
|
||||
// check to ensure there is a response back
|
||||
if (is_array($decrypted_file)) {
|
||||
header('Content-type: application/x-gzip');
|
||||
header("Content-Disposition: attachment; filename=\"".$decrypted_file['basename']."\";");
|
||||
header("Content-Length: ".filesize($decrypted_file['fullpath']));
|
||||
readfile($decrypted_file['fullpath']);
|
||||
|
||||
// need to remove the file as this is no longer needed on the local server
|
||||
unlink($decrypted_file['fullpath']);
|
||||
} else {
|
||||
header('Content-type: text/plain');
|
||||
echo esc_html(__('Decryption failed.', 'updraftplus').' '.__('The most likely cause is that you used the wrong key.', 'updraftplus').' '.__('The decryption key used:', 'updraftplus').' '.$encryption);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether an indicated backup file is encrypted or not, as indicated by the suffix
|
||||
*
|
||||
* @param String $file - the filename
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public static function is_file_encrypted($file) {
|
||||
return preg_match('/\.crypt$/i', $file);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,937 @@
|
||||
<?php
|
||||
|
||||
if (!defined('UPDRAFTPLUS_DIR')) die('No access.');
|
||||
|
||||
/*
|
||||
See class-commands.php for explanation about how these classes work.
|
||||
*/
|
||||
|
||||
if (!class_exists('UpdraftPlus_Commands')) updraft_try_include_file('includes/class-commands.php', 'require_once');
|
||||
|
||||
/**
|
||||
* An extension, because commands available via wp-admin are a super-set of those which are available through all mechanisms
|
||||
*/
|
||||
class UpdraftPlus_WPAdmin_Commands extends UpdraftPlus_Commands {
|
||||
|
||||
private $_uc_helper;
|
||||
|
||||
private $_updraftplus_admin;
|
||||
|
||||
private $_updraftplus;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $uc_helper The 'helper' needs to provide the method _updraftplus_background_operation_started
|
||||
*/
|
||||
public function __construct($uc_helper) {
|
||||
$this->_uc_helper = $uc_helper;
|
||||
global $updraftplus_admin, $updraftplus;
|
||||
$this->_updraftplus_admin = $updraftplus_admin;
|
||||
$this->_updraftplus = $updraftplus;
|
||||
parent::__construct($uc_helper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces a resumption of a backup where the resumption is overdue (so apparently cron is not working)
|
||||
*
|
||||
* @param Array $info - keys 'job_id' and 'resumption'
|
||||
*
|
||||
* @return Array - if there is an error. Otherwise, dies.
|
||||
*/
|
||||
public function forcescheduledresumption($info) {
|
||||
|
||||
// Casting $resumption to int is absolutely necessary, as the WP cron system uses a hashed serialisation of the parameters for identifying jobs. Different type => different hash => does not match
|
||||
$resumption = (int) $info['resumption'];
|
||||
$job_id = $info['job_id'];
|
||||
$get_cron = $this->_updraftplus_admin->get_cron($job_id);
|
||||
if (!is_array($get_cron)) {
|
||||
return array('r' => false);
|
||||
} else {
|
||||
$this->_updraftplus->log("Forcing resumption: job id=$job_id, resumption=$resumption");
|
||||
wp_clear_scheduled_hook('updraft_backup_resume', array($resumption, $job_id));
|
||||
$this->_updraftplus->close_browser_connection(json_encode(array('r' => true)));
|
||||
$this->_updraftplus->jobdata_set_from_array($get_cron[1]);
|
||||
$this->_updraftplus->backup_resume($resumption, $job_id);
|
||||
// We don't want to return. The close_browser_connection call already returned a result.
|
||||
die;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a WordPress action and dies
|
||||
*
|
||||
* @param Array $data - must have at least the key 'wpaction' with a string value
|
||||
*
|
||||
* @return WP_Error if no command was included
|
||||
*/
|
||||
public function call_wordpress_action($data) {
|
||||
|
||||
if (empty($data['wpaction'])) return new WP_Error('error', '', 'no command sent');
|
||||
|
||||
$response = $this->_updraftplus_admin->call_wp_action($data, array($this->_uc_helper, '_updraftplus_background_operation_started'));// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Unused variable is for future use.
|
||||
|
||||
die;
|
||||
|
||||
// return array('response' => $response['response'], 'status' => $response['status'], 'log' => $response['log'] );
|
||||
}
|
||||
|
||||
public function updraftcentral_delete_key($params) {
|
||||
global $updraftcentral_main;
|
||||
if (!is_a($updraftcentral_main, 'UpdraftCentral_Main')) {
|
||||
return array('error' => 'UpdraftCentral_Main object not found');
|
||||
}
|
||||
|
||||
return $updraftcentral_main->delete_key($params['key_id']);
|
||||
}
|
||||
|
||||
public function updraftcentral_get_log($params) {
|
||||
global $updraftcentral_main;
|
||||
if (!is_a($updraftcentral_main, 'UpdraftCentral_Main')) {
|
||||
return array('error' => 'UpdraftCentral_Main object not found');
|
||||
}
|
||||
return call_user_func(array($updraftcentral_main, 'get_log'), $params);
|
||||
}
|
||||
|
||||
public function updraftcentral_create_key($params) {
|
||||
global $updraftcentral_main;
|
||||
if (!is_a($updraftcentral_main, 'UpdraftCentral_Main')) {
|
||||
return array('error' => 'UpdraftCentral_Main object not found');
|
||||
}
|
||||
return call_user_func(array($updraftcentral_main, 'create_key'), $params);
|
||||
}
|
||||
|
||||
public function restore_alldownloaded($params) {
|
||||
|
||||
$backups = UpdraftPlus_Backup_History::get_history();
|
||||
$updraft_dir = $this->_updraftplus->backups_dir_location();
|
||||
|
||||
$timestamp = (int) $params['timestamp'];
|
||||
if (!isset($backups[$timestamp])) {
|
||||
return array('m' => '', 'w' => '', 'e' => __('No such backup set exists', 'updraftplus'));
|
||||
}
|
||||
|
||||
$mess = array();
|
||||
parse_str(stripslashes($params['restoreopts']), $res);
|
||||
|
||||
if (isset($res['updraft_restore'])) {
|
||||
|
||||
$error_levels = version_compare(PHP_VERSION, '8.4.0', '>=') ? E_ALL : E_ALL & ~E_STRICT;
|
||||
set_error_handler(array($this->_updraftplus_admin, 'get_php_errors'), $error_levels);
|
||||
|
||||
$elements = array_flip($res['updraft_restore']);
|
||||
|
||||
$warn = array();
|
||||
$err = array();
|
||||
|
||||
if (function_exists('set_time_limit')) @set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
|
||||
$max_execution_time = (int) @ini_get('max_execution_time');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
|
||||
|
||||
if ($max_execution_time>0 && $max_execution_time<61) {
|
||||
$warn[] = sprintf(__('The PHP setup on this webserver allows only %s seconds for PHP to run, and does not allow this limit to be raised.', 'updraftplus'), $max_execution_time).' '.__('If you have a lot of data to import, and if the restore operation times out, then you will need to ask your web hosting company for ways to raise this limit (or attempt the restoration piece-by-piece).', 'updraftplus');
|
||||
}
|
||||
|
||||
if (isset($backups[$timestamp]['native']) && false == $backups[$timestamp]['native']) {
|
||||
$warn[] = __('This backup set was not known by UpdraftPlus to be created by the current WordPress installation, but was either found in remote storage, or was sent from a remote site.', 'updraftplus').' '.__('You should make sure that this really is a backup set intended for use on this website, before you restore (rather than a backup set of an unrelated website).', 'updraftplus');
|
||||
}
|
||||
|
||||
if (isset($elements['db'])) {
|
||||
|
||||
// Analyse the header of the database file + display results
|
||||
list ($mess2, $warn2, $err2, $info) = $this->_updraftplus->analyse_db_file($timestamp, $res);
|
||||
$mess = array_merge($mess, $mess2);
|
||||
$warn = array_merge($warn, $warn2);
|
||||
$err = array_merge($err, $err2);
|
||||
foreach ($backups[$timestamp] as $bid => $bval) {
|
||||
if ('db' != $bid && 'db' == substr($bid, 0, 2) && '-size' != substr($bid, -5, 5)) {
|
||||
$warn[] = __('Only the WordPress database can be restored; you will need to deal with the external database manually.', 'updraftplus');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$backupable_entities = $this->_updraftplus->get_backupable_file_entities(true, true);
|
||||
$backupable_plus_db = $backupable_entities;
|
||||
$backupable_plus_db['db'] = array('path' => 'path-unused', 'description' => __('Database', 'updraftplus'));
|
||||
|
||||
if (!empty($backups[$timestamp]['meta_foreign'])) {
|
||||
$foreign_known = apply_filters('updraftplus_accept_archivename', array());
|
||||
if (!is_array($foreign_known) || empty($foreign_known[$backups[$timestamp]['meta_foreign']])) {
|
||||
$err[] = sprintf(__('Backup created by unknown source (%s) - cannot be restored.', 'updraftplus'), $backups[$timestamp]['meta_foreign']);
|
||||
} else {
|
||||
// For some reason, on PHP 5.5 passing by reference in a single array stopped working with apply_filters_ref_array (though not with do_action_ref_array).
|
||||
$backupable_plus_db = apply_filters_ref_array("updraftplus_importforeign_backupable_plus_db", array($backupable_plus_db, array($foreign_known[$backups[$timestamp]['meta_foreign']], &$mess, &$warn, &$err)));
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($backupable_plus_db as $type => $entity_info) {
|
||||
if (!isset($elements[$type]) || (isset($entity_info['restorable']) && !$entity_info['restorable'])) continue;
|
||||
$whatwegot = $backups[$timestamp][$type];
|
||||
if (is_string($whatwegot)) $whatwegot = array($whatwegot);
|
||||
$expected_index = 0;
|
||||
$missing = '';
|
||||
ksort($whatwegot);
|
||||
$outof = false;
|
||||
foreach ($whatwegot as $index => $file) {
|
||||
if (preg_match('/\d+of(\d+)\.zip/', $file, $omatch)) {
|
||||
$outof = max($omatch[1], 1);
|
||||
}
|
||||
while ($expected_index < $index) {
|
||||
$missing .= ('' == $missing) ? (1+$expected_index) : ",".(1+$expected_index);
|
||||
$expected_index++;
|
||||
}
|
||||
if (!file_exists($updraft_dir.'/'.$file)) {
|
||||
$err[] = sprintf(__('File not found (you need to upload it): %s', 'updraftplus'), $updraft_dir.'/'.$file);
|
||||
} elseif (filesize($updraft_dir.'/'.$file) == 0) {
|
||||
$err[] = sprintf(__('File was found, but is zero-sized (you need to re-upload it): %s', 'updraftplus'), $file);
|
||||
} else {
|
||||
$itext = (0 == $index) ? '' : $index;
|
||||
if (!empty($backups[$timestamp][$type.$itext.'-size']) && filesize($updraft_dir.'/'.$file) != $backups[$timestamp][$type.$itext.'-size']) {
|
||||
if (empty($warn['doublecompressfixed'])) {
|
||||
$warn[] = sprintf(__('File (%s) was found, but has a different size (%s) from what was expected (%s) - it may be corrupt.', 'updraftplus'), $file, filesize($updraft_dir.'/'.$file), $backups[$timestamp][$type.$itext.'-size']);
|
||||
}
|
||||
}
|
||||
do_action_ref_array("updraftplus_checkzip_$type", array($updraft_dir.'/'.$file, &$mess, &$warn, &$err));
|
||||
}
|
||||
$expected_index++;
|
||||
}
|
||||
do_action_ref_array("updraftplus_checkzip_end_$type", array(&$mess, &$warn, &$err));
|
||||
// Detect missing archives where they are missing from the end of the set
|
||||
if ($outof>0 && $expected_index < $outof) {
|
||||
for ($j = $expected_index; $j<$outof; $j++) {
|
||||
$missing .= ('' == $missing) ? (1+$j) : ",".(1+$j);
|
||||
}
|
||||
}
|
||||
if ('' != $missing) {
|
||||
$warn[] = sprintf(__("This multi-archive backup set appears to have the following archives missing: %s", 'updraftplus'), $missing.' ('.$entity_info['description'].')');
|
||||
}
|
||||
}
|
||||
|
||||
// Check this backup set has a incremental_sets array e.g may have been created before this array was introduced
|
||||
if (isset($backups[$timestamp]['incremental_sets'])) {
|
||||
if (isset($elements['db']) && 1 === count($elements)) {
|
||||
// Don't show the incremental dropdown if the user only selects 'database'
|
||||
} else {
|
||||
$incremental_sets = array_keys($backups[$timestamp]['incremental_sets']);
|
||||
// Check if there are more than one timestamp in the incremental set
|
||||
if (1 < count($incremental_sets)) {
|
||||
$incremental_select_html = '<div class="udp-notice updraft-restore-option"><label>'.__('This backup set contains incremental backups of your files; please select the time you wish to restore your files to', 'updraftplus').': </label>';
|
||||
$incremental_select_html .= '<select name="updraft_incremental_restore_point" id="updraft_incremental_restore_point">';
|
||||
$incremental_sets = array_reverse($incremental_sets);
|
||||
$first_timestamp = $incremental_sets[0];
|
||||
|
||||
foreach ($incremental_sets as $set_timestamp) {
|
||||
$pretty_date = get_date_from_gmt(gmdate('Y-m-d H:i:s', (int) $set_timestamp), 'M d, Y G:i');
|
||||
$esc_pretty_date = esc_attr($pretty_date);
|
||||
$incremental_select_html .= '<option value="'.$set_timestamp.'" '.selected($set_timestamp, $first_timestamp, false).'>'.$esc_pretty_date.'</option>';
|
||||
}
|
||||
|
||||
$incremental_select_html .= '</select>';
|
||||
$incremental_select_html .= '</div>';
|
||||
$info['addui'] = empty($info['addui']) ? $incremental_select_html : $info['addui'].'<br>'.$incremental_select_html;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (0 == count($err) && 0 == count($warn)) {
|
||||
$mess_first = __('The backup archive files have been successfully processed.', 'updraftplus').' '.__('Now press Restore to proceed.', 'updraftplus');
|
||||
} elseif (0 == count($err)) {
|
||||
$mess_first = __('The backup archive files have been processed, but with some warnings.', 'updraftplus').' '.__('If all is well, then press Restore to proceed.', 'updraftplus').' '.__('Otherwise, cancel and correct any problems first.', 'updraftplus');
|
||||
} else {
|
||||
$mess_first = __('The backup archive files have been processed, but with some errors.', 'updraftplus').' '.__('You will need to cancel and correct any problems before retrying.', 'updraftplus');
|
||||
}
|
||||
|
||||
if (count($this->_updraftplus_admin->logged) >0) {
|
||||
foreach ($this->_updraftplus_admin->logged as $lwarn) $warn[] = $lwarn;
|
||||
}
|
||||
restore_error_handler();
|
||||
|
||||
// Get the info if it hasn't already come from the DB scan
|
||||
if (!isset($info) || !is_array($info)) $info = array();
|
||||
|
||||
// Not all characters can be json-encoded, and we don't need this potentially-arbitrary user-supplied info.
|
||||
unset($info['label']);
|
||||
|
||||
if (!isset($info['created_by_version']) && !empty($backups[$timestamp]['created_by_version'])) $info['created_by_version'] = $backups[$timestamp]['created_by_version'];
|
||||
|
||||
if (!isset($info['multisite']) && !empty($backups[$timestamp]['is_multisite'])) $info['multisite'] = $backups[$timestamp]['is_multisite'];
|
||||
|
||||
do_action_ref_array('updraftplus_restore_all_downloaded_postscan', array($backups, $timestamp, $elements, &$info, &$mess, &$warn, &$err));
|
||||
|
||||
if (0 == count($err) && 0 == count($warn)) {
|
||||
$mess_first = __('The backup archive files have been successfully processed.', 'updraftplus').' '.__('Now press Restore again to proceed.', 'updraftplus');
|
||||
} elseif (0 == count($err)) {
|
||||
$mess_first = __('The backup archive files have been processed, but with some warnings.', 'updraftplus').' '.__('If all is well, then now press Restore again to proceed.', 'updraftplus').' '.__('Otherwise, cancel and correct any problems first.', 'updraftplus');
|
||||
} else {
|
||||
$mess_first = __('The backup archive files have been processed, but with some errors.', 'updraftplus').' '.__('You will need to cancel and correct any problems before retrying.', 'updraftplus');
|
||||
}
|
||||
|
||||
$warn_result = '';
|
||||
foreach ($warn as $warning) {
|
||||
if (!$warn_result) $warn_result = '<ul id="updraft_restore_warnings">';
|
||||
$warn_result .= '<li>'.$warning.'</li>';
|
||||
}
|
||||
if ($warn_result) $warn_result .= '</ul>';
|
||||
|
||||
return array('m' => '<p>'.$mess_first.'</p>'.implode('<br>', $mess), 'w' => $warn_result, 'e' => implode('<br>', $err), 'i' => json_encode($info));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The purpose of this is to detect brokenness caused by extra line feeds in plugins/themes - before it breaks other AJAX operations and leads to support requests
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function ping() {
|
||||
return 'pong';
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called via ajax and will update the autobackup notice dismiss time
|
||||
*
|
||||
* @return array - an empty array
|
||||
*/
|
||||
public function dismissautobackup() {
|
||||
UpdraftPlus_Options::update_updraft_option('updraftplus_dismissedautobackup', time() + 84*86400);
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called via ajax and will update the general notice dismiss time
|
||||
*
|
||||
* @return array - an empty array
|
||||
*/
|
||||
public function dismiss_notice() {
|
||||
UpdraftPlus_Options::update_updraft_option('dismissed_general_notices_until', time() + 84*86400);
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called via ajax and will update the review notice dismiss time
|
||||
*
|
||||
* @param array $data - an array that contains the dismiss notice for time
|
||||
*
|
||||
* @return array - an empty array
|
||||
*/
|
||||
public function dismiss_review_notice($data) {
|
||||
if (empty($data['dismiss_forever'])) {
|
||||
UpdraftPlus_Options::update_updraft_option('dismissed_review_notice', time() + 84*86400);
|
||||
} else {
|
||||
UpdraftPlus_Options::update_updraft_option('dismissed_review_notice', 100 * (365.25 * 86400));
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called via ajax and will update the season notice dismiss time
|
||||
*
|
||||
* @return array - an empty array
|
||||
*/
|
||||
public function dismiss_season() {
|
||||
UpdraftPlus_Options::update_updraft_option('dismissed_season_notices_until', time() + 366*86400);
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called via ajax and will update the clone php notice dismiss time
|
||||
*
|
||||
* @return array - an empty array
|
||||
*/
|
||||
public function dismiss_clone_php_notice() {
|
||||
UpdraftPlus_Options::update_updraft_option('dismissed_clone_php_notices_until', time() + 180 * 86400);
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update and set dismiss_phpseclib_notice option name to true
|
||||
*
|
||||
* @return array - an associative array containing a key named 'success' with 1 value which indicates the successful of updating the option
|
||||
*/
|
||||
public function dismiss_phpseclib_notice() {
|
||||
UpdraftPlus_Options::update_updraft_option('updraft_dismiss_phpseclib_notice', true);
|
||||
return array('success' => 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called via ajax and will update the WooCommerce clone notice dismiss time
|
||||
*
|
||||
* @return array - an empty array
|
||||
*/
|
||||
public function dismiss_clone_wc_notice() {
|
||||
UpdraftPlus_Options::update_updraft_option('dismissed_clone_wc_notices_until', time() + 90 * 86400);
|
||||
return array();
|
||||
}
|
||||
|
||||
public function set_autobackup_default($params) {
|
||||
$default = empty($params['default']) ? 0 : 1;
|
||||
UpdraftPlus_Options::update_updraft_option('updraft_autobackup_default', $default);
|
||||
return array();
|
||||
}
|
||||
|
||||
public function dismissexpiry() {
|
||||
UpdraftPlus_Options::update_updraft_option('updraftplus_dismissedexpiry', time() + 14*86400);
|
||||
return array();
|
||||
}
|
||||
|
||||
public function dismissdashnotice() {
|
||||
UpdraftPlus_Options::update_updraft_option('updraftplus_dismisseddashnotice', time() + 366*86400);
|
||||
return array();
|
||||
}
|
||||
|
||||
public function rawbackuphistory() {
|
||||
// This is used for iframe source; hence, returns a string
|
||||
$show_raw_data = $this->_updraftplus_admin->show_raw_backups();
|
||||
return $show_raw_data['html'];
|
||||
}
|
||||
|
||||
/**
|
||||
* N.B. Not exactly the same as the phpinfo method in the UpdraftCentral core class
|
||||
* Returns a string, as it is directly fetched as the source of an iframe
|
||||
*
|
||||
* @return String - returns the resulting HTML
|
||||
*/
|
||||
public function phpinfo() {
|
||||
|
||||
ob_start();
|
||||
|
||||
if (function_exists('phpinfo')) phpinfo(INFO_ALL ^ (INFO_CREDITS | INFO_LICENSE));
|
||||
|
||||
echo '<h3 id="ud-debuginfo-constants">'.esc_html__('Constants', 'updraftplus').'</h3>';
|
||||
$opts = @get_defined_constants();// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
|
||||
ksort($opts);
|
||||
echo '<table><thead></thead><tbody>';
|
||||
foreach ($opts as $key => $opt) {
|
||||
// Administrators can already read these in other ways, but we err on the side of caution
|
||||
if (is_string($opt) && false !== stripos($opt, 'api_key')) $opt = '***';
|
||||
echo '<tr><td>'.esc_html($key).'</td><td>'.esc_html(print_r($opt, true)).'</td>';
|
||||
}
|
||||
echo '</tbody></table>';
|
||||
|
||||
$ret = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
return $ret;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return messages if there are more than 4 overdue cron jobs
|
||||
*
|
||||
* @return Array - the messages are stored in an associative array and are indexed with key 'm'
|
||||
*/
|
||||
public function check_overdue_crons() {
|
||||
$messages = array();
|
||||
$how_many_overdue = $this->_updraftplus_admin->howmany_overdue_crons();
|
||||
if ($how_many_overdue >= 4) {
|
||||
$messages['m'] = array();
|
||||
$messages['m'][] = $this->_updraftplus_admin->show_admin_warning_overdue_crons($how_many_overdue);
|
||||
if (defined('DISABLE_WP_CRON') && DISABLE_WP_CRON && (!defined('UPDRAFTPLUS_DISABLE_WP_CRON_NOTICE') || !UPDRAFTPLUS_DISABLE_WP_CRON_NOTICE)) $messages['m'][] = $this->_updraftplus_admin->show_admin_warning_disabledcron();
|
||||
}
|
||||
return $messages;
|
||||
}
|
||||
|
||||
public function whichdownloadsneeded($params) {
|
||||
// The purpose of this is to look at the list of indicated downloads, and indicate which are not already fully downloaded. i.e. Which need further action.
|
||||
$send_back = array();
|
||||
$backup = UpdraftPlus_Backup_History::get_history($params['timestamp']);
|
||||
$updraft_dir = $this->_updraftplus->backups_dir_location();
|
||||
$backupable_entities = $this->_updraftplus->get_backupable_file_entities();
|
||||
|
||||
if (empty($backup)) return array('result' => 'asyouwere');
|
||||
|
||||
if (isset($params['updraftplus_clone']) && empty($params['downloads'])) {
|
||||
$entities = array('db', 'plugins', 'themes', 'uploads', 'others');
|
||||
foreach ($entities as $entity) {
|
||||
|
||||
foreach ($backup as $key => $data) {
|
||||
if ($key != $entity) continue;
|
||||
|
||||
$set_contents = '';
|
||||
$entity_array = array();
|
||||
$entity_array[] = $key;
|
||||
|
||||
if ('db' == $key) {
|
||||
$set_contents = "0";
|
||||
} else {
|
||||
foreach (array_keys($data) as $findex) {
|
||||
$set_contents .= ('' == $set_contents) ? $findex : ",$findex";
|
||||
}
|
||||
}
|
||||
|
||||
$entity_array[] = $set_contents;
|
||||
$params['downloads'][] = $entity_array;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($params['downloads'] as $i => $download) {
|
||||
if (is_array($download) && 2 == count($download) && isset($download[0]) && isset($download[1])) {
|
||||
$entity = $download[0];
|
||||
if (('db' == $entity || isset($backupable_entities[$entity])) && isset($backup[$entity])) {
|
||||
$indexes = explode(',', $download[1]);
|
||||
$retain_string = '';
|
||||
foreach ($indexes as $index) {
|
||||
$retain = true; // default
|
||||
$findex = (0 == $index) ? '' : (string) $index;
|
||||
$files = $backup[$entity];
|
||||
if (!is_array($files)) $files = array($files);
|
||||
$size_key = $entity.$findex.'-size';
|
||||
if (isset($files[$index]) && isset($backup[$size_key])) {
|
||||
$file = $updraft_dir.'/'.$files[$index];
|
||||
if (file_exists($file) && filesize($file) >= $backup[$size_key]) {
|
||||
$retain = false;
|
||||
}
|
||||
}
|
||||
if ($retain) {
|
||||
$retain_string .= ('' === $retain_string) ? $index : ','.$index;
|
||||
$send_back[$i][0] = $entity;
|
||||
$send_back[$i][1] = $retain_string;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$send_back[$i][0] = $entity;
|
||||
$send_back[$i][1] = $download[$i][1];
|
||||
}
|
||||
} else {
|
||||
// Format not understood. Just send it back as-is.
|
||||
$send_back[$i] = $download[$i];
|
||||
}
|
||||
}
|
||||
// Finally, renumber the keys (to usual PHP style - 0, 1, ...). Otherwise, in order to preserve the indexes, json_encode() will create an object instead of an array in the case where $send_back only has one element (and is indexed with an index > 0)
|
||||
$send_back = array_values($send_back);
|
||||
return array('downloads' => $send_back);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is an handler function that checks what entity has been specified in the $params and calls the required method
|
||||
*
|
||||
* @param [array] $params this is an array of parameters sent via ajax it can include various things depending on what has called this method, this method only cares about the entity parameter which is used to call the correct method and return tree nodes based on that
|
||||
* @return [array] returns an array of jstree nodes
|
||||
*/
|
||||
public function get_jstree_directory_nodes($params) {
|
||||
|
||||
if ('filebrowser' == $params['entity']) {
|
||||
$node_array = $this->_updraft_jstree_directory($params);
|
||||
} elseif ('zipbrowser' == $params['entity']) {
|
||||
$node_array = $this->_updraft_jstree_zip($params);
|
||||
} else {
|
||||
$node_array = apply_filters('updraftplus_jstree_'.$params['entity'], array(), $params);
|
||||
}
|
||||
return empty($node_array['error']) ? array('nodes' => $node_array) : $node_array;
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates an array of nodes, built from either ABSPATH or the given directory ready to be returned to the jstree object.
|
||||
*
|
||||
* @param [array] $params this is an array of parameters sent via ajax it can include the following:
|
||||
* node - this is a jstree node object containing information about the selected node
|
||||
* path - this is a path if provided this will be used to build the tree otherwise ABSPATH is used
|
||||
* drop_directory - this is a boolean that if set to true will drop one directory level off the path this is used so that you can move above the current root directory
|
||||
* @return [array] returns an array of jstree nodes
|
||||
*/
|
||||
private function _updraft_jstree_directory($params) {
|
||||
$node_array = array();
|
||||
|
||||
// # is the root node if it's the root node then this is the first call so create a parent node otherwise it's a child node and we should get the path from the node id
|
||||
if ('#' == $params['node']['id']) {
|
||||
$path = ABSPATH;
|
||||
|
||||
if (!empty($params['path']) && is_dir($params['path']) && is_readable($params['path'])) $path = $params['path'];
|
||||
$one_dir_up = dirname($path);
|
||||
|
||||
if (!empty($params['drop_directory']) && true == $params['drop_directory'] && is_readable($one_dir_up)) $path = $one_dir_up;
|
||||
if (empty($params['skip_root_node'])) {
|
||||
$node_array[] = array(
|
||||
'text' => basename($path),
|
||||
'children' => true,
|
||||
'id' => $path,
|
||||
'icon' => 'jstree-folder',
|
||||
'state' => array(
|
||||
'opened' => true
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$path = $params['node']['id'];
|
||||
}
|
||||
|
||||
$page = empty($params['page']) ? '' : $params['page'];
|
||||
|
||||
if ($dh = opendir($path)) {
|
||||
$path = rtrim($path, DIRECTORY_SEPARATOR);
|
||||
|
||||
$skip_paths = array(".", "..");
|
||||
|
||||
while (($value = readdir($dh)) !== false) {
|
||||
if (!in_array($value, $skip_paths)) {
|
||||
if (is_dir($path . DIRECTORY_SEPARATOR . $value)) {
|
||||
$node_array[] = array(
|
||||
'text' => $value,
|
||||
'children' => true,
|
||||
'id' => UpdraftPlus_Manipulation_Functions::wp_normalize_path($path . DIRECTORY_SEPARATOR . $value),
|
||||
'icon' => 'jstree-folder'
|
||||
);
|
||||
} elseif (empty($params['directories_only']) && 'restore' != $page && is_file($path . DIRECTORY_SEPARATOR . $value)) {
|
||||
$node_array[] = array(
|
||||
'text' => $value,
|
||||
'children' => false,
|
||||
'id' => UpdraftPlus_Manipulation_Functions::wp_normalize_path($path . DIRECTORY_SEPARATOR . $value),
|
||||
'type' => 'file',
|
||||
'icon' => 'jstree-file'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$node_array['error'] = sprintf(__('Failed to open directory: %s.', 'updraftplus'), $path).' '.__('This is normally caused by file permissions.', 'updraftplus');
|
||||
}
|
||||
|
||||
return $node_array;
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates an array of nodes, built from a unzipped zip file structure.
|
||||
*
|
||||
* @param [array] $params this is an array of parameters sent via ajax it can include the following:
|
||||
* node - this is a jstree node object containing information about the selected node
|
||||
* timestamp - this is the backup timestamp and is used to get the backup archive
|
||||
* type - this is the type of backup and is used to get the backup archive
|
||||
* findex - this is the index used to get the correct backup archive if theres more than one of a single archive type
|
||||
* @return [array] returns an array of jstree nodes
|
||||
*/
|
||||
private function _updraft_jstree_zip($params) {
|
||||
|
||||
$updraftplus = $this->_updraftplus;
|
||||
|
||||
$node_array = array();
|
||||
|
||||
$zip_object = $updraftplus->get_zip_object_name();
|
||||
|
||||
// Retrieve the information from our backup history
|
||||
$backup_history = UpdraftPlus_Backup_History::get_history();
|
||||
|
||||
if (!isset($backup_history[$params['timestamp']][$params['type']])) {
|
||||
return array('error' => __('Backup set not found', 'updraftplus'));
|
||||
}
|
||||
|
||||
// Base name
|
||||
$file = $backup_history[$params['timestamp']][$params['type']];
|
||||
|
||||
// Get date in human readable form
|
||||
$pretty_date = get_date_from_gmt(gmdate('Y-m-d H:i:s', (int) $params['timestamp']), 'M d, Y G:i');
|
||||
|
||||
$backupable_entities = $updraftplus->get_backupable_file_entities(true, true);
|
||||
|
||||
// Check the file type and set the name in a more friendly way
|
||||
$archive_name = isset($backupable_entities[$params['type']]['description']) ? $backupable_entities[$params['type']]['description'] : $params['type'];
|
||||
|
||||
if (substr($params['type'], 0, 2) === 'db') $archive_name = __('Extra database', 'updraftplus') . ' ' . substr($params['type'], 3, 1);
|
||||
if ('db' == $params['type']) $archive_name = __('Database', 'updraftplus');
|
||||
if ('more' == $params['type']) $archive_name = $backupable_entities[$params['type']]['shortdescription'];
|
||||
if ('wpcore' == $params['type']) $archive_name = __('WordPress Core', 'updraftplus');
|
||||
|
||||
$archive_set = ($params['findex'] + 1) . '/' . sizeof($file);
|
||||
|
||||
if ('1/1' == $archive_set) $archive_set = '';
|
||||
|
||||
$parent_name = $archive_name . ' ' . __('archive', 'updraftplus') . ' ' . $archive_set . ' ' . $pretty_date;
|
||||
|
||||
// Deal with multi-archive sets
|
||||
if (is_array($file)) $file = $file[$params['findex']];
|
||||
|
||||
// Where it should end up being downloaded to
|
||||
$fullpath = $updraftplus->backups_dir_location().'/'.$file;
|
||||
|
||||
if (file_exists($fullpath) && is_readable($fullpath) && filesize($fullpath)>0) {
|
||||
|
||||
$node_array[] = array(
|
||||
'text' => $parent_name,
|
||||
'parent' => '#',
|
||||
'id' => $parent_name,
|
||||
'icon' => 'jstree-folder',
|
||||
'state' => array('opened' => true),
|
||||
'li_attr' => array('path' => $parent_name)
|
||||
);
|
||||
|
||||
$zip = new $zip_object;
|
||||
|
||||
$zip_opened = $zip->open($fullpath);
|
||||
|
||||
if (true !== $zip_opened) {
|
||||
return array('error' => 'UpdraftPlus: opening zip (' . $fullpath . '): failed to open this zip file (object='.$zip_object.', code: '.$zip_opened.')');
|
||||
} else {
|
||||
|
||||
$numfiles = $zip->numFiles;
|
||||
|
||||
if (false === $numfiles) return array('error' => 'UpdraftPlus: reading zip: '.$zip->last_error);
|
||||
|
||||
for ($i=0; $i < $numfiles; $i++) {
|
||||
$si = $zip->statIndex($i);
|
||||
|
||||
// Fix for windows being unable to build jstree due to different directory separators being used
|
||||
$si['name'] = str_replace("/", DIRECTORY_SEPARATOR, $si['name']);
|
||||
|
||||
// if it's a dot then we don't want to append this as it will break the ids and the tree structure
|
||||
if ('.' == dirname($si['name'])) {
|
||||
$node_id = $parent_name;
|
||||
} else {
|
||||
$node_id = $parent_name . DIRECTORY_SEPARATOR . dirname($si['name']) . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
$extension = substr(strrchr($si['name'], "."), 1);
|
||||
|
||||
if (0 == $si['size'] && empty($extension)) {
|
||||
$node_array[] = array(
|
||||
'text' => basename($si['name']),
|
||||
'parent' => $node_id,
|
||||
'id' => $parent_name . DIRECTORY_SEPARATOR . $si['name'],
|
||||
'icon' => 'jstree-folder',
|
||||
'li_attr' => array(
|
||||
'path' => $parent_name . DIRECTORY_SEPARATOR . $si['name']
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$node_array[] = array(
|
||||
'text' => basename($si['name']),
|
||||
'parent' => $node_id,
|
||||
'id' => $parent_name . DIRECTORY_SEPARATOR . $si['name'],
|
||||
'type' => 'file',
|
||||
'icon' => 'jstree-file',
|
||||
'li_attr' => array(
|
||||
'path' => $parent_name . DIRECTORY_SEPARATOR . $si['name'],
|
||||
'size' => UpdraftPlus_Manipulation_Functions::convert_numeric_size_to_text($si['size'])
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// check if this is an upload archive if it is add a 'uploads' folder so that the children can attach to it
|
||||
if ('uploads' == $params['type']) $node_array[] = array(
|
||||
'text' => 'uploads',
|
||||
'parent' => $parent_name,
|
||||
'id' => $parent_name . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR,
|
||||
'icon' => 'jstree-folder',
|
||||
'li_attr' => array(
|
||||
'path' => $parent_name . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR
|
||||
)
|
||||
);
|
||||
|
||||
@$zip->close();// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the method.
|
||||
}
|
||||
}
|
||||
|
||||
return $node_array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return information on the zipfile download
|
||||
*
|
||||
* @param Array $params - details on the download; keys: type, findex, path, timestamp
|
||||
*
|
||||
* @return Array
|
||||
*/
|
||||
public function get_zipfile_download($params) {
|
||||
return apply_filters('updraftplus_command_get_zipfile_download', array('error' => 'UpdraftPlus: command (get_zipfile_download) not installed (are you missing an add-on?)'), $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss the notice which will if .htaccess have any old migrated site reference.
|
||||
*
|
||||
* @return Boolean Return true if migration notice is dismissed
|
||||
*/
|
||||
public function dismiss_migration_notice_for_old_site_reference() {
|
||||
delete_site_option('updraftplus_migrated_site_domain');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* When character set and collate both are unsupported at restoration time and if user change anyone substitution dropdown from both, Other substitution select box value should be change respectively. To achieve this functionality, Ajax calls comes here.
|
||||
*
|
||||
* @param Array $params this is an array of parameters sent via ajax it can include the following:
|
||||
* collate_change_on_charset_selection_data - It is data in serialize form which is need for choose other dropdown option value. It contains below elements data:
|
||||
* db_supported_collations - All collations supported by current database. This is result of 'SHOW COLLATION' query
|
||||
* db_unsupported_collate_unique - Unsupported collates unique array
|
||||
* db_collates_found - All collates found in database backup file
|
||||
* event_source_elem - Dropdown elemtn id which trigger the ajax request
|
||||
* updraft_restorer_charset - Charset dropdown selected value option
|
||||
* updraft_restorer_collate - Collate dropdown selected value option
|
||||
*
|
||||
* @return array - $action_data which contains following data:
|
||||
* is_action_required - 1 or 0 Whether or not change other dropdown value
|
||||
* elem_id - Dropdown element id which value need to change. The other dropdown element id
|
||||
* elem_val - Dropdown element value which should be selected for other drodown
|
||||
*/
|
||||
public function collate_change_on_charset_selection($params) {
|
||||
$collate_change_on_charset_selection_data = json_decode(UpdraftPlus_Manipulation_Functions::wp_unslash($params['collate_change_on_charset_selection_data']), true);
|
||||
$updraft_restorer_collate = $params['updraft_restorer_collate'];
|
||||
$updraft_restorer_charset = $params['updraft_restorer_charset'];
|
||||
|
||||
$db_supported_collations = $collate_change_on_charset_selection_data['db_supported_collations'];
|
||||
$db_unsupported_collate_unique = $collate_change_on_charset_selection_data['db_unsupported_collate_unique'];
|
||||
$db_collates_found = $collate_change_on_charset_selection_data['db_collates_found'];
|
||||
|
||||
$action_data = array(
|
||||
'is_action_required' => 0,
|
||||
);
|
||||
// No need to change other dropdown value
|
||||
if (isset($db_supported_collations[$updraft_restorer_collate]->Charset) && $updraft_restorer_charset == $db_supported_collations[$updraft_restorer_collate]->Charset) {
|
||||
return $action_data;
|
||||
}
|
||||
$similar_type_collate = $this->_updraftplus->get_similar_collate_related_to_charset($db_supported_collations, $db_unsupported_collate_unique, $updraft_restorer_charset);
|
||||
if (empty($similar_type_collate)) {
|
||||
$similar_type_collate = $this->_updraftplus->get_similar_collate_based_on_ocuurence_count($db_collates_found, $db_supported_collations, $updraft_restorer_collate);
|
||||
}
|
||||
// Default collation for changed character set
|
||||
if (empty($similar_type_collate)) {
|
||||
$charset_row = $GLOBALS['wpdb']->get_row($GLOBALS['wpdb']->prepare("SHOW CHARACTER SET LIKE '%s'", $updraft_restorer_charset));
|
||||
if (null !== $charset_row && !empty($charset_row->{'Default collation'})) {
|
||||
$similar_type_collate = $charset_row->{'Default collation'};
|
||||
}
|
||||
}
|
||||
if (empty($similar_type_collate)) {
|
||||
foreach ($db_supported_collations as $db_supported_collation => $db_supported_collation_info) {
|
||||
if (isset($db_supported_collation_info->Charset) && $updraft_restorer_charset == $db_supported_collation_info->Charset) {
|
||||
$similar_type_collate = $db_supported_collation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($similar_type_collate)) {
|
||||
$action_data['is_action_required'] = 1;
|
||||
$action_data['similar_type_collate'] = $similar_type_collate;
|
||||
}
|
||||
return $action_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Tour status
|
||||
*
|
||||
* @param array $params - the $_REQUEST. We're looking for 'current_step'
|
||||
* @return bool
|
||||
*/
|
||||
public function set_tour_status($params) {
|
||||
return class_exists('UpdraftPlus_Tour') ? UpdraftPlus_Tour::get_instance()->set_tour_status($params) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the tour status
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function reset_tour_status() {
|
||||
return class_exists('UpdraftPlus_Tour') ? UpdraftPlus_Tour::get_instance()->reset_tour_status() : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the database information
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function db_size() {
|
||||
global $wpdb;
|
||||
|
||||
$db_table_res = $wpdb->get_results('SHOW TABLE STATUS', ARRAY_A);
|
||||
$db_table_size = 0;
|
||||
$db_table_html = '';
|
||||
|
||||
if ($wpdb->num_rows > 0) {
|
||||
$key_field_name = UpdraftPlus_Manipulation_Functions::backquote('Key');
|
||||
|
||||
foreach ($db_table_res as $row) {
|
||||
// Try search from transient
|
||||
$rows_count = get_transient('wpo_'.$row['Name'].'_count');
|
||||
if (false === $rows_count) {
|
||||
// If not found, try search primary key first
|
||||
$table_name = UpdraftPlus_Manipulation_Functions::backquote($row['Name']);
|
||||
$primary_key = $wpdb->get_row("SHOW COLUMNS FROM $table_name WHERE $key_field_name = 'PRI'", ARRAY_A);
|
||||
|
||||
if ($primary_key) {
|
||||
// Count rows by primary key
|
||||
$primary_key_field = UpdraftPlus_Manipulation_Functions::backquote($primary_key['Field']);
|
||||
$rows_count = $wpdb->get_var("SELECT COUNT($primary_key_field) FROM ".$table_name);
|
||||
}
|
||||
|
||||
if (is_null($rows_count) || false === $rows_count) $rows_count = $wpdb->get_var("SELECT COUNT(*) FROM ".$table_name);
|
||||
}
|
||||
|
||||
$db_table_html .= '<tr>';
|
||||
$db_table_html .= sprintf('<td>%s</td>', esc_html($row['Name']));
|
||||
$db_table_html .= sprintf('<td>%s</td>', esc_html($rows_count));
|
||||
$db_table_html .= sprintf('<td>%s</td>', esc_html(size_format($row['Data_length'], 2)));
|
||||
$db_table_html .= sprintf('<td>%s</td>', esc_html(size_format($row['Index_length'], 2)));
|
||||
$db_table_html .= sprintf('<td>%s</td>', esc_html($row['Engine']));
|
||||
$db_table_html .= '</tr>';
|
||||
|
||||
$db_table_size += $row['Data_length'] + $row['Index_length'];
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'size' => size_format((int) $db_table_size, 2),
|
||||
'html' => $db_table_html
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the scheduled UpdraftPlus cron events.
|
||||
*
|
||||
* This function fetches all cron events and filters those related to UpdraftPlus.
|
||||
* It then formats the schedule information, calculates the time difference,
|
||||
* and returns a structured array of cron details.
|
||||
*
|
||||
* @return array[] An array of cron event details, where each event contains:
|
||||
* - `overdue` (int): Whether the event is overdue (1) or not (0).
|
||||
* - `hook` (string): The name of the cron hook.
|
||||
* - `name` (string): The display name of the schedule.
|
||||
* - `time` (string): The formatted execution time.
|
||||
* - `interval` (string): The time remaining or overdue duration.
|
||||
*/
|
||||
public function get_cron_events() {
|
||||
$data = array();
|
||||
|
||||
$schedules = wp_get_schedules();
|
||||
$cron = _get_cron_array();
|
||||
|
||||
$date_format = get_option('date_format');
|
||||
$time_format = get_option('time_format');
|
||||
|
||||
// Loop through the cron schedules
|
||||
foreach ($cron as $timestamp => $cron_hooks) {
|
||||
foreach ($cron_hooks as $hook => $events) {
|
||||
if (!preg_match('/^updraft(_backup(_database|_resume|_increments)?|plus_clean_temporary_files)$/', $hook)) continue;
|
||||
|
||||
sort($events);
|
||||
|
||||
$schedule_name = $schedules[$events[0]['schedule']];
|
||||
|
||||
$formatted_date = wp_date($date_format. ' ' .$time_format, $timestamp);
|
||||
|
||||
$difference = $timestamp - current_time('timestamp', true);
|
||||
$difference_in_seconds = abs($difference);
|
||||
$overdue = $difference < 0 ? 1 : 0;
|
||||
|
||||
$hours = floor($difference_in_seconds / 3600);
|
||||
$minutes = floor(($difference_in_seconds % 3600) / 60);
|
||||
|
||||
if ($overdue) {
|
||||
// translators: 1: Number of hours, 2: Number of minutes
|
||||
$interval = sprintf(__('%1$d hours and %2$d minutes ago', 'updraftplus'), $hours, $minutes);
|
||||
} else {
|
||||
// translators: 1: Number of hours, 2: Number of minutes
|
||||
$interval = sprintf(__('%1$d hours and %2$d minutes', 'updraftplus'), $hours, $minutes);
|
||||
}
|
||||
|
||||
$data[] = array(
|
||||
'overdue' => $overdue,
|
||||
'hook' => $hook,
|
||||
'name' => $schedule_name['display'],
|
||||
'time' => $formatted_date,
|
||||
'interval' => $interval
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,552 @@
|
||||
<?php
|
||||
|
||||
if (!defined('ABSPATH')) die('No direct access allowed');
|
||||
|
||||
if (class_exists('ZipArchive')) :
|
||||
/**
|
||||
* We just add a last_error variable for comaptibility with our UpdraftPlus_PclZip object
|
||||
*/
|
||||
class UpdraftPlus_ZipArchive extends ZipArchive {
|
||||
|
||||
public $last_error = 'Unknown: ZipArchive does not return error messages';
|
||||
}
|
||||
endif;
|
||||
|
||||
/**
|
||||
* A ZipArchive compatibility layer, with behaviour sufficient for our usage of ZipArchive
|
||||
*/
|
||||
class UpdraftPlus_PclZip {
|
||||
|
||||
protected $pclzip;
|
||||
|
||||
protected $path;
|
||||
|
||||
protected $addfiles;
|
||||
|
||||
protected $adddirs;
|
||||
|
||||
private $statindex;
|
||||
|
||||
private $include_mtime = false;
|
||||
|
||||
public $last_error;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->addfiles = array();
|
||||
$this->adddirs = array();
|
||||
// Put this in a non-backed-up, writeable location, to make sure that huge temporary files aren't created and then added to the backup - and that we have somewhere writable
|
||||
global $updraftplus;
|
||||
if (!defined('PCLZIP_TEMPORARY_DIR')) define('PCLZIP_TEMPORARY_DIR', trailingslashit($updraftplus->backups_dir_location()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to include mtime in statindex (by default, not done - to save memory; probably a bit paranoid)
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function ud_include_mtime() {
|
||||
if (empty($this->include_mtime)) $this->statindex = null;
|
||||
$this->include_mtime = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic function for getting an otherwise-undefined class variable
|
||||
*
|
||||
* @param String $name
|
||||
*
|
||||
* @return Boolean|Null|Integer - the value, or null if an unknown variable, or false if something goes wrong
|
||||
*/
|
||||
public function __get($name) {
|
||||
|
||||
if ('numFiles' == $name) {
|
||||
|
||||
if (empty($this->pclzip)) return false;
|
||||
|
||||
if (!empty($this->statindex)) return count($this->statindex);
|
||||
|
||||
$statindex = $this->pclzip->listContent();
|
||||
|
||||
if (empty($statindex)) {
|
||||
$this->statindex = array();
|
||||
// We return a value that is == 0, but allowing a PclZip error to be detected (PclZip returns 0 in the case of an error).
|
||||
if (0 === $statindex) $this->last_error = $this->pclzip->errorInfo(true);
|
||||
return (0 === $statindex) ? false : 0;
|
||||
}
|
||||
|
||||
// We used to exclude folders in the case of numFiles (and implemented a private alternative, numAll, that included them), because we had no use for them (we ran a loop over $statindex to build a result that excluded the folders); but that is no longer the case (Dec 2018)
|
||||
$this->statindex = $statindex;
|
||||
|
||||
return count($this->statindex);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stat info for a file
|
||||
*
|
||||
* @param Integer $i The index of the file
|
||||
*
|
||||
* @return Array - the stat info
|
||||
*/
|
||||
public function statIndex($i) {
|
||||
if (empty($this->statindex[$i])) return array('name' => null, 'size' => 0);
|
||||
$v = array('name' => $this->statindex[$i]['filename'], 'size' => $this->statindex[$i]['size']);
|
||||
if ($this->include_mtime) $v['mtime'] = $this->statindex[$i]['mtime'];
|
||||
return $v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compatibility function for WP < 3.7; taken from WP 5.2.2
|
||||
*
|
||||
* @staticvar array $encodings
|
||||
* @staticvar bool $overloaded
|
||||
*
|
||||
* @param bool $reset - Whether to reset the encoding back to a previously-set encoding.
|
||||
*/
|
||||
private function mbstring_binary_safe_encoding($reset = false) {
|
||||
|
||||
if (function_exists('mbstring_binary_safe_encoding')) return mbstring_binary_safe_encoding($reset);
|
||||
|
||||
static $encodings = array();
|
||||
static $overloaded = null;
|
||||
|
||||
if (is_null($overloaded)) {
|
||||
$overloaded = function_exists('mb_internal_encoding') && (ini_get('mbstring.func_overload') & 2); // phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.mbstring_func_overloadDeprecated
|
||||
}
|
||||
|
||||
if (false === $overloaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$reset) {
|
||||
$encoding = mb_internal_encoding();
|
||||
array_push($encodings, $encoding);
|
||||
mb_internal_encoding('ISO-8859-1');
|
||||
}
|
||||
|
||||
if ($reset && $encodings) {
|
||||
$encoding = array_pop($encodings);
|
||||
mb_internal_encoding($encoding);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compatibility function for WP < 3.7
|
||||
*/
|
||||
private function reset_mbstring_encoding() {
|
||||
return function_exists('reset_mbstring_encoding') ? reset_mbstring_encoding() : $this->mbstring_binary_safe_encoding(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the entry contents using its index. This is used only in PclZip, to get better performance (i.e. no such method exists on other zip objects, so don't call it on them). The caller must be careful not to request more than will fit into available memory.
|
||||
*
|
||||
* @see https://php.net/manual/en/ziparchive.getfromindex.php
|
||||
*
|
||||
* @param Array $indexes - List of indexes for entries
|
||||
*
|
||||
* @return Boolean|Array - Returns a keyed list (keys matching $indexes) of contents of the entry on success or FALSE on failure.
|
||||
*/
|
||||
public function updraftplus_getFromIndexBulk($indexes) {
|
||||
|
||||
$results = array();
|
||||
|
||||
// This is just for crazy people with mbstring.func_overload enabled (deprecated from PHP 7.2)
|
||||
$this->mbstring_binary_safe_encoding();
|
||||
|
||||
$contents = $this->pclzip->extract(PCLZIP_OPT_BY_INDEX, $indexes, PCLZIP_OPT_EXTRACT_AS_STRING);
|
||||
|
||||
$this->reset_mbstring_encoding();
|
||||
|
||||
if (0 === $contents) {
|
||||
$this->last_error = $this->pclzip->errorInfo(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_array($contents)) {
|
||||
$this->last_error = 'PclZip::extract() did not return the expected information (1)';
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($contents as $item) {
|
||||
$index = $item['index'];
|
||||
$content = isset($item['content']) ? $item['content'] : '';
|
||||
$results[$index] = $content;
|
||||
}
|
||||
|
||||
return $results;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the entry contents using its index
|
||||
*
|
||||
* @see https://php.net/manual/en/ziparchive.getfromindex.php
|
||||
*
|
||||
* @param Integer $index - Index of the entry
|
||||
* @param Integer $length - The length to be read from the entry. If 0, then the entire entry is read.
|
||||
* @param Integer $flags - The flags to use to open the archive.
|
||||
*
|
||||
* @return String|Boolean - Returns the contents of the entry on success or FALSE on failure.
|
||||
*/
|
||||
public function getFromIndex($index, $length = 0, $flags = 0) {
|
||||
|
||||
$contents = $this->pclzip->extract(PCLZIP_OPT_BY_INDEX, array($index), PCLZIP_OPT_EXTRACT_AS_STRING);
|
||||
|
||||
if (0 === $contents) {
|
||||
$this->last_error = $this->pclzip->errorInfo(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
// This also prevents CI complaining about an unused parameter
|
||||
if ($flags) {
|
||||
error_log("A call to UpdraftPlus_PclZip::getFromIndex() set flags=$flags, but this is not implemented");
|
||||
}
|
||||
|
||||
if (!is_array($contents)) {
|
||||
$this->last_error = 'PclZip::extract() did not return the expected information (1)';
|
||||
return false;
|
||||
}
|
||||
|
||||
$content = array_pop($contents);
|
||||
|
||||
if (!isset($content['content'])) {
|
||||
$this->last_error = 'PclZip::extract() did not return the expected information (2)';
|
||||
return false;
|
||||
}
|
||||
|
||||
$results = $content['content'];
|
||||
|
||||
return $length ? substr($results, 0, $length) : $results;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a zip file
|
||||
*
|
||||
* @param String $path - the filesystem path to the zip file
|
||||
* @param Integer $flags - flags for the open operation (see ZipArchive::open() - N.B. may not all be implemented)
|
||||
*
|
||||
* @return Boolean - success or failure. Failure will set self::last_error
|
||||
*/
|
||||
public function open($path, $flags = 0) {
|
||||
|
||||
if (!class_exists('PclZip')) include_once(ABSPATH.'/wp-admin/includes/class-pclzip.php');
|
||||
if (!class_exists('PclZip')) {
|
||||
$this->last_error = "No PclZip class was found";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Route around PHP bug (exact version with the problem not known)
|
||||
$ziparchive_create_match = (version_compare(PHP_VERSION, '5.2.12', '>') && defined('ZIPARCHIVE::CREATE')) ? ZIPARCHIVE::CREATE : 1;
|
||||
|
||||
if ($flags == $ziparchive_create_match && file_exists($path)) @unlink($path);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise if the file doesn't exist.
|
||||
|
||||
$this->pclzip = new PclZip($path);
|
||||
|
||||
if (empty($this->pclzip)) {
|
||||
$this->last_error = 'Could not get a PclZip object';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make the empty directory we need to implement add_empty_dir()
|
||||
global $updraftplus;
|
||||
$updraft_dir = $updraftplus->backups_dir_location();
|
||||
if (!is_dir($updraft_dir.'/emptydir') && !mkdir($updraft_dir.'/emptydir')) {
|
||||
$this->last_error = "Could not create empty directory ($updraft_dir/emptydir)";
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->path = $path;
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the actual write-out - it is assumed that close() is where this is done. Needs to return true/false
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function close() {
|
||||
|
||||
if (empty($this->pclzip)) {
|
||||
$this->last_error = 'Zip file was not opened';
|
||||
return false;
|
||||
}
|
||||
|
||||
global $updraftplus;
|
||||
$updraft_dir = $updraftplus->backups_dir_location();
|
||||
|
||||
$activity = false;
|
||||
|
||||
// Add the empty directories
|
||||
foreach ($this->adddirs as $dir) {
|
||||
if (false == $this->pclzip->add($updraft_dir.'/emptydir', PCLZIP_OPT_REMOVE_PATH, $updraft_dir.'/emptydir', PCLZIP_OPT_ADD_PATH, $dir)) {
|
||||
$this->last_error = $this->pclzip->errorInfo(true);
|
||||
return false;
|
||||
}
|
||||
$activity = true;
|
||||
}
|
||||
|
||||
foreach ($this->addfiles as $rdirname => $adirnames) {
|
||||
foreach ($adirnames as $adirname => $files) {
|
||||
if (false == $this->pclzip->add($files, PCLZIP_OPT_REMOVE_PATH, $rdirname, PCLZIP_OPT_ADD_PATH, $adirname)) {
|
||||
$this->last_error = $this->pclzip->errorInfo(true);
|
||||
return false;
|
||||
}
|
||||
$activity = true;
|
||||
}
|
||||
unset($this->addfiles[$rdirname]);
|
||||
}
|
||||
|
||||
$this->pclzip = false;
|
||||
$this->addfiles = array();
|
||||
$this->adddirs = array();
|
||||
|
||||
clearstatcache();
|
||||
|
||||
if ($activity && filesize($this->path) < 50) {
|
||||
$this->last_error = "Write failed - unknown cause (check your file permissions)";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: basename($add_as) is irrelevant; that is, it is actually basename($file) that will be used. But these are always identical in our usage.
|
||||
*
|
||||
* @param string $file Specific file to add
|
||||
* @param string $add_as This is the name of the file that it is added as but it is usually the same as $file
|
||||
*/
|
||||
public function addFile($file, $add_as) {
|
||||
// Add the files. PclZip appears to do the whole (copy zip to temporary file, add file, move file) cycle for each file - so batch them as much as possible. We have to batch by dirname(). On a test with 1000 files of 25KB each in the same directory, this reduced the time needed on that directory from 120s to 15s (or 5s with primed caches).
|
||||
$rdirname = dirname($file);
|
||||
$adirname = dirname($add_as);
|
||||
$this->addfiles[$rdirname][$adirname][] = $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* PclZip doesn't have a direct way to do this
|
||||
*
|
||||
* @param string $dir Specific Directory to empty
|
||||
*/
|
||||
public function addEmptyDir($dir) {
|
||||
$this->adddirs[] = $dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a path
|
||||
*
|
||||
* @param String $path_to_extract
|
||||
* @param String $path
|
||||
*
|
||||
* @see http://www.phpconcept.net/pclzip/user-guide/55
|
||||
*
|
||||
* @return Array|Integer - either an array with the extracted files or an error. N.B. "If one file extraction fail, the full extraction does not fail. The method does not return an error, but the file status is set with the error reason."
|
||||
*/
|
||||
public function extract($path_to_extract, $path) {
|
||||
return $this->pclzip->extract(PCLZIP_OPT_PATH, $path_to_extract, PCLZIP_OPT_BY_NAME, $path);
|
||||
}
|
||||
}
|
||||
|
||||
class UpdraftPlus_BinZip extends UpdraftPlus_PclZip {
|
||||
|
||||
private $binzip;
|
||||
|
||||
private $symlink_reversals = array();
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
global $updraftplus_backup;
|
||||
$this->binzip = $updraftplus_backup->binzip;
|
||||
if (!is_string($this->binzip)) {
|
||||
$this->last_error = "No binary zip was found";
|
||||
return false;
|
||||
}
|
||||
return parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive a list of directory symlinks found, allowing their later reversal
|
||||
*
|
||||
* @param Array $symlink_reversals
|
||||
*/
|
||||
public function ud_notify_symlink_reversals($symlink_reversals) {
|
||||
$this->symlink_reversals = $symlink_reversals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a file to the zip
|
||||
*
|
||||
* @param String $file
|
||||
* @param String $add_as
|
||||
*/
|
||||
public function addFile($file, $add_as) {
|
||||
|
||||
global $updraftplus;
|
||||
|
||||
// If $file was reached through a symlink and has been dereferenced, then see if we can do anything about that.
|
||||
foreach ($this->symlink_reversals as $target => $link) {
|
||||
if (0 === strpos($file, $target)) {
|
||||
// Get the "within WP" path back so that we can eventually run "zip -@" from a directory where $add_as actually exists with its given path
|
||||
$file = UpdraftPlus_Manipulation_Functions::str_replace_once($target, $link, $file);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the directory that $add_as is relative to
|
||||
$base = UpdraftPlus_Manipulation_Functions::str_lreplace($add_as, '', $file);
|
||||
|
||||
// If the replacement operation has done nothing, i.e. if $file did not begin with $add_as
|
||||
if ($file == $base) {
|
||||
// Shouldn't happen; but see: https://bugs.php.net/bug.php?id=62119
|
||||
$updraftplus->log("File skipped due to unexpected name mismatch (locale: ".setlocale(LC_CTYPE, "0")."): file=$file add_as=$add_as", 'notice', false, true);
|
||||
} else {
|
||||
$rdirname = untrailingslashit($base);
|
||||
// Note: $file equals $rdirname/$add_as
|
||||
$this->addfiles[$rdirname][] = $add_as;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The standard zip binary cannot list; so we use PclZip for that
|
||||
* Do the actual write-out - it is assumed that close() is where this is done. Needs to return true/false
|
||||
*
|
||||
* @return Boolean - success or failure state
|
||||
*/
|
||||
public function close() {
|
||||
|
||||
if (empty($this->pclzip)) {
|
||||
$this->last_error = 'Zip file was not opened';
|
||||
return false;
|
||||
}
|
||||
|
||||
global $updraftplus, $updraftplus_backup;
|
||||
|
||||
// BinZip does not like zero-sized zip files
|
||||
if (file_exists($this->path) && 0 == filesize($this->path)) @unlink($this->path);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise if the file doesn't exist.
|
||||
|
||||
$descriptorspec = array(
|
||||
0 => array('pipe', 'r'),
|
||||
1 => array('pipe', 'w'),
|
||||
2 => array('pipe', 'w')
|
||||
);
|
||||
$exec = $this->binzip;
|
||||
if (defined('UPDRAFTPLUS_BINZIP_OPTS') && UPDRAFTPLUS_BINZIP_OPTS) $exec .= ' '.UPDRAFTPLUS_BINZIP_OPTS;
|
||||
$exec .= " -v -@ ".escapeshellarg($this->path);
|
||||
|
||||
$last_recorded_alive = time();
|
||||
$something_useful_happened = $updraftplus->something_useful_happened;
|
||||
$orig_size = file_exists($this->path) ? filesize($this->path) : 0;
|
||||
$last_size = $orig_size;
|
||||
clearstatcache();
|
||||
|
||||
$added_dirs_yet = false;
|
||||
|
||||
// If there are no files to add, but there are empty directories, then we need to make sure the directories actually get added
|
||||
if (0 == count($this->addfiles) && 0 < count($this->adddirs)) {
|
||||
$dir = realpath($updraftplus_backup->make_zipfile_source);
|
||||
$this->addfiles[$dir] = '././.';
|
||||
}
|
||||
// Loop over each destination directory name
|
||||
foreach ($this->addfiles as $rdirname => $files) {
|
||||
|
||||
$process = function_exists('proc_open') ? proc_open($exec, $descriptorspec, $pipes, $rdirname) : false;
|
||||
|
||||
if (!is_resource($process)) {
|
||||
$updraftplus->log('BinZip error: proc_open failed');
|
||||
$this->last_error = 'BinZip error: proc_open failed';
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$added_dirs_yet) {
|
||||
// Add the directories - (in fact, with binzip, non-empty directories automatically have their entries added; but it doesn't hurt to add them explicitly)
|
||||
foreach ($this->adddirs as $dir) {
|
||||
fwrite($pipes[0], $dir."/\n");
|
||||
}
|
||||
$added_dirs_yet = true;
|
||||
}
|
||||
|
||||
$read = array($pipes[1], $pipes[2]);
|
||||
$except = null;
|
||||
|
||||
if (!is_array($files) || 0 == count($files)) {
|
||||
fclose($pipes[0]);
|
||||
$write = array();
|
||||
} else {
|
||||
$write = array($pipes[0]);
|
||||
}
|
||||
|
||||
while ((!feof($pipes[1]) || !feof($pipes[2]) || (is_array($files) && count($files)>0)) && false !== @stream_select($read, $write, $except, 0, 200000)) {// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the call being interrupted by an incoming signal
|
||||
|
||||
if (is_array($write) && in_array($pipes[0], $write) && is_array($files) && count($files)>0) {
|
||||
$file = array_pop($files);
|
||||
// Send the list of files on stdin
|
||||
fwrite($pipes[0], $file."\n");
|
||||
if (0 == count($files)) fclose($pipes[0]);
|
||||
}
|
||||
|
||||
if (is_array($read) && in_array($pipes[1], $read)) {
|
||||
$w = fgets($pipes[1]);
|
||||
// Logging all this really slows things down; use debug to mitigate
|
||||
if ($w && $updraftplus_backup->debug) $updraftplus->log("Output from zip: ".trim($w), 'debug');
|
||||
if (time() > $last_recorded_alive + 5) {
|
||||
UpdraftPlus_Job_Scheduler::record_still_alive();
|
||||
$last_recorded_alive = time();
|
||||
}
|
||||
if (file_exists($this->path)) {
|
||||
$new_size = @filesize($this->path);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
|
||||
if (!$something_useful_happened && $new_size > $orig_size + 20) {
|
||||
UpdraftPlus_Job_Scheduler::something_useful_happened();
|
||||
$something_useful_happened = true;
|
||||
}
|
||||
clearstatcache();
|
||||
// Log when 20% bigger or at least every 50MB
|
||||
if ($new_size > $last_size*1.2 || $new_size > $last_size + 52428800) {
|
||||
$updraftplus->log(basename($this->path).sprintf(": size is now: %.2f MB", round($new_size/1048576, 1)));
|
||||
$last_size = $new_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($read) && in_array($pipes[2], $read)) {
|
||||
$last_error = fgets($pipes[2]);
|
||||
if (!empty($last_error)) $this->last_error = rtrim($last_error);
|
||||
}
|
||||
|
||||
// Re-set
|
||||
$read = array($pipes[1], $pipes[2]);
|
||||
$write = (is_array($files) && count($files) >0) ? array($pipes[0]) : array();
|
||||
$except = null;
|
||||
|
||||
}
|
||||
|
||||
fclose($pipes[1]);
|
||||
fclose($pipes[2]);
|
||||
|
||||
$ret = function_exists('proc_close') ? proc_close($process) : -1;
|
||||
|
||||
if (0 != $ret && 12 != $ret) {
|
||||
if ($ret < 128) {
|
||||
$updraftplus->log("Binary zip: error (code: $ret - look it up in the Diagnostics section of the zip manual at http://infozip.sourceforge.net/FAQ.html#error-codes for interpretation... and also check that your hosting account quota is not full)");
|
||||
} else {
|
||||
$updraftplus->log("Binary zip: error (code: $ret - a code above 127 normally means that the zip process was deliberately killed ... and also check that your hosting account quota is not full)");
|
||||
}
|
||||
if (!empty($w) && !$updraftplus_backup->debug) $updraftplus->log("Last output from zip: ".trim($w), 'debug');
|
||||
return false;
|
||||
}
|
||||
|
||||
unset($this->addfiles[$rdirname]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
/**
|
||||
* Custom Exceptions for the CloudFiles API
|
||||
*
|
||||
* Requires PHP 5.x (for Exceptions and OO syntax)
|
||||
*
|
||||
* See COPYING for license information.
|
||||
*
|
||||
* @author Eric "EJ" Johnson <ej@racklabs.com>
|
||||
* @copyright Copyright (c) 2008, Rackspace US, Inc.
|
||||
* @package php-cloudfiles-exceptions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Custom Exceptions for the CloudFiles API
|
||||
* @package php-cloudfiles-exceptions
|
||||
*/
|
||||
if (!class_exists('SyntaxException')) {
|
||||
class SyntaxException extends Exception { }
|
||||
}
|
||||
|
||||
if (!class_exists('AuthenticationException')) {
|
||||
class AuthenticationException extends Exception { }
|
||||
}
|
||||
|
||||
if (!class_exists('InvalidResponseException')) {
|
||||
class InvalidResponseException extends Exception { }
|
||||
}
|
||||
|
||||
if (!class_exists('NonEmptyContainerException')) {
|
||||
class NonEmptyContainerException extends Exception { }
|
||||
}
|
||||
|
||||
if (!class_exists('NoSuchObjectException')) {
|
||||
class NoSuchObjectException extends Exception { }
|
||||
}
|
||||
|
||||
if (!class_exists('NoSuchContainerException')) {
|
||||
class NoSuchContainerException extends Exception { }
|
||||
}
|
||||
|
||||
if (!class_exists('NoSuchAccountException')) {
|
||||
class NoSuchAccountException extends Exception { }
|
||||
}
|
||||
|
||||
if (!class_exists('MisMatchedChecksumException')) {
|
||||
class MisMatchedChecksumException extends Exception { }
|
||||
}
|
||||
|
||||
if (!class_exists('IOException')) {
|
||||
class IOException extends Exception { }
|
||||
}
|
||||
|
||||
if (!class_exists('CDNNotEnabledException')) {
|
||||
class CDNNotEnabledException extends Exception { }
|
||||
}
|
||||
|
||||
if (!class_exists('BadContentTypeException')) {
|
||||
class BadContentTypeException extends Exception { }
|
||||
}
|
||||
|
||||
if (!class_exists('InvalidUTF8Exception')) {
|
||||
class InvalidUTF8Exception extends Exception { }
|
||||
}
|
||||
|
||||
if (!class_exists('ConnectionNotOpenException')) {
|
||||
class ConnectionNotOpenException extends Exception { }
|
||||
}
|
||||
|
||||
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* tab-width: 4
|
||||
* c-basic-offset: 4
|
||||
* c-hanging-comment-ender-p: nil
|
||||
* End:
|
||||
*/
|
||||
?>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,239 @@
|
||||
<?php
|
||||
|
||||
if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed.');
|
||||
|
||||
/**
|
||||
* Adapted from http://www.solutionbot.com/2009/01/02/php-ftp-class/
|
||||
*/
|
||||
class UpdraftPlus_ftp_wrapper {
|
||||
|
||||
private $conn_id;
|
||||
|
||||
private $host;
|
||||
|
||||
private $username;
|
||||
|
||||
private $password;
|
||||
|
||||
private $port;
|
||||
|
||||
public $timeout = 60;
|
||||
|
||||
public $passive = true;
|
||||
|
||||
public $system_type = '';
|
||||
|
||||
public $ssl = false;
|
||||
|
||||
public $use_server_certs = false;
|
||||
|
||||
public $disable_verify = true;
|
||||
|
||||
public $login_type = 'non-encrypted';
|
||||
|
||||
public function __construct($host, $username, $password, $port = 21) {
|
||||
$this->host = $host;
|
||||
$this->username = $username;
|
||||
$this->password = $password;
|
||||
$this->port = $port;
|
||||
}
|
||||
|
||||
public function connect() {
|
||||
|
||||
$time_start = time();
|
||||
$this->conn_id = ftp_connect($this->host, $this->port, 20);
|
||||
|
||||
if ($this->conn_id) $result = ftp_login($this->conn_id, $this->username, $this->password);
|
||||
|
||||
if (!empty($result)) {
|
||||
ftp_set_option($this->conn_id, FTP_TIMEOUT_SEC, $this->timeout);
|
||||
ftp_pasv($this->conn_id, $this->passive);
|
||||
$this->system_type = ftp_systype($this->conn_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (time() - $time_start > 19) {
|
||||
global $updraftplus_admin;
|
||||
if (isset($updraftplus_admin->logged) && is_array($updraftplus_admin->logged)) {
|
||||
$updraftplus_admin->logged[] = sprintf(__('The %s connection timed out; if you entered the server correctly, then this is usually caused by a firewall blocking the connection - you should check with your web hosting company.', 'updraftplus'), 'FTP');
|
||||
} else {
|
||||
global $updraftplus;
|
||||
$updraftplus->log(sprintf(__('The %s connection timed out; if you entered the server correctly, then this is usually caused by a firewall blocking the connection - you should check with your web hosting company.', 'updraftplus'), 'FTP'), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function put($local_file_path, $remote_file_path, $mode = FTP_BINARY, $resume = false, $updraftplus = false) {
|
||||
|
||||
$file_size = filesize($local_file_path);
|
||||
|
||||
$existing_size = 0;
|
||||
if ($resume) {
|
||||
$existing_size = ftp_size($this->conn_id, $remote_file_path);
|
||||
if ($existing_size <=0) {
|
||||
$resume = false;
|
||||
$existing_size = 0;
|
||||
} else {
|
||||
if (is_a($updraftplus, 'UpdraftPlus')) $updraftplus->log("File already exists at remote site: size $existing_size. Will attempt resumption.");
|
||||
if ($existing_size >= $file_size) {
|
||||
if (is_a($updraftplus, 'UpdraftPlus')) $updraftplus->log("File is apparently already completely uploaded");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// From here on, $file_size is only used for logging calculations. We want to avoid division by zero.
|
||||
$file_size = max($file_size, 1);
|
||||
|
||||
if (!$fh = fopen($local_file_path, 'rb')) return false;
|
||||
if ($existing_size) fseek($fh, $existing_size);
|
||||
|
||||
$ret = ftp_nb_fput($this->conn_id, $remote_file_path, $fh, $mode, $existing_size);
|
||||
|
||||
// $existing_size can now be re-purposed
|
||||
|
||||
while (FTP_MOREDATA == $ret) {
|
||||
if (is_a($updraftplus, 'UpdraftPlus')) {
|
||||
$new_size = ftell($fh);
|
||||
$record_after = 524288;
|
||||
if ($existing_size > 2097152) {
|
||||
$record_after = ($existing_size > 4194304) ? 2097152 : 1048576;
|
||||
}
|
||||
if ($new_size - $existing_size > $record_after) {
|
||||
$existing_size = $new_size;
|
||||
$percent = round(100*$new_size/$file_size, 1);
|
||||
$updraftplus->record_uploaded_chunk($percent, '', $local_file_path);
|
||||
}
|
||||
}
|
||||
// Continue upload
|
||||
$ret = ftp_nb_continue($this->conn_id);
|
||||
}
|
||||
|
||||
fclose($fh);
|
||||
|
||||
if (FTP_FINISHED != $ret) {
|
||||
if (is_a($updraftplus, 'UpdraftPlus')) $updraftplus->log("FTP upload: error ($ret)");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public function get($local_file_path, $remote_file_path, $mode = FTP_BINARY, $resume = false, $updraftplus = false) {
|
||||
|
||||
$file_last_size = 0;
|
||||
|
||||
if ($resume) {
|
||||
if (!$fh = fopen($local_file_path, 'ab')) return false;
|
||||
clearstatcache($local_file_path);// phpcs:ignore PHPCompatibility.FunctionUse.NewFunctionParameters.clearstatcache_clear_realpath_cacheFound -- The function clearstatcache() does not have a parameter "clear_realpath_cache" in PHP version 5.2 or earlier
|
||||
$file_last_size = filesize($local_file_path);
|
||||
} else {
|
||||
if (!$fh = fopen($local_file_path, 'wb')) return false;
|
||||
}
|
||||
|
||||
$ret = ftp_nb_fget($this->conn_id, $fh, $remote_file_path, $mode, $file_last_size);
|
||||
|
||||
if (false == $ret) return false;
|
||||
|
||||
while (FTP_MOREDATA == $ret) {
|
||||
|
||||
if ($updraftplus) {
|
||||
$file_now_size = filesize($local_file_path);
|
||||
if ($file_now_size - $file_last_size > 524288) {
|
||||
$updraftplus->log("FTP fetch: file size is now: ".sprintf("%0.2f", filesize($local_file_path)/1048576)." Mb");
|
||||
$file_last_size = $file_now_size;
|
||||
}
|
||||
clearstatcache($local_file_path);// phpcs:ignore PHPCompatibility.FunctionUse.NewFunctionParameters.clearstatcache_clear_realpath_cacheFound -- The function clearstatcache() does not have a parameter "clear_realpath_cache" in PHP version 5.2 or earlier
|
||||
}
|
||||
|
||||
$ret = ftp_nb_continue($this->conn_id);
|
||||
}
|
||||
|
||||
fclose($fh);
|
||||
|
||||
if (FTP_FINISHED == $ret) {
|
||||
if ($updraftplus) $updraftplus->log("FTP fetch: fetch complete");
|
||||
return true;
|
||||
} else {
|
||||
if ($updraftplus) $updraftplus->log("FTP fetch: fetch failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function chmod($permissions, $remote_filename) {
|
||||
if ($this->is_octal($permissions)) {
|
||||
$result = ftp_chmod($this->conn_id, $permissions, $remote_filename);
|
||||
if ($result) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
throw new Exception('$permissions must be an octal number');
|
||||
}
|
||||
}
|
||||
|
||||
public function chdir($directory) {
|
||||
ftp_chdir($this->conn_id, $directory);
|
||||
}
|
||||
|
||||
public function delete($remote_file_path) {
|
||||
if (ftp_delete($this->conn_id, $remote_file_path)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function make_dir($directory) {
|
||||
if (ftp_mkdir($this->conn_id, $directory)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function rename($old_name, $new_name) {
|
||||
if (ftp_rename($this->conn_id, $old_name, $new_name)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function remove_dir($directory) {
|
||||
if (ftp_rmdir($this->conn_id, $directory)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function dir_list($directory) {
|
||||
return ftp_nlist($this->conn_id, $directory);
|
||||
}
|
||||
|
||||
public function cdup() {
|
||||
ftp_cdup($this->conn_id);
|
||||
}
|
||||
|
||||
public function size($f) {
|
||||
return ftp_size($this->conn_id, $f);
|
||||
}
|
||||
|
||||
public function current_dir() {
|
||||
return ftp_pwd($this->conn_id);
|
||||
}
|
||||
|
||||
private function is_octal($i) {
|
||||
return decoct(octdec($i)) == $i;
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
if ($this->conn_id) ftp_close($this->conn_id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#!/usr/local/bin/perl
|
||||
|
||||
use strict;
|
||||
use Env qw(UPDRAFTPLUSKEY);
|
||||
|
||||
if ($UPDRAFTPLUSKEY ne 'updraftplus') { die('Error'); }
|
||||
BEGIN { unshift @INC, '/usr/local/cpanel'; }
|
||||
|
||||
use Cpanel::Quota ();
|
||||
|
||||
# Used, limit, remain, files used, files limit, files remain
|
||||
my @homesize = ( Cpanel::Quota::displayquota( { 'bytes' => 1, 'include_sqldbs' => 1, 'include_mailman' => 1, }));
|
||||
print 'RESULT: '.join(" ", @homesize)."\n";
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user