PageRenderTime 46ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/administrator/components/com_admintools/engine/Postproc/Googlestoragejson.php

https://bitbucket.org/saltwaterdev/offshorefinancial.com
PHP | 418 lines | 255 code | 88 blank | 75 comment | 31 complexity | 2d13dd76c76dc36fa42463f77664761e MD5 | raw file
Possible License(s): GPL-2.0, GPL-3.0, 0BSD, MIT, Apache-2.0, LGPL-2.1
  1. <?php
  2. /**
  3. * Akeeba Engine
  4. * The modular PHP5 site backup engine
  5. *
  6. * @copyright Copyright (c)2006-2017 Nicholas K. Dionysopoulos / Akeeba Ltd
  7. * @license GNU GPL version 3 or, at your option, any later version
  8. * @package akeebaengine
  9. */
  10. namespace Akeeba\Engine\Postproc;
  11. // Protection against direct access
  12. defined('AKEEBAENGINE') or die();
  13. use Akeeba\Engine\Factory;
  14. use Akeeba\Engine\Platform;
  15. use Akeeba\Engine\Postproc\Connector\GoogleStorage as ConnectorGoogleStorage;
  16. use Psr\Log\LogLevel;
  17. class Googlestoragejson extends Base
  18. {
  19. /** @var int The retry count of this file (allow up to 2 retries after the first upload failure) */
  20. private $tryCount = 0;
  21. /** @var ConnectorGoogleStorage The Google Drive API instance */
  22. private $connector;
  23. /** @var string The currently configured bucket */
  24. private $bucket;
  25. /** @var string The currently configured directory */
  26. private $directory;
  27. /** @var bool Are we using chunk uploads? */
  28. private $chunked = false;
  29. /** @var int Chunk size (MB) */
  30. private $chunk_size = 10;
  31. /** @var array The decoded Google Cloud JSON configuration file */
  32. private $config = array();
  33. public function __construct()
  34. {
  35. $this->can_download_to_browser = false;
  36. $this->can_delete = true;
  37. $this->can_download_to_file = true;
  38. }
  39. /**
  40. * This function takes care of post-processing a backup archive's part, or the
  41. * whole backup archive if it's not a split archive type. If the process fails
  42. * it should return false. If it succeeds and the entirety of the file has been
  43. * processed, it should return true. If only a part of the file has been uploaded,
  44. * it must return 1.
  45. *
  46. * @param string $absolute_filename Absolute path to the part we'll have to process
  47. * @param string $upload_as Base name of the uploaded file, skip to use $absolute_filename's
  48. *
  49. * @return boolean|integer False on failure, true on success, 1 if more work is required
  50. */
  51. public function processPart($absolute_filename, $upload_as = null)
  52. {
  53. // Make sure we can get a connector object
  54. $validSettings = $this->initialiseConnector();
  55. if ($validSettings === false)
  56. {
  57. return false;
  58. }
  59. // Get a reference to the engine configuration
  60. $config = Factory::getConfiguration();
  61. // Store the absolute remote path in the class property
  62. $directory = $this->directory;
  63. $basename = empty($upload_as) ? basename($absolute_filename) : $upload_as;
  64. $this->remote_path = $directory . '/' . $basename;
  65. // Do not use multipart uploads when in an immediate post-processing step,
  66. // i.e. we are uploading a part right after its creation
  67. if ($this->chunked)
  68. {
  69. // Retrieve engine configuration data
  70. $config = Factory::getConfiguration();
  71. $immediateEnabled = $config->get('engine.postproc.common.after_part', 0);
  72. if ($immediateEnabled)
  73. {
  74. $this->chunked = false;
  75. }
  76. }
  77. // Are we already processing a multipart upload?
  78. if ($this->chunked)
  79. {
  80. Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . '::' . __METHOD__ . " - Using chunked upload, part size {$this->chunk_size}");
  81. $offset = $config->get('volatile.engine.postproc.googlestoragejson.offset', 0);
  82. $upload_id = $config->get('volatile.engine.postproc.googlestoragejson.upload_id', null);
  83. if (empty($upload_id))
  84. {
  85. Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . '::' . __METHOD__ . " - Creating new upload session");
  86. try
  87. {
  88. $upload_id = $this->connector->createUploadSession($this->bucket, $this->remote_path, $absolute_filename);
  89. }
  90. catch (\Exception $e)
  91. {
  92. $this->setWarning("The upload session for remote file {$this->remote_path} cannot be created. Debug info: #" . $e->getCode() . ' – ' . $e->getMessage());
  93. return false;
  94. }
  95. Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . '::' . __METHOD__ . " - New upload session $upload_id");
  96. $config->set('volatile.engine.postproc.googlestoragejson.upload_id', $upload_id);
  97. }
  98. try
  99. {
  100. if (empty($offset))
  101. {
  102. $offset = 0;
  103. }
  104. Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . '::' . __METHOD__ . " - Uploading chunked part");
  105. $result = $this->connector->uploadPart($upload_id, $absolute_filename, $offset, $this->chunk_size);
  106. Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . '::' . __METHOD__ . " - Got uploadPart result " . print_r($result, true));
  107. }
  108. catch (\Exception $e)
  109. {
  110. Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . '::' . __METHOD__ . " - Got uploadPart Exception " . $e->getCode() . ': ' . $e->getMessage());
  111. $this->setWarning($e->getMessage());
  112. $result = false;
  113. }
  114. // Did we fail uploading?
  115. if ($result === false)
  116. {
  117. // Let's retry
  118. $this->tryCount++;
  119. // However, if we've already retried twice, we stop retrying and call it a failure
  120. if ($this->tryCount > 2)
  121. {
  122. Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . '::' . __METHOD__ . " - Maximum number of retries exceeded. The upload has failed.");
  123. return false;
  124. }
  125. Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . '::' . __METHOD__ . " - Error detected, retrying chunk upload");
  126. return -1;
  127. }
  128. // Are we done uploading?
  129. clearstatcache();
  130. $totalSize = filesize($absolute_filename);
  131. $nextOffset = $offset + $this->chunk_size - 1;
  132. if (isset($result['name']) || ($nextOffset > $totalSize))
  133. {
  134. Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . '::' . __METHOD__ . " - Chunked upload is now complete");
  135. $config->set('volatile.engine.postproc.googlestoragejson.offset', null);
  136. $config->set('volatile.engine.postproc.googlestoragejson.upload_id', null);
  137. return true;
  138. }
  139. // Otherwise, continue uploading
  140. $config->set('volatile.engine.postproc.googlestoragejson.offset', $offset + $this->chunk_size);
  141. return -1;
  142. }
  143. // Single part upload
  144. try
  145. {
  146. Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . '::' . __METHOD__ . " - Performing simple upload.");
  147. $result = $this->connector->upload($this->bucket, $this->remote_path, $absolute_filename);
  148. }
  149. catch (\Exception $e)
  150. {
  151. Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . '::' . __METHOD__ . " - Simple upload failed, " . $e->getCode() . ": " . $e->getMessage());
  152. $this->setWarning($e->getMessage());
  153. $result = false;
  154. }
  155. if ($result === false)
  156. {
  157. // Let's retry
  158. $this->tryCount++;
  159. // However, if we've already retried twice, we stop retrying and call it a failure
  160. if ($this->tryCount > 2)
  161. {
  162. Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . '::' . __METHOD__ . " - Maximum number of retries exceeded. The upload has failed.");
  163. return false;
  164. }
  165. Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . '::' . __METHOD__ . " - Error detected, retrying upload");
  166. return -1;
  167. }
  168. // Upload complete. Reset the retry counter.
  169. $this->tryCount = 0;
  170. return true;
  171. }
  172. /**
  173. * Downloads a remote file to a local file, optionally doing a range download. If the
  174. * download fails we return false. If the download succeeds we return true. If range
  175. * downloads are not supported, -1 is returned and nothing is written to disk.
  176. *
  177. * @param $remotePath string The path to the remote file
  178. * @param $localFile string The absolute path to the local file we're writing to
  179. * @param $fromOffset int|null The offset (in bytes) to start downloading from
  180. * @param $length int|null The amount of data (in bytes) to download
  181. *
  182. * @return bool|int True on success, false on failure, -1 if ranges are not supported
  183. */
  184. public function downloadToFile($remotePath, $localFile, $fromOffset = null, $length = null)
  185. {
  186. // Get settings
  187. $settings = $this->initialiseConnector();
  188. if ($settings === false)
  189. {
  190. return false;
  191. }
  192. if (!is_null($fromOffset))
  193. {
  194. // Ranges are not supported
  195. return -1;
  196. }
  197. // Download the file
  198. try
  199. {
  200. $this->connector->download($this->bucket, $remotePath, $localFile);
  201. }
  202. catch (\Exception $e)
  203. {
  204. $this->setWarning($e->getMessage());
  205. return false;
  206. }
  207. return true;
  208. }
  209. public function delete($path)
  210. {
  211. // Get settings
  212. $settings = $this->initialiseConnector();
  213. if ($settings === false)
  214. {
  215. return false;
  216. }
  217. try
  218. {
  219. $this->connector->delete($this->bucket, $path, true);
  220. }
  221. catch (\Exception $e)
  222. {
  223. $this->setWarning($e->getMessage());
  224. return false;
  225. }
  226. return true;
  227. }
  228. /**
  229. * Initialises the Google Storage connector object
  230. *
  231. * @return bool True on success, false if we cannot proceed
  232. */
  233. protected function initialiseConnector()
  234. {
  235. // Retrieve engine configuration data
  236. $config = Factory::getConfiguration();
  237. if (!$this->canReadJsonConfig())
  238. {
  239. return false;
  240. }
  241. $this->chunked = $config->get('engine.postproc.googlestoragejson.chunk_upload', true);
  242. $this->chunk_size = $config->get('engine.postproc.googlestoragejson.chunk_upload_size', 10) * 1024 * 1024;
  243. $this->bucket = $config->get('engine.postproc.googlestoragejson.bucket', null);
  244. $this->directory = $config->get('volatile.postproc.directory', null);
  245. if (empty($this->directory))
  246. {
  247. $this->directory = $config->get('engine.postproc.googlestoragejson.directory', '');
  248. }
  249. // Environment checks
  250. if (!function_exists('curl_init'))
  251. {
  252. $this->setWarning('cURL is not enabled, please enable it in order to post-process your archives');
  253. return false;
  254. }
  255. if (!function_exists('openssl_sign') || !function_exists('openssl_get_md_methods'))
  256. {
  257. $this->setWarning('The PHP module for OpenSSL integration is not enabled or openssl_sign() is disabled. Please contact your host and ask them to fix this issue for the version of PHP you are currently using on your site (PHP reports itself as version ' . PHP_VERSION . ').');
  258. return false;
  259. }
  260. $openSSLAlgos = openssl_get_md_methods(true);
  261. if (!in_array('sha256WithRSAEncryption', $openSSLAlgos))
  262. {
  263. $this->setWarning('The PHP module for OpenSSL integration does not support the sha256WithRSAEncryption signature algorithm. Please ask your host to compile BOTH a newer version of the OpenSSL library AND the OpenSSL module for PHP against this (new) OpenSSL library for the version of PHP you are currently using on your site (PHP reports itself as version ' . PHP_VERSION . ').');
  264. return false;
  265. }
  266. // Fix the directory name, if required
  267. if (!empty($this->directory))
  268. {
  269. $this->directory = trim($this->directory);
  270. $this->directory = ltrim(Factory::getFilesystemTools()->TranslateWinPath($this->directory), '/');
  271. }
  272. else
  273. {
  274. $this->directory = '';
  275. }
  276. // Parse tags
  277. $this->directory = Factory::getFilesystemTools()->replace_archive_name_variables($this->directory);
  278. $config->set('volatile.postproc.directory', $this->directory);
  279. $this->connector = new ConnectorGoogleStorage($this->config['client_email'], $this->config['private_key']);
  280. return true;
  281. }
  282. /**
  283. * Tries to read the Google Cloud JSON credentials from the configuration. If something doesn't work out it will
  284. * return false.
  285. *
  286. * @return bool
  287. */
  288. protected function canReadJsonConfig()
  289. {
  290. $config = Factory::getConfiguration();
  291. $hasJsonConfig = false;
  292. $jsonConfig = trim($config->get('engine.postproc.googlestoragejson.jsoncreds', ''));
  293. if (!empty($jsonConfig))
  294. {
  295. $hasJsonConfig = true;
  296. $this->config = @json_decode($jsonConfig, true);
  297. }
  298. if (empty($this->config))
  299. {
  300. $hasJsonConfig = false;
  301. }
  302. if ($hasJsonConfig && (
  303. !isset($this->config['type']) ||
  304. !isset($this->config['project_id']) ||
  305. !isset($this->config['private_key']) ||
  306. !isset($this->config['client_email'])
  307. )
  308. )
  309. {
  310. $hasJsonConfig = false;
  311. }
  312. if ($hasJsonConfig && (
  313. ($this->config['type'] != 'service_account') ||
  314. (empty($this->config['project_id'])) ||
  315. (empty($this->config['private_key'])) ||
  316. (empty($this->config['client_email']))
  317. )
  318. )
  319. {
  320. $hasJsonConfig = false;
  321. }
  322. if (!$hasJsonConfig)
  323. {
  324. $this->config = array();
  325. $this->setWarning('You have not provided a valid Google Cloud JSON configuration (googlestorage.json) in the configuration page. As a result I cannot upload anything to Google Storage. Please fix this issue and try backing up again.');
  326. return false;
  327. }
  328. return true;
  329. }
  330. }