InfiniteWP Client - Version 1.8.1

Version Description

  • Apr 3rd 2018 =
  • Feature: New backup method introduced named Phoenix.
  • Improvement: Multicall method is implemented for Restore process.
  • Improvement: Backup Constants added for Phoenix method.
  • Improvement: Support for Wordfence Security plugin New version.
  • Improvement: Support for purging cache on WordPress sites.
  • Improvement: Phoenix backup now supports V4 AWS regions.
  • Improvement: WordFence and Itheme Security added for client reporting.
  • Improvement: Site FTP details will be used from the panel if any update require FTP details.
  • Improvement: Now you can create S3 buckets in the Paris region.
  • Fix: PHP fatal error while backing up your site to a SFTP server using the single call backup method.
  • Fix: Restore failed if the backup is placed inside a folder on your S3 bucket(Single call and Multicall).
  • Fix: Restore failed if backup files are splitted into part files (Single call and Multicall).
  • Fix: Undefined index: hook_suffix warning is fixed.
  • Fix: Compatibility with All In One Security.
  • Fix: PHP fatal errors due to incompatibility with few plugins like Cornerstone, Litespeed cache, Offers for WooCommerce etc.
  • Fix: Dropbox storage exceeds limit error showing incorrectly with "path error".
  • Fix: A Few security plugins block the calls to IWP Client plugin breaking the panel and site connection.
  • Fix: PHP Fatal error while backing up your site to your SFTP server using SIngle call method.
  • Fix: PHP Fatal error occurred: Uncaught Guzzle\Service\Exception\ValidationException: Validation errors: [Key] is a required string in while backing up the site using the multicall method.
Download this release

Release Info

Developer infinitewp
Plugin Icon 128x128 InfiniteWP Client
Version 1.8.1
Comparing to
See all releases

Code changes from version 1.6.6.3 to 1.8.1

Files changed (65) hide show
  1. activities_log.class.php +21 -1
  2. api.php → addons.api.php +4 -4
  3. addons/itheme_security/class-iwp-client-ithemes-security-class.php +10 -6
  4. addons/wordfence/wordfence.class.php +93 -4
  5. addons/wp_optimize/comet-cache-class.php +12 -0
  6. addons/wp_optimize/purge-plugins-cache-class.php +261 -0
  7. backup.class.multicall.php +241 -33
  8. backup.class.singlecall.php +61 -13
  9. backup/backup.core.class.php +4878 -0
  10. backup/backup.options.php +26 -0
  11. backup/backup.php +2935 -0
  12. backup/backup.upload.php +294 -0
  13. backup/backup.zip.class.php +342 -0
  14. backup/class.semaphore.php +188 -0
  15. backup/dropbox.php +525 -0
  16. backup/ftp.php +277 -0
  17. backup/functions.php +147 -0
  18. backup/googledrive.php +787 -0
  19. backup/s3.php +899 -0
  20. backup/sftp.php +23 -0
  21. core.class.php +114 -22
  22. helper.class.php +50 -22
  23. init.php +497 -127
  24. installer.class.php +76 -22
  25. lib/Dropbox/API.php +802 -0
  26. lib/Dropbox/Exception.php +31 -0
  27. lib/Dropbox/OAuth/Consumer/ConsumerAbstract.php +371 -0
  28. lib/Dropbox/OAuth/Consumer/Curl.php +290 -0
  29. lib/{Dropbox2 → Dropbox}/OAuth/Consumer/ca-bundle.pem +0 -0
  30. lib/Dropbox2/API.php +661 -801
  31. lib/Dropbox2/Exception.php +7 -10
  32. lib/Dropbox2/OAuth/Consumer/ConsumerAbstract.php +445 -327
  33. lib/Dropbox2/OAuth/Consumer/Curl.php +277 -277
  34. lib/Dropbox2/OAuth/Consumer/WordPress.php +80 -0
  35. lib/Dropbox2/OAuth/Storage/Encrypter.php +109 -0
  36. lib/Dropbox2/OAuth/Storage/StorageInterface.php +30 -0
  37. lib/Dropbox2/OAuth/Storage/WordPress.php +195 -0
  38. lib/Google2/Auth/Abstract.php +38 -0
  39. lib/Google2/Auth/AppIdentity.php +120 -0
  40. lib/Google2/Auth/AssertionCredentials.php +136 -0
  41. lib/Google2/Auth/ComputeEngine.php +146 -0
  42. lib/Google2/Auth/Exception.php +24 -0
  43. lib/Google2/Auth/LoginTicket.php +71 -0
  44. lib/Google2/Auth/OAuth2.php +632 -0
  45. lib/Google2/Auth/Simple.php +63 -0
  46. lib/Google2/Cache/Abstract.php +53 -0
  47. lib/Google2/Cache/Apc.php +113 -0
  48. lib/Google2/Cache/Exception.php +24 -0
  49. lib/Google2/Cache/File.php +206 -0
  50. lib/Google2/Cache/Memcache.php +184 -0
  51. lib/Google2/Cache/Null.php +57 -0
  52. lib/Google2/Client.php +712 -0
  53. lib/Google2/Collection.php +101 -0
  54. lib/Google2/Config.php +452 -0
  55. lib/Google2/Exception.php +20 -0
  56. lib/Google2/Http/Batch.php +143 -0
  57. lib/Google2/Http/CacheParser.php +185 -0
  58. lib/Google2/Http/MediaFileUpload.php +306 -0
  59. lib/Google2/Http/REST.php +179 -0
  60. lib/Google2/Http/Request.php +504 -0
  61. lib/Google2/IO/Abstract.php +339 -0
  62. lib/Google2/IO/Curl.php +181 -0
  63. lib/Google2/IO/Exception.php +69 -0
  64. lib/Google2/IO/Stream.php +275 -0
  65. lib/Google2/IO/cacerts.pem +1842 -0
activities_log.class.php CHANGED
@@ -591,6 +591,8 @@ class IWP_MMB_Activities_log {
591
  $to_key = 'to';
592
  $translations_updated = 'translations-updated';
593
  $sucuri = 'sucuri';
 
 
594
 
595
  if(
596
  !is_array($params['originalActions'])
@@ -690,6 +692,20 @@ class IWP_MMB_Activities_log {
690
  }
691
  }
692
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
693
  iwp_mmb_response($return, true);
694
  }
695
 
@@ -711,7 +727,7 @@ class IWP_MMB_Activities_log {
711
  }
712
 
713
  function iwp_mmb_register_custom_post_type(){
714
- register_post_type('iwp-log');
715
  }
716
  function iwp_mmb_save_sucuri_activity_log(){
717
  $object = new IWP_MMB_Sucuri();
@@ -722,6 +738,10 @@ class IWP_MMB_Activities_log {
722
  $this->iwp_mmb_save_iwp_activities('sucuri', 'scan', 'automatic',$info, $userid);
723
  }
724
  }
 
 
 
 
725
  }
726
 
727
  if(!function_exists('iwp_make_values_as_zero')) {
591
  $to_key = 'to';
592
  $translations_updated = 'translations-updated';
593
  $sucuri = 'sucuri';
594
+ $ithemesec = 'ithemesec';
595
+ $wordfence = 'wordfence';
596
 
597
  if(
598
  !is_array($params['originalActions'])
692
  }
693
  }
694
  }
695
+ if (in_array($ithemesec, $params['actions']) && iwp_mmb_ithemes_security_check()) {
696
+ global $iwp_mmb_core;
697
+ $ithemessec_instance = $iwp_mmb_core->get_ithemessec_instance();
698
+ $logCounts = $ithemessec_instance->getLogCounts($params['fromDate'], $params['toDate']);
699
+ $return['detailed'][$ithemesec]['details'] = $logCounts;
700
+ }
701
+
702
+ if (in_array($wordfence, $params['actions']) && iwp_mmb_is_wordfence()) {
703
+ global $iwp_mmb_core;
704
+ require_once($GLOBALS['iwp_mmb_plugin_dir'] . "/addons/wordfence/wordfence.class.php");
705
+ $wordfence_instance = $iwp_mmb_core->get_wordfence_instance();
706
+ $logCounts = $wordfence_instance->getLogCounts($params['fromDate'], $params['toDate']);
707
+ $return['detailed'][$wordfence]['details'] = $logCounts;
708
+ }
709
  iwp_mmb_response($return, true);
710
  }
711
 
727
  }
728
 
729
  function iwp_mmb_register_custom_post_type(){
730
+ register_post_type('iwp-log', array('label' => 'IWP Log'));
731
  }
732
  function iwp_mmb_save_sucuri_activity_log(){
733
  $object = new IWP_MMB_Sucuri();
738
  $this->iwp_mmb_save_iwp_activities('sucuri', 'scan', 'automatic',$info, $userid);
739
  }
740
  }
741
+
742
+ function iwp_mmb_backup_complete(){
743
+ return true;
744
+ }
745
  }
746
 
747
  if(!function_exists('iwp_make_values_as_zero')) {
api.php → addons.api.php RENAMED
@@ -14,9 +14,9 @@
14
  * Copyright (c) 2011 Prelovac Media
15
  * www.prelovac.com
16
  **************************************************************/
17
- if(basename($_SERVER['SCRIPT_FILENAME']) == "api.php"):
18
- exit;
19
- endif;
20
  if( !function_exists('iwp_mmb_add_action')) :
21
  function iwp_mmb_add_action($action = false, $callback = false)
22
  {
@@ -37,7 +37,7 @@ if( !function_exists('iwp_mmb_function_exists') ) :
37
  function iwp_mmb_function_exists($callback)
38
  {
39
  global $iwp_core;
40
- if (count($callback) === 2) {
41
  if (!method_exists($callback[0], $callback[1]))
42
  wp_die($iwp_core->iwp_dashboard_widget('Information', '', '<p>Class ' . get_class($callback[0]) . ' does not contain <b>"' . $callback[1] . '"</b> function</p>', '', '50%'));
43
  } else {
14
  * Copyright (c) 2011 Prelovac Media
15
  * www.prelovac.com
16
  **************************************************************/
17
+ if ( ! defined('ABSPATH') )
18
+ die();
19
+
20
  if( !function_exists('iwp_mmb_add_action')) :
21
  function iwp_mmb_add_action($action = false, $callback = false)
22
  {
37
  function iwp_mmb_function_exists($callback)
38
  {
39
  global $iwp_core;
40
+ if (!is_string($callback) && !empty($callback) && count($callback) === 2) {
41
  if (!method_exists($callback[0], $callback[1]))
42
  wp_die($iwp_core->iwp_dashboard_widget('Information', '', '<p>Class ' . get_class($callback[0]) . ' does not contain <b>"' . $callback[1] . '"</b> function</p>', '', '50%'));
43
  } else {
addons/itheme_security/class-iwp-client-ithemes-security-class.php CHANGED
@@ -14,12 +14,16 @@ final class IWP_MMB_IThemes_Security extends IWP_MMB_Core {
14
  self::checkIThemesModules();
15
  }
16
 
17
- function getLogCounts(){
18
- $from = date('Y-m-d h:i:s', strtotime('yesterday'));
19
- $to = date('Y-m-d H:i:s', time());
20
- $return['file_change'] = self::getFileChangeHistory($from, $to);
21
- $return['four_oh_four'] = self::getFourOhFourHistory($from, $to);
22
- $return['brute_force'] = self::getBruteForceHistory($from, $to);
 
 
 
 
23
  return $return;
24
  }
25
 
14
  self::checkIThemesModules();
15
  }
16
 
17
+ function getLogCounts($from = false, $to = false ){
18
+ if (!$from && !$to) {
19
+ $from = strtotime('yesterday');
20
+ $to = time();
21
+ }
22
+ $from = date('Y-m-d h:i:s', $from);
23
+ $to = date('Y-m-d H:i:s', $to);
24
+ $return['itheme_file_change'] = self::getFileChangeHistory($from, $to);
25
+ $return['itheme_four_oh_four'] = self::getFourOhFourHistory($from, $to);
26
+ $return['itheme_brute_force'] = self::getBruteForceHistory($from, $to);
27
  return $return;
28
  }
29
 
addons/wordfence/wordfence.class.php CHANGED
@@ -14,8 +14,7 @@ class IWP_WORDFENCE extends IWP_MMB_Core
14
  */
15
  function load() {
16
  if($this->_checkWordFence()) {
17
-
18
- if(wfUtils::isScanRunning()){
19
  return array('scan'=>'yes');
20
  } else {
21
  return wordfence::ajax_loadIssues_callback();
@@ -25,6 +24,96 @@ class IWP_WORDFENCE extends IWP_MMB_Core
25
  }
26
  }
27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  /*
29
  * Start the new scan on WordFence
30
  */
@@ -37,9 +126,9 @@ class IWP_WORDFENCE extends IWP_MMB_Core
37
  }
38
 
39
  /*
40
- * Private function, Will return the wordfence is load or not
41
  */
42
- private function _checkWordFence() {
43
  include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
44
  if ( is_plugin_active( 'wordfence/wordfence.php' ) ) {
45
  @include_once(WP_PLUGIN_DIR . '/wordfence/wordfence.php');
14
  */
15
  function load() {
16
  if($this->_checkWordFence()) {
17
+ if(wfConfig::get('wf_scanRunning')){
 
18
  return array('scan'=>'yes');
19
  } else {
20
  return wordfence::ajax_loadIssues_callback();
24
  }
25
  }
26
 
27
+
28
+ /*
29
+ * Get Log count from WordFence
30
+ */
31
+
32
+ function getLogCounts($from = false, $to = false ){
33
+ if (!$from && !$to) {
34
+ $from = strtotime('yesterday');
35
+ $to = time();
36
+ }
37
+
38
+ $return['wordfence_humans'] = self::getGivenLogHistory('humans', $from, $to);
39
+ $return['wordfence_registered_users'] = self::getGivenLogHistory('registered_users', $from, $to);
40
+ $return['wordfence_crawlers'] = self::getGivenLogHistory('crawlers', $from, $to);
41
+ $return['wordfence_google_crawlers'] = self::getGivenLogHistory('google_crawlers', $from, $to);
42
+ $return['wordfence_four_oh_four'] = self::getGivenLogHistory('four_oh_four', $from, $to);
43
+ $return['wordfence_logins_logouts'] = self::getGivenLogHistory('logins_logouts', $from, $to);
44
+ $return['wordfence_locked_out'] = self::getGivenLogHistory('locked_out', $from, $to);
45
+ $return['wordfence_blocked'] = self::getGivenLogHistory('blocked', $from, $to);
46
+ $return['wordfence_blocked_firewall'] = self::getGivenLogHistory('blocked_firewall', $from, $to);
47
+ return $return;
48
+ }
49
+
50
+ /*
51
+ * Get Humans history
52
+ */
53
+ function getGivenLogHistory($type, $from = false, $to = false){
54
+ global $wpdb;
55
+ $query = '';
56
+ switch ($type) {
57
+ case 'humans':
58
+ $query = "SELECT count('id')
59
+ FROM ".$wpdb->base_prefix."wfHits
60
+ WHERE jsRun = 1 AND ctime >= '$from' AND ctime <= '$to'";
61
+ break;
62
+
63
+ case 'registered_users':
64
+ $query = "SELECT count('id')
65
+ FROM ".$wpdb->base_prefix."wfHits
66
+ WHERE userID > 0 AND ctime >= '$from' AND ctime <= '$to'";
67
+ break;
68
+ case 'crawlers':
69
+ $query = "SELECT count('id')
70
+ FROM ".$wpdb->base_prefix."wfHits
71
+ WHERE jsRun = 0 AND ctime >= '$from' AND ctime <= '$to'";
72
+ break;
73
+
74
+ case 'google_crawlers':
75
+ $query = "SELECT count('id')
76
+ FROM ".$wpdb->base_prefix."wfHits
77
+ WHERE isGoogle = 1 AND ctime >= '$from' AND ctime <= '$to'";
78
+ break;
79
+ case 'four_oh_four':
80
+ $query = "SELECT count('id')
81
+ FROM ".$wpdb->base_prefix."wfHits
82
+ WHERE statusCode = '404' AND ctime >= '$from' AND ctime <= '$to'";
83
+ break;
84
+ case 'logins_logouts':
85
+ $query = "SELECT count('id')
86
+ FROM ".$wpdb->base_prefix."wfLogins
87
+ WHERE ctime >= '$from' AND ctime <= '$to'";
88
+ break;
89
+ case 'locked_out':
90
+ $query = "SELECT count('IP')
91
+ FROM ".$wpdb->base_prefix."wfLockedOut
92
+ WHERE blockedTime >= '$from' AND `blockedTime` <= '$to'";
93
+ break;
94
+ case 'blocked':
95
+ $query = "SELECT count('id')
96
+ FROM ".$wpdb->base_prefix."wfHits
97
+ WHERE action = 'blocked:wordfence' AND ctime >= '$from' AND ctime <= '$to'";
98
+ break;
99
+ case 'blocked_firewall':
100
+ $query = "SELECT count('id')
101
+ FROM ".$wpdb->base_prefix."wfHits
102
+ WHERE action = 'blocked:waf' AND ctime >= '$from' AND ctime <= '$to'";
103
+ break;
104
+
105
+ default:
106
+ return 0;
107
+
108
+ }
109
+ $count = $wpdb->get_var( $query );
110
+ if (empty($count)) {
111
+ return 0;
112
+ }
113
+ return $count;
114
+
115
+ }
116
+
117
  /*
118
  * Start the new scan on WordFence
119
  */
126
  }
127
 
128
  /*
129
+ * Will return the wordfence is load or not
130
  */
131
+ function _checkWordFence() {
132
  include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
133
  if ( is_plugin_active( 'wordfence/wordfence.php' ) ) {
134
  @include_once(WP_PLUGIN_DIR . '/wordfence/wordfence.php');
addons/wp_optimize/comet-cache-class.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ use WebSharks\CometCache\Classes\ApiBase;
4
+ function clearCometCacheIWP(){
5
+ $api = new ApiBase();
6
+ $plugin = $api->plugin(true);
7
+ $response = $plugin->clearCache(true);
8
+ if ($response === false) {
9
+ return array('error' => 'Unable to perform Comet cache', 'error_code' => 'comet_cache_plugin_delete_cache');
10
+ }
11
+ return array('success' => 'All cache files have been deleted');
12
+ }
addons/wp_optimize/purge-plugins-cache-class.php ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if ( ! defined('ABSPATH') )
4
+ die();
5
+
6
+ class IWP_MMB_PURGE_CACHE extends IWP_MMB_Core
7
+ {
8
+
9
+ function __construct()
10
+ {
11
+ parent::__construct();
12
+ }
13
+ /*
14
+ * Public function, Will Clear all cache plugins
15
+ */
16
+ public function purgeAllCache($params = array()){
17
+ $cleanup_values = array();
18
+ $cleanup_values['value_array'] = array();
19
+ $text = '';
20
+ if (empty($params) || isset($params['wpfc_cache'])) {
21
+ $response = $this->deleteALLWPFCCache();
22
+ if (!empty($response['success'])) {
23
+ $text .= "<span class='wpm_results_db'> WP Fastest Cache"." : " . $response['success'] . "</span><br>";
24
+ $cleanup_values['value_array']['wpfc_cache'] = $values['value'];
25
+ }elseif(!empty($response['error'])){
26
+ $text .= "<span class='wpm_results_db'> WP Fastest Cache"." : " . $response['error'] . "</span><br>";
27
+ $cleanup_values['value_array']['wpfc_cache'] = 'WP Fastest Cache';
28
+ }
29
+ }
30
+ if (empty($params) || isset($params['wp_super_cache'])) {
31
+ $response = $this->deleteALLWPSuperCache();
32
+ if (!empty($response['success'])) {
33
+ $text .= "<span class='wpm_results_db'> WP Super Cache"." : " . $response['success'] . "</span><br>";
34
+ $cleanup_values['value_array']['wp_super_cache'] = $values['value'];
35
+ }elseif(!empty($response['error'])){
36
+ $text .= "<span class='wpm_results_db'> WP Super Cache"." : " . $response['error'] . "</span><br>";
37
+ $cleanup_values['value_array']['wp_super_cache'] = 'WP Super Cache';
38
+ }
39
+ }
40
+ if (empty($params) || isset($params['w3_total_cache'])) {
41
+ $response = $this->deleteALLW3TotalCache();
42
+ if (!empty($response['success'])) {
43
+ $text .= "<span class='wpm_results_db'> W3 Total Cache"." : " . $response['success'] . "</span><br>";
44
+ $cleanup_values['value_array']['w3_total_cache'] = $values['value'];
45
+ }elseif(!empty($response['error'])){
46
+ $text .= "<span class='wpm_results_db'> W3 Total Cache"." : " . $response['error'] . "</span><br>";
47
+ $cleanup_values['value_array']['w3_total_cache'] = 'W3 Total Cache';
48
+ }
49
+ }
50
+ if (empty($params) || isset($params['wp_rocket_cache'])) {
51
+ $response = $this->deleteALLWPRocketCache();
52
+ if (!empty($response['success'])) {
53
+ $text .= "<span class='wpm_results_db'> WP Rocket Cache"." : " . $response['success'] . "</span><br>";
54
+ $cleanup_values['value_array']['wp_rocket_cache'] = $values['value'];
55
+ }elseif(!empty($response['error'])){
56
+ $text .= "<span class='wpm_results_db'> WP Rocket Cache"." : " . $response['error'] . "</span><br>";
57
+ $cleanup_values['value_array']['wp_rocket_cache'] = 'WP Rocket Cache';
58
+ }
59
+ }
60
+ if (empty($params) || isset($params['comet_cache'])) {
61
+ $response = $this->deleteALLCometCache();
62
+ if (!empty($response['success'])) {
63
+ $text .= "<span class='wpm_results_db'> Comet Cache"." : " . $response['success'] . "</span><br>";
64
+ $cleanup_values['value_array']['comet_cache'] = $values['value'];
65
+ }elseif(!empty($response['error'])){
66
+ $text .= "<span class='wpm_results_db'> Comet Cache"." : " . $response['error'] . "</span><br>";
67
+ $cleanup_values['value_array']['comet_cache'] = 'Comet Cache';
68
+ }
69
+ }
70
+
71
+ if ($text !==''){
72
+ $cleanup_values['message'] = $text;
73
+ return $cleanup_values;
74
+ }
75
+ }
76
+ /*
77
+ * Public function, Will return the WpFastestCache is loaded or not
78
+ */
79
+
80
+ public function checkWPFCPlugin() {
81
+ include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
82
+ if ( is_plugin_active( 'wp-fastest-cache/wpFastestCache.php' ) ) {
83
+ @include_once(WP_PLUGIN_DIR . '/wp-fastest-cache/wpFastestCache.php');
84
+ if (class_exists('WpFastestCache')) {
85
+ return true;
86
+ }
87
+ }
88
+ return false;
89
+ }
90
+
91
+ /*
92
+ * Public function, Will return the WP Super cache Plugin is loaded or not
93
+ */
94
+
95
+ public function checkWPSuperCachePlugin() {
96
+ include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
97
+ if ( is_plugin_active( 'wp-super-cache/wp-cache.php' ) ) {
98
+ @include_once(WP_PLUGIN_DIR . '/wp-super-cache/wp-cache.php');
99
+ if (function_exists('wp_cache_clean_cache')) {
100
+ return true;
101
+ }
102
+ }
103
+ return false;
104
+ }
105
+
106
+ /*
107
+ * Public function, Will return the W3 Total cache Plugin is loaded or not
108
+ */
109
+
110
+ public function checkW3TotalCachePlugin() {
111
+ include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
112
+ if ( is_plugin_active( 'w3-total-cache/w3-total-cache.php' ) ) {
113
+ @include_once(WP_PLUGIN_DIR . '/w3-total-cache/w3-total-cache.php');
114
+ if (function_exists('w3tc_flush_all')) {
115
+ return true;
116
+ }
117
+ }
118
+ return false;
119
+ }
120
+
121
+ /*
122
+ * Public function, Will return the Comet cache plugin is loaded or not
123
+ */
124
+
125
+ public function checkCometCachePlugin() {
126
+ include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
127
+ if ( is_plugin_active( 'comet-cache/comet-cache.php' ) ) {
128
+ // @include_once(WP_PLUGIN_DIR . '/comet-cache/comet-cache.php');
129
+ //if (class_exists('ApiBase')) {
130
+ return true;
131
+ // }
132
+ }
133
+ return false;
134
+ }
135
+
136
+ /*
137
+ * Public function, Will return the Comet cache plugin is loaded or not
138
+ */
139
+
140
+ public function checkWPRocketPlugin() {
141
+ include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
142
+ if ( is_plugin_active( 'wp-rocket/wp-rocket.php' ) ) {
143
+ @include_once(WP_PLUGIN_DIR . '/wp-rocket/wp-rocket.php');
144
+ if (function_exists('rocket_clean_domain') && function_exists('rocket_clean_minify') && function_exists('rocket_clean_cache_busting') && function_exists('create_rocket_uniqid')) {
145
+ return true;
146
+ }
147
+ }
148
+ return false;
149
+ }
150
+
151
+ /*
152
+ * This function will delete all cache files for WP Fastest Plugin
153
+ */
154
+
155
+ public function deleteALLWPFCCache(){
156
+ if($this->checkWPFCPlugin()) {
157
+ $wpfc = new IWP_MMB_WPFC_CACHE();
158
+ $wpfc->deleteMinifiedCache();
159
+ $response = $wpfc->_getSystemMessage();
160
+ if ($response[1] == 'error') {
161
+ return array('error' => $response[0], 'error_code' => 'wpfc_plugin_delete_cache');
162
+ }elseif($response[1] == 'success'){
163
+ return array('success' => $response[0]);
164
+ }else{
165
+ return array('error' => 'Unable to perform WP Fastest cache', 'error_code' => 'wpfc_plugin_delete_cache');
166
+ }
167
+ } else {
168
+ return array('error'=>"WP fastest cache not activated", 'error_code' => 'wpfc_plugin_is_not_activated');
169
+ }
170
+ }
171
+
172
+ /*
173
+ * This function will delete all cache files for WP Super Cache Plugin
174
+ */
175
+
176
+ public function deleteALLWPSuperCache(){
177
+ if($this->checkWPSuperCachePlugin()) {
178
+ global $file_prefix;
179
+ $wp_super_cache = wp_cache_clean_cache($file_prefix, true);
180
+ if ($wp_super_cache == false) {
181
+ return array('error' => 'Unable to perform WP Super cache', 'error_code' => 'wp_super_cache_plugin_delete_cache');
182
+ }
183
+ return array('success' => 'All cache files have been deleted');
184
+ } else {
185
+ return array('error'=>"WP Super cache not activated", 'error_code' => 'wp_super_cache_plugin_is_not_activated');
186
+ }
187
+ }
188
+
189
+ /*
190
+ * This function will delete all cache files for W3 Total Cache Plugin
191
+ */
192
+
193
+ public function deleteALLW3TotalCache(){
194
+ if($this->checkW3TotalCachePlugin()) {
195
+ w3tc_flush_all();
196
+ return array('success' => 'All cache files have been deleted');
197
+ } else {
198
+ return array('error'=>"W3 Total cache not activated", 'error_code' => 'wp_super_cache_plugin_is_not_activated');
199
+ }
200
+ }
201
+
202
+ /*
203
+ * This function will delete all cache files for Comet Cache Plugin
204
+ */
205
+
206
+ public function deleteALLCometCache(){
207
+ if($this->checkCometCachePlugin()) {
208
+ global $iwp_mmb_plugin_dir;
209
+ require_once("$iwp_mmb_plugin_dir/addons/wp_optimize/comet-cache-class.php");
210
+ return clearCometCacheIWP();
211
+ } else {
212
+ return array('error'=>"Comet cache not activated", 'error_code' => 'comet_cache_plugin_is_not_activated');
213
+ }
214
+ }
215
+
216
+ /*
217
+ * This function will delete all cache files for WP Rocket Plugin
218
+ */
219
+
220
+ public function deleteALLWPRocketCache(){
221
+ if($this->checkWPRocketPlugin()) {
222
+ $lang = '';
223
+ // Remove all cache files.
224
+ rocket_clean_domain( $lang );
225
+
226
+ // Remove all minify cache files.
227
+ rocket_clean_minify();
228
+
229
+ // Remove cache busting files.
230
+ rocket_clean_cache_busting();
231
+
232
+ // Generate a new random key for minify cache file.
233
+ $options = get_option( WP_ROCKET_SLUG );
234
+ $options['minify_css_key'] = create_rocket_uniqid();
235
+ $options['minify_js_key'] = create_rocket_uniqid();
236
+ remove_all_filters( 'update_option_' . WP_ROCKET_SLUG );
237
+ update_option( WP_ROCKET_SLUG, $options );
238
+ return array('success' => 'All cache files have been deleted');
239
+ } else {
240
+ return array('error'=>"WP Rocket not activated", 'error_code' => 'comet_cache_plugin_is_not_activated');
241
+ }
242
+ }
243
+
244
+ }
245
+
246
+ if(class_exists('WpFastestCache')){
247
+ class IWP_MMB_WPFC_CACHE extends WpFastestCache{
248
+
249
+ public function deleteALLCache(){
250
+ $this->deleteCacheToolbar();
251
+ }
252
+
253
+ public function deleteMinifiedCache(){
254
+ $this->deleteCssAndJsCacheToolbar();
255
+ }
256
+
257
+ public function _getSystemMessage(){
258
+ return $this->getSystemMessage();
259
+ }
260
+ }
261
+ }
backup.class.multicall.php CHANGED
@@ -15,9 +15,8 @@
15
  * Copyright (c) 2011 Prelovac Media
16
  * www.prelovac.com
17
  **************************************************************/
18
- if(basename($_SERVER['SCRIPT_FILENAME']) == "backup.class.multicall.php"):
19
- exit;
20
- endif;
21
 
22
  if(!defined('IWP_BACKUP_DIR')){
23
  define('IWP_BACKUP_DIR', WP_CONTENT_DIR . '/infinitewp/backups');
@@ -108,7 +107,11 @@ class IWP_MMB_Backup_Multicall extends IWP_MMB_Core
108
  'finished' => 100
109
  );
110
  $this->tasks = get_option('iwp_client_multi_backup_temp_values');
111
- $this->iwpScriptStartTime = $GLOBALS['IWP_MMB_PROFILING']['ACTION_START'];
 
 
 
 
112
  }
113
 
114
  function set_resource_limit()
@@ -143,6 +146,7 @@ class IWP_MMB_Backup_Multicall extends IWP_MMB_Core
143
  return $this->statusLog($datas['backupParentHID'], array('stage' => 'trigger_check', 'status' => 'error', 'statusMsg' => 'Error while fetching table data', 'statusCode' => 'error_while_fetching_table_data'));
144
  }
145
  $action = $responseParams['nextFunc'];
 
146
  if(empty($action))
147
  {
148
  manual_debug('', 'triggerError');
@@ -164,6 +168,11 @@ class IWP_MMB_Backup_Multicall extends IWP_MMB_Core
164
  }
165
  manual_debug('', 'triggerEnd');
166
  return $result;
 
 
 
 
 
167
  }
168
  }
169
  }
@@ -190,13 +199,18 @@ class IWP_MMB_Backup_Multicall extends IWP_MMB_Core
190
 
191
  if(!empty($params))
192
  {
 
 
 
 
193
  initialize_manual_debug();
194
  $this->cleanup();
195
  $initialize_result = refresh_iwp_files_db();
196
  if(is_array($initialize_result) && isset($initialize_result['error'])){
197
  return $initialize_result;
198
  }
199
-
 
200
  //darkCode testing purpose static values
201
  if((empty($params['args']['file_block_size']))||($params['args']['file_block_size'] < 1))
202
  {
@@ -362,6 +376,114 @@ class IWP_MMB_Backup_Multicall extends IWP_MMB_Core
362
 
363
 
364
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
 
366
  function backup_uploads($historyID)
367
  {
@@ -524,7 +646,14 @@ class IWP_MMB_Backup_Multicall extends IWP_MMB_Core
524
  $res_arr['backup_url'] = $this -> backup_url;
525
  $res_arr['account_info'] = $account_info;
526
  $this->statusLog($historyID, array('stage' => 'backupDB', 'status' => 'initiating', 'statusMsg' => 'createdFileNameAndSent','responseParams' => $res_arr));
527
- $db_result = $this->backupDBPHP($historyID);
 
 
 
 
 
 
 
528
 
529
  //arguments format - dbresult_before_zip
530
  //$result = $this->backupDBZip($historyID,$db_result,$backup_url); //if DB is succsessful do the DB zip
@@ -1416,6 +1545,7 @@ class IWP_MMB_Backup_Multicall extends IWP_MMB_Core
1416
  if ( ($p_filedescr_list[$j]['type'] != 'virtual_file')
1417
  && (!file_exists($p_filedescr_list[$j]['filename']))) {
1418
  echo 'iwpmsg FILE DOESNT EXIST';
 
1419
  }
1420
 
1421
  // ----- Look if it is a file or a dir with no all path remove option
@@ -1924,6 +2054,7 @@ class IWP_MMB_Backup_Multicall extends IWP_MMB_Core
1924
  if ( ($p_filedescr_list[$j]['type'] != 'virtual_file')
1925
  && (!file_exists($p_filedescr_list[$j]['filename']))) {
1926
  echo 'FILE DOESNT EXIST';
 
1927
  }
1928
 
1929
  // ----- Look if it is a file or a dir with no all path remove option
@@ -4111,7 +4242,7 @@ function ftp_backup($historyID,$args = '')
4111
  }
4112
  }
4113
 
4114
- function postUploadS3VerificationBwdComp($backup_file, $destFile, $type = "", $as3_bucket = "", $as3_access_key = "", $as3_secure_key = "", $as3_bucket_region = "", &$obj, $actual_file_size, $size1, $size2){
4115
  $response = $obj -> if_object_exists($as3_bucket, $destFile);
4116
  if($response == true)
4117
  {
@@ -4119,6 +4250,9 @@ function ftp_backup($historyID,$args = '')
4119
  $cfu_obj = new CFUtilities;
4120
  $meta_response_array = $cfu_obj->convert_response_to_array($meta);
4121
  $s3_filesize = $meta_response_array['header']['content-length'];
 
 
 
4122
  echo "S3 fileszie during verification - ".$s3_filesize;
4123
  if((($s3_filesize >= $size1 && $s3_filesize <= $actual_file_size) || ($s3_filesize <= $size2 && $s3_filesize >= $actual_file_size) || ($s3_filesize == $actual_file_size)) && ($s3_filesize != 0))
4124
  {
@@ -4387,10 +4521,10 @@ function ftp_backup($historyID,$args = '')
4387
  else
4388
  $dropbox_destination .= '/' . basename($backup_file);
4389
  }else{
4390
- require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox2/API.php';
4391
- require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox2/Exception.php';
4392
- require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox2/OAuth/Consumer/ConsumerAbstract.php';
4393
- require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox2/OAuth/Consumer/Curl.php';
4394
 
4395
  $oauth = new IWP_Dropbox_OAuth_Consumer_Curl($dropbox_app_key, $dropbox_app_secure_key);
4396
  $oauth->setToken($dropbox_access_token);
@@ -4527,7 +4661,11 @@ function ftp_backup($historyID,$args = '')
4527
  // Try the indicated offset
4528
  $we_tried = $matches[1];
4529
  $offset = $matches[2];
4530
- $chunkResult = $dropbox->chunked_upload($backup_file, $dropbox_destination, true, $uploadid, $offset, $isCommit);
 
 
 
 
4531
  $result_arr = array();
4532
  $result_arr['response_data'] = $chunkResult;
4533
  $result_arr['nextFunc'] = 'dropbox_backup';
@@ -4536,6 +4674,7 @@ function ftp_backup($historyID,$args = '')
4536
  $uploadid = isset($chunkResult['upload_id']) ? $chunkResult['upload_id'] : 0;
4537
  $resArray = array (
4538
  'backupParentHID' => $historyID,
 
4539
  );
4540
  $result_arr['nextFunc'] = 'dropbox_backup';
4541
  $result_arr['current_file_num'] = $current_file_num;
@@ -4543,6 +4682,19 @@ function ftp_backup($historyID,$args = '')
4543
  $this->statusLog($historyID, array('stage' => 'dropboxMultiCall', 'status' => 'completed', 'statusMsg' => 'nextCall','nextFunc' => 'dropbox_backup', 'task_result' => $task_result, 'responseParams' => $result_arr));
4544
  return $resArray;
4545
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
4546
  $this->_log($e->getMessage());
4547
  return array(
4548
  'error' => $e->getMessage(),
@@ -4575,10 +4727,10 @@ function ftp_backup($historyID,$args = '')
4575
  if ($dropbox_site_folder == true)
4576
  $dropbox_destination .= '/' . $this->site_name;
4577
  }else{
4578
- require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox2/API.php';
4579
- require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox2/Exception.php';
4580
- require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox2/OAuth/Consumer/ConsumerAbstract.php';
4581
- require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox2/OAuth/Consumer/Curl.php';
4582
 
4583
  $oauth = new IWP_Dropbox_OAuth_Consumer_Curl($dropbox_app_key, $dropbox_app_secure_key);
4584
  $oauth->setToken($dropbox_access_token);
@@ -4637,10 +4789,10 @@ function ftp_backup($historyID,$args = '')
4637
  if ($dropbox_site_folder == true)
4638
  $dropbox_destination .= '/' . $this->site_name;
4639
  }else{
4640
- require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox2/API.php';
4641
- require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox2/Exception.php';
4642
- require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox2/OAuth/Consumer/ConsumerAbstract.php';
4643
- require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox2/OAuth/Consumer/Curl.php';
4644
 
4645
  $oauth = new IWP_Dropbox_OAuth_Consumer_Curl($dropbox_app_key, $dropbox_app_secure_key);
4646
  $oauth->setToken($dropbox_access_token);
@@ -5813,13 +5965,12 @@ function ftp_backup($historyID,$args = '')
5813
  }
5814
  */
5815
 
5816
- function remove_old_backups($task_name)
5817
  {
5818
  //Check for previous failed backups first
5819
  //$this->cleanup();
5820
 
5821
  global $wpdb;
5822
-
5823
  $table_name = $wpdb->base_prefix . "iwp_backup_status";
5824
 
5825
  //Check for previous failed backups first
@@ -5828,24 +5979,63 @@ function ftp_backup($historyID,$args = '')
5828
  //Remove by limit
5829
  $backups = $this->get_all_tasks();
5830
 
5831
- $thisTask = $this->get_this_tasks();
5832
- $requestParams = unserialize($thisTask['requestParams']);
5833
- $limit = $requestParams['args']['limit'];
5834
-
 
 
 
 
5835
  /*if ($task_name == 'Backup Now') {
5836
  $num = 0;
5837
  } else {
5838
  $num = 1;
5839
  }*/
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5840
 
5841
  $select_prev_backup = "SELECT historyID, taskResults FROM ".$table_name." WHERE taskName = '".$task_name."' ORDER BY ID DESC LIMIT ".$limit.",100 ";
5842
 
5843
  $select_prev_backup_res = $wpdb->get_results($select_prev_backup, ARRAY_A);
5844
 
5845
 
5846
-
5847
  foreach ( $select_prev_backup_res as $backup_data )
5848
  {
 
5849
  $task_result = unserialize($backup_data['taskResults']);
5850
 
5851
  if (isset($task_result['task_results'][$backup_data['historyID']]['server'])) {
@@ -5906,8 +6096,11 @@ function ftp_backup($historyID,$args = '')
5906
 
5907
  $deleteRes = $wpdb->query($delete_query);
5908
  }
5909
-
5910
- return true;
 
 
 
5911
 
5912
  }
5913
 
@@ -5995,7 +6188,7 @@ function ftp_backup($historyID,$args = '')
5995
  foreach ($files as $file) {
5996
  if ((!in_array($file, $results) || in_array($file, $failedBackupHisID)) && basename($file) != 'index.php') {
5997
  @unlink($file);
5998
- //$deleted[] = basename($file);
5999
  $deleted[] = $file;
6000
  $num_deleted++;
6001
  }
@@ -6212,6 +6405,18 @@ function ftp_backup($historyID,$args = '')
6212
  'what' => $what
6213
  );
6214
  }
 
 
 
 
 
 
 
 
 
 
 
 
6215
  }
6216
 
6217
  if( !function_exists('initialize_manual_debug') ){
@@ -6473,8 +6678,7 @@ function ftp_backup($historyID,$args = '')
6473
  if($action == 'insert'){
6474
  $this_insertID = $wpdb->insert_id;
6475
  }
6476
- }
6477
- else{
6478
  if($action == 'update'){
6479
  return array('error' => 1);
6480
  }
@@ -6495,7 +6699,11 @@ function ftp_backup($historyID,$args = '')
6495
 
6496
  if( !function_exists('check_and_break_iwp') ){
6497
  function check_and_break_iwp(){
6498
- if((microtime(true) - $GLOBALS['IWP_MMB_PROFILING']['ACTION_START']) > 22){
 
 
 
 
6499
  return true;
6500
  }
6501
  else{
15
  * Copyright (c) 2011 Prelovac Media
16
  * www.prelovac.com
17
  **************************************************************/
18
+ if ( ! defined('ABSPATH') )
19
+ die();
 
20
 
21
  if(!defined('IWP_BACKUP_DIR')){
22
  define('IWP_BACKUP_DIR', WP_CONTENT_DIR . '/infinitewp/backups');
107
  'finished' => 100
108
  );
109
  $this->tasks = get_option('iwp_client_multi_backup_temp_values');
110
+ if (!empty($GLOBALS['IWP_MMB_PROFILING']['ACTION_START'])) {
111
+ $this->iwpScriptStartTime = $GLOBALS['IWP_MMB_PROFILING']['ACTION_START'];
112
+ }else{
113
+ $this->iwpScriptStartTime = microtime(1);
114
+ }
115
  }
116
 
117
  function set_resource_limit()
146
  return $this->statusLog($datas['backupParentHID'], array('stage' => 'trigger_check', 'status' => 'error', 'statusMsg' => 'Error while fetching table data', 'statusCode' => 'error_while_fetching_table_data'));
147
  }
148
  $action = $responseParams['nextFunc'];
149
+ $status = $responseParams['status'];
150
  if(empty($action))
151
  {
152
  manual_debug('', 'triggerError');
168
  }
169
  manual_debug('', 'triggerEnd');
170
  return $result;
171
+ }else{
172
+ if ($action == 'backupFilesZIPOver' && $status == 'completed') {
173
+ $result = array('status' => 'completed');
174
+ return $result;
175
+ }
176
  }
177
  }
178
  }
199
 
200
  if(!empty($params))
201
  {
202
+ $disk_space = iwp_mmb_check_disk_space();
203
+ if ($disk_space != false) {
204
+ iwp_mmb_response(array('error' => 'Your disk space is very low available space: '.$disk_space.'MB'), false);
205
+ }
206
  initialize_manual_debug();
207
  $this->cleanup();
208
  $initialize_result = refresh_iwp_files_db();
209
  if(is_array($initialize_result) && isset($initialize_result['error'])){
210
  return $initialize_result;
211
  }
212
+
213
+
214
  //darkCode testing purpose static values
215
  if((empty($params['args']['file_block_size']))||($params['args']['file_block_size'] < 1))
216
  {
376
 
377
 
378
  }
379
+
380
+ function backup_db_dump_multi($historyID)
381
+ {
382
+ $requestParams = $this->getRequiredData($historyID, "requestParams");
383
+ $db_loop_break_time = $requestParams['args']['db_loop_break_time'];
384
+ $responseParams = $this -> getRequiredData($historyID,"responseParams");
385
+ $file = $responseParams['file_name'];
386
+
387
+ if(!$file)
388
+ {
389
+ $file = '';
390
+ }
391
+ $db_folder = IWP_DB_DIR . '/';
392
+ $temp_sql_file_name = '';
393
+ $db_final_response = array();
394
+ $db_final_response['success'] = array ();
395
+ $db_final_response['success']['type'] = 'db';
396
+ if($file == '')
397
+ {
398
+ $file = DB_NAME;
399
+ }
400
+ $db_final_response['success']['file_name'] = $file;
401
+ //$temp_sql_file_name = $file."-".$callCount.".sql"; //old method
402
+ $temp_sql_file_name = $file.".sql";
403
+ $file = $db_folder . $temp_sql_file_name;
404
+ global $wpdb;
405
+ $paths = $this->check_mysql_paths();
406
+ $brace = (substr(PHP_OS, 0, 3) == 'WIN') ? '"' : '';
407
+ //$command = $brace . $paths['mysqldump'] . $brace . ' --force --host="' . DB_HOST . '" --user="' . DB_USER . '" --password="' . DB_PASSWORD . '" --add-drop-table --skip-lock-tables "' . DB_NAME . '" > ' . $brace . $file . $brace;
408
+ $command0 = $wpdb->get_col('SHOW TABLES LIKE "'.$wpdb->base_prefix.'%"');
409
+ $wp_tables = join("\" \"",$command0);
410
+ $command = $brace . $paths['mysqldump'] . $brace . ' --force --host="' . DB_HOST . '" --user="' . DB_USER . '" --password="' . DB_PASSWORD . '" --add-drop-table --skip-lock-tables --extended-insert=FALSE "' . DB_NAME . '" "'.$wp_tables.'" > ' . $brace . $file . $brace;
411
+ iwp_mmb_print_flush('DB DUMP CMD: Start');
412
+ ob_start();
413
+ $result = $this->iwp_mmb_exec($command);
414
+ ob_get_clean();
415
+ iwp_mmb_print_flush('DB DUMP CMD: End');
416
+ $time = microtime(true);
417
+ $finish_part = $time;
418
+ $total_time_part = $finish_part - $this->iwpScriptStartTime;
419
+
420
+ if (!$result) { // Fallback to php
421
+ // $result = $this->backup_db_php($file);
422
+ return $result;
423
+ }
424
+
425
+ if (iwp_mmb_get_file_size($file) == 0 || !is_file($file) || !$result) {
426
+ @unlink($file);
427
+ return false;
428
+ } else {
429
+ $db_final_response['success']['backup_file'] = $responseParams['backup_file'];
430
+ $db_final_response['success']['backup_url'] = $responseParams['backup_url'];
431
+ $db_final_response['success']['parentHID'] = $historyID;
432
+ $db_final_response['success']['backupParentHID'] = $historyID;
433
+ $db_final_response['success']['nextFunc'] = 'backupDBZip';
434
+ $db_final_response['success']['account_info'] = $responseParams['account_info'];
435
+ //$this->statusLog($historyID, "backupDB", true, "completed", $params, true);
436
+ //$this->statusLog($historyID, array('stage' => $backupStage, 'status' => 'completed', 'statusMsg' => 'backupDBCompleted'));
437
+ $db_final_response['success']['status'] = 'partiallyCompleted';
438
+ $this->statusLog($historyID, array('stage' => $backupStage, 'status' => 'completed', 'statusMsg' => 'backupDBCompleted','nextFunc' => 'backupDBZip', 'responseParams' => $db_final_response['success']));
439
+ unset($db_final_response['success']['response_data']);
440
+ //to continue in the same call
441
+ if(($db_loop_break_time - $total_time_part) > 5)
442
+ {
443
+ return $this->backupDBZip($historyID);
444
+ }
445
+ else
446
+ {
447
+ $db_res_array = array();
448
+ $db_res_array['status'] = $db_final_response['success']['status'];
449
+ $db_res_array['backupParentHID'] = $db_final_response['success']['backupParentHID'];
450
+ $db_res_array['parentHID'] = $db_final_response['success']['parentHID'];
451
+ return $db_res_array;
452
+ }
453
+ }
454
+ }
455
+
456
+ function check_mysql_paths()
457
+ {
458
+ global $wpdb;
459
+ $paths = array(
460
+ 'mysql' => '',
461
+ 'mysqldump' => ''
462
+ );
463
+ if (substr(PHP_OS, 0, 3) == 'WIN') {
464
+ $mysql_install = $wpdb->get_row("SHOW VARIABLES LIKE 'basedir'");
465
+ if ($mysql_install) {
466
+ $install_path = str_replace('\\', '/', $mysql_install->Value);
467
+ $paths['mysql'] = $install_path . '/bin/mysql.exe';
468
+ $paths['mysqldump'] = $install_path . '/bin/mysqldump.exe';
469
+ } else {
470
+ $paths['mysql'] = 'mysql.exe';
471
+ $paths['mysqldump'] = 'mysqldump.exe';
472
+ }
473
+ } else {
474
+ $paths['mysql'] = $this->iwp_mmb_exec('which mysql', true);
475
+ if (empty($paths['mysql']))
476
+ $paths['mysql'] = 'mysql'; // try anyway
477
+
478
+ $paths['mysqldump'] = $this->iwp_mmb_exec('which mysqldump', true);
479
+ if (empty($paths['mysqldump']))
480
+ $paths['mysqldump'] = 'mysqldump'; // try anyway
481
+
482
+ }
483
+
484
+
485
+ return $paths;
486
+ }
487
 
488
  function backup_uploads($historyID)
489
  {
646
  $res_arr['backup_url'] = $this -> backup_url;
647
  $res_arr['account_info'] = $account_info;
648
  $this->statusLog($historyID, array('stage' => 'backupDB', 'status' => 'initiating', 'statusMsg' => 'createdFileNameAndSent','responseParams' => $res_arr));
649
+ $func = $this->check_sys();
650
+ $db_result = true;
651
+ if ($db_result) {
652
+ $db_result = $this->backup_db_dump_multi($historyID);
653
+ }
654
+ if ($db_result == false) {
655
+ $db_result = $this->backupDBPHP($historyID);
656
+ }
657
 
658
  //arguments format - dbresult_before_zip
659
  //$result = $this->backupDBZip($historyID,$db_result,$backup_url); //if DB is succsessful do the DB zip
1545
  if ( ($p_filedescr_list[$j]['type'] != 'virtual_file')
1546
  && (!file_exists($p_filedescr_list[$j]['filename']))) {
1547
  echo 'iwpmsg FILE DOESNT EXIST';
1548
+ continue;
1549
  }
1550
 
1551
  // ----- Look if it is a file or a dir with no all path remove option
2054
  if ( ($p_filedescr_list[$j]['type'] != 'virtual_file')
2055
  && (!file_exists($p_filedescr_list[$j]['filename']))) {
2056
  echo 'FILE DOESNT EXIST';
2057
+ continue;
2058
  }
2059
 
2060
  // ----- Look if it is a file or a dir with no all path remove option
4242
  }
4243
  }
4244
 
4245
+ function postUploadS3VerificationBwdComp($backup_file, $destFile, $type = "", $as3_bucket = "", $as3_access_key = "", $as3_secure_key = "", $as3_bucket_region = "", &$obj, $actual_file_size, $size1, $size2, $return_size = false){
4246
  $response = $obj -> if_object_exists($as3_bucket, $destFile);
4247
  if($response == true)
4248
  {
4250
  $cfu_obj = new CFUtilities;
4251
  $meta_response_array = $cfu_obj->convert_response_to_array($meta);
4252
  $s3_filesize = $meta_response_array['header']['content-length'];
4253
+ if ($return_size == true) {
4254
+ return $s3_file_size;
4255
+ }
4256
  echo "S3 fileszie during verification - ".$s3_filesize;
4257
  if((($s3_filesize >= $size1 && $s3_filesize <= $actual_file_size) || ($s3_filesize <= $size2 && $s3_filesize >= $actual_file_size) || ($s3_filesize == $actual_file_size)) && ($s3_filesize != 0))
4258
  {
4521
  else
4522
  $dropbox_destination .= '/' . basename($backup_file);
4523
  }else{
4524
+ require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/API.php';
4525
+ require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/Exception.php';
4526
+ require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/OAuth/Consumer/ConsumerAbstract.php';
4527
+ require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/OAuth/Consumer/Curl.php';
4528
 
4529
  $oauth = new IWP_Dropbox_OAuth_Consumer_Curl($dropbox_app_key, $dropbox_app_secure_key);
4530
  $oauth->setToken($dropbox_access_token);
4661
  // Try the indicated offset
4662
  $we_tried = $matches[1];
4663
  $offset = $matches[2];
4664
+ if($oldVersion){
4665
+ $chunkResult = $dropbox->chunked_upload($backup_file, $dropbox_destination, true, $uploadid, $offset, $readsize, $isCommit);
4666
+ }else{
4667
+ $chunkResult = $dropbox->chunked_upload($backup_file ,$dropbox_destination, true, $uploadid, $offset, $isCommit);
4668
+ }
4669
  $result_arr = array();
4670
  $result_arr['response_data'] = $chunkResult;
4671
  $result_arr['nextFunc'] = 'dropbox_backup';
4674
  $uploadid = isset($chunkResult['upload_id']) ? $chunkResult['upload_id'] : 0;
4675
  $resArray = array (
4676
  'backupParentHID' => $historyID,
4677
+ 'status' => 'partiallyCompleted'
4678
  );
4679
  $result_arr['nextFunc'] = 'dropbox_backup';
4680
  $result_arr['current_file_num'] = $current_file_num;
4682
  $this->statusLog($historyID, array('stage' => 'dropboxMultiCall', 'status' => 'completed', 'statusMsg' => 'nextCall','nextFunc' => 'dropbox_backup', 'task_result' => $task_result, 'responseParams' => $result_arr));
4683
  return $resArray;
4684
  }
4685
+
4686
+ if ($e->getMessage() == 'path') {
4687
+
4688
+ $response = $dropbox->quotaInfo();
4689
+ $usedSize = $response['body']->used;
4690
+ $allocated = $response['body']->allocation->allocated;
4691
+ if ($usedSize>=$allocated) {
4692
+ return array(
4693
+ 'error' => "Dropbox quota exceeded (Allowed ".round($allocated / (1024*1024), 2)." MB and used ".round($usedSize / (1024*1024), 2)." MB)",
4694
+ 'partial' => 1
4695
+ );
4696
+ }
4697
+ }
4698
  $this->_log($e->getMessage());
4699
  return array(
4700
  'error' => $e->getMessage(),
4727
  if ($dropbox_site_folder == true)
4728
  $dropbox_destination .= '/' . $this->site_name;
4729
  }else{
4730
+ require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/API.php';
4731
+ require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/Exception.php';
4732
+ require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/OAuth/Consumer/ConsumerAbstract.php';
4733
+ require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/OAuth/Consumer/Curl.php';
4734
 
4735
  $oauth = new IWP_Dropbox_OAuth_Consumer_Curl($dropbox_app_key, $dropbox_app_secure_key);
4736
  $oauth->setToken($dropbox_access_token);
4789
  if ($dropbox_site_folder == true)
4790
  $dropbox_destination .= '/' . $this->site_name;
4791
  }else{
4792
+ require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/API.php';
4793
+ require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/Exception.php';
4794
+ require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/OAuth/Consumer/ConsumerAbstract.php';
4795
+ require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/OAuth/Consumer/Curl.php';
4796
 
4797
  $oauth = new IWP_Dropbox_OAuth_Consumer_Curl($dropbox_app_key, $dropbox_app_secure_key);
4798
  $oauth->setToken($dropbox_access_token);
5965
  }
5966
  */
5967
 
5968
+ function remove_old_backups($task_name, $limit = false)
5969
  {
5970
  //Check for previous failed backups first
5971
  //$this->cleanup();
5972
 
5973
  global $wpdb;
 
5974
  $table_name = $wpdb->base_prefix . "iwp_backup_status";
5975
 
5976
  //Check for previous failed backups first
5979
  //Remove by limit
5980
  $backups = $this->get_all_tasks();
5981
 
5982
+ if ($limit === false) {
5983
+ $thisTask = $this->get_this_tasks();
5984
+ $requestParams = unserialize($thisTask['requestParams']);
5985
+ $limit = $requestParams['args']['limit'];
5986
+ }else{
5987
+ $limit = ($limit == 1)?0:$limit;
5988
+ $fromNewBackup = true;
5989
+ }
5990
  /*if ($task_name == 'Backup Now') {
5991
  $num = 0;
5992
  } else {
5993
  $num = 1;
5994
  }*/
5995
+ $other_method_backups = iwp_mmb_get_backup_ID_by_taskname('advanced', $task_name);
5996
+ $current_backups = $this->get_timestamp_by_label($task_name);
5997
+ $all_backups = array();
5998
+ $delete_backup = array();
5999
+ if (!empty($other_method_backups)) {
6000
+ $all_backups = array_merge($all_backups, $other_method_backups);
6001
+ if (!empty($current_backups)) {
6002
+ $all_backups = array_merge($all_backups, $current_backups);
6003
+ ksort($all_backups);
6004
+ ksort($current_backups);
6005
+ foreach ($other_method_backups as $timestamp => $historyID) {
6006
+ foreach ($current_backups as $time => $value) {
6007
+ if ($time > $timestamp) {
6008
+ $delete_backup[$timestamp] = $timestamp;
6009
+ }
6010
+ break;
6011
+ }
6012
+ }
6013
+ }
6014
+ }
6015
+ if (!empty($delete_backup)) {
6016
+ $total_backups = count($all_backups);
6017
+ if ($total_backups > $limit) {
6018
+ require_once($GLOBALS['iwp_mmb_plugin_dir'].'/backup/backup.core.class.php');
6019
+ iwp_mmb_define_constant();
6020
+ $backup_instance = new IWP_MMB_Backup_Core();
6021
+ foreach ($delete_backup as $timestamp => $historyID) {
6022
+ $total_backups--;
6023
+ $backup_instance->delete_backup(array('result_id' => $historyID));
6024
+ if ($total_backups<= $limit) {
6025
+ return;
6026
+ }
6027
+ }
6028
+ }
6029
+ }
6030
 
6031
  $select_prev_backup = "SELECT historyID, taskResults FROM ".$table_name." WHERE taskName = '".$task_name."' ORDER BY ID DESC LIMIT ".$limit.",100 ";
6032
 
6033
  $select_prev_backup_res = $wpdb->get_results($select_prev_backup, ARRAY_A);
6034
 
6035
 
 
6036
  foreach ( $select_prev_backup_res as $backup_data )
6037
  {
6038
+ $deleted = 1;
6039
  $task_result = unserialize($backup_data['taskResults']);
6040
 
6041
  if (isset($task_result['task_results'][$backup_data['historyID']]['server'])) {
6096
 
6097
  $deleteRes = $wpdb->query($delete_query);
6098
  }
6099
+ if ($fromNewBackup) {
6100
+ return ($deleted)?true:false;
6101
+ }else{
6102
+ return true;
6103
+ }
6104
 
6105
  }
6106
 
6188
  foreach ($files as $file) {
6189
  if ((!in_array($file, $results) || in_array($file, $failedBackupHisID)) && basename($file) != 'index.php') {
6190
  @unlink($file);
6191
+ // $deleted[] = basename($file);
6192
  $deleted[] = $file;
6193
  $num_deleted++;
6194
  }
6405
  'what' => $what
6406
  );
6407
  }
6408
+
6409
+ function get_timestamp_by_label($label){
6410
+ $new_backup_keys = array();
6411
+ global $wpdb;
6412
+ $table_name = $wpdb->base_prefix . "iwp_backup_status";
6413
+ $select_prev_backup = "SELECT historyID, lastUpdateTime FROM ".$table_name." WHERE taskName = '".$label."' ORDER BY ID DESC ";
6414
+ $select_prev_backup_res = $wpdb->get_results($select_prev_backup, ARRAY_A);
6415
+ foreach ($select_prev_backup_res as $key => $value) {
6416
+ $new_backup_keys[$value['lastUpdateTime']]= $value['historyID'];
6417
+ }
6418
+ return $new_backup_keys;
6419
+ }
6420
  }
6421
 
6422
  if( !function_exists('initialize_manual_debug') ){
6678
  if($action == 'insert'){
6679
  $this_insertID = $wpdb->insert_id;
6680
  }
6681
+ }elseif ($result === false) {
 
6682
  if($action == 'update'){
6683
  return array('error' => 1);
6684
  }
6699
 
6700
  if( !function_exists('check_and_break_iwp') ){
6701
  function check_and_break_iwp(){
6702
+ $timeLimit = 18;
6703
+ if (defined('IWP_FILE_LIST_BREAK_TIME') && IWP_FILE_LIST_BREAK_TIME) {
6704
+ $timeLimit = IWP_FILE_LIST_BREAK_TIME;
6705
+ }
6706
+ if((microtime(true) - $GLOBALS['IWP_MMB_PROFILING']['ACTION_START']) > $timeLimit){
6707
  return true;
6708
  }
6709
  else{
backup.class.singlecall.php CHANGED
@@ -1822,7 +1822,7 @@ function ftp_backup($args)
1822
  * SFTP section start here phpseclib library is used for this functionality
1823
  */
1824
  $iwp_mmb_plugin_dir = WP_PLUGIN_DIR . '/' . basename(dirname(__FILE__));
1825
- $path = $iwp_mmb_plugin_dir.'/lib/phpseclib';
1826
  set_include_path(get_include_path() . PATH_SEPARATOR . $path);
1827
  include_once('Net/SFTP.php');
1828
 
@@ -1943,7 +1943,7 @@ function ftp_backup($args)
1943
  * SFTP section start here phpseclib library is used for this functionality
1944
  */
1945
  $iwp_mmb_plugin_dir = WP_PLUGIN_DIR . '/' . basename(dirname(__FILE__));
1946
- $path = $iwp_mmb_plugin_dir.'/lib/phpseclib';
1947
  set_include_path(get_include_path() . PATH_SEPARATOR . $path);
1948
  include_once('Net/SFTP.php');
1949
 
@@ -2026,10 +2026,10 @@ function ftp_backup($args)
2026
  else
2027
  $dropbox_destination .= '/' . basename($backup_file);
2028
  }else{
2029
- require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox2/API.php';
2030
- require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox2/Exception.php';
2031
- require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox2/OAuth/Consumer/ConsumerAbstract.php';
2032
- require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox2/OAuth/Consumer/Curl.php';
2033
 
2034
  $oauth = new IWP_Dropbox_OAuth_Consumer_Curl($dropbox_app_key, $dropbox_app_secure_key);
2035
  $oauth->setToken($dropbox_access_token);
@@ -2094,10 +2094,10 @@ function ftp_backup($args)
2094
  $dropbox_destination .= '/' . $this->site_name;
2095
  $oldVersion = true;
2096
  }else{
2097
- require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox2/API.php';
2098
- require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox2/Exception.php';
2099
- require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox2/OAuth/Consumer/ConsumerAbstract.php';
2100
- require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox2/OAuth/Consumer/Curl.php';
2101
 
2102
  $oauth = new IWP_Dropbox_OAuth_Consumer_Curl($dropbox_app_key, $dropbox_app_secure_key);
2103
  $oauth->setToken($dropbox_access_token);
@@ -2726,7 +2726,7 @@ function ftp_backup($args)
2726
  $task_res[$value->taskName][$value->historyID] = $task_results['task_results'][$value->historyID];
2727
  $task_res[$value->taskName][$value->historyID]['backhack_status'] = $task_results['backhack_status'];
2728
  }
2729
- $stats = $task_res;
2730
  return $stats;
2731
  /*foreach ($rows as $obj) {
2732
 
@@ -2772,6 +2772,42 @@ function ftp_backup($args)
2772
  $requestParams = $this->get_this_tasks("requestParams");
2773
 
2774
  $limit = $requestParams['args']['limit'];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2775
 
2776
  $select_prev_backup = "SELECT historyID, taskResults FROM ".$table_name." WHERE taskName = '".$task_name."' ORDER BY ID DESC LIMIT ".$limit.",100 ";
2777
 
@@ -3012,9 +3048,9 @@ function ftp_backup($args)
3012
  $num_deleted = 0;
3013
 
3014
  foreach ($files as $file) {
3015
- if (!in_array($file, $results) && basename($file) != 'index.php') {
3016
  @unlink($file);
3017
- //$deleted[] = basename($file);
3018
  $deleted[] = $file;
3019
  $num_deleted++;
3020
  }
@@ -3211,6 +3247,18 @@ function ftp_backup($args)
3211
  else
3212
  return true;
3213
  }
 
 
 
 
 
 
 
 
 
 
 
 
3214
  }
3215
 
3216
  /*if( function_exists('add_filter') ){
1822
  * SFTP section start here phpseclib library is used for this functionality
1823
  */
1824
  $iwp_mmb_plugin_dir = WP_PLUGIN_DIR . '/' . basename(dirname(__FILE__));
1825
+ $path = $iwp_mmb_plugin_dir.'/lib/phpseclib/phpseclib/phpseclib';
1826
  set_include_path(get_include_path() . PATH_SEPARATOR . $path);
1827
  include_once('Net/SFTP.php');
1828
 
1943
  * SFTP section start here phpseclib library is used for this functionality
1944
  */
1945
  $iwp_mmb_plugin_dir = WP_PLUGIN_DIR . '/' . basename(dirname(__FILE__));
1946
+ $path = $iwp_mmb_plugin_dir.'/lib/phpseclib/phpseclib/phpseclib';
1947
  set_include_path(get_include_path() . PATH_SEPARATOR . $path);
1948
  include_once('Net/SFTP.php');
1949
 
2026
  else
2027
  $dropbox_destination .= '/' . basename($backup_file);
2028
  }else{
2029
+ require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/API.php';
2030
+ require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/Exception.php';
2031
+ require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/OAuth/Consumer/ConsumerAbstract.php';
2032
+ require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/OAuth/Consumer/Curl.php';
2033
 
2034
  $oauth = new IWP_Dropbox_OAuth_Consumer_Curl($dropbox_app_key, $dropbox_app_secure_key);
2035
  $oauth->setToken($dropbox_access_token);
2094
  $dropbox_destination .= '/' . $this->site_name;
2095
  $oldVersion = true;
2096
  }else{
2097
+ require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/API.php';
2098
+ require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/Exception.php';
2099
+ require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/OAuth/Consumer/ConsumerAbstract.php';
2100
+ require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/OAuth/Consumer/Curl.php';
2101
 
2102
  $oauth = new IWP_Dropbox_OAuth_Consumer_Curl($dropbox_app_key, $dropbox_app_secure_key);
2103
  $oauth->setToken($dropbox_access_token);
2726
  $task_res[$value->taskName][$value->historyID] = $task_results['task_results'][$value->historyID];
2727
  $task_res[$value->taskName][$value->historyID]['backhack_status'] = $task_results['backhack_status'];
2728
  }
2729
+ $stats = $task_res;
2730
  return $stats;
2731
  /*foreach ($rows as $obj) {
2732
 
2772
  $requestParams = $this->get_this_tasks("requestParams");
2773
 
2774
  $limit = $requestParams['args']['limit'];
2775
+
2776
+ $other_method_backups = iwp_mmb_get_backup_ID_by_taskname('advanced', $task_name);
2777
+ $current_backups = $this->get_timestamp_by_label($task_name);
2778
+ $all_backups = array();
2779
+ $delete_backup = array();
2780
+ if (!empty($other_method_backups)) {
2781
+ $all_backups = array_merge($all_backups, $other_method_backups);
2782
+ if (!empty($current_backups)) {
2783
+ $all_backups = array_merge($all_backups, $current_backups);
2784
+ ksort($all_backups);
2785
+ ksort($current_backups);
2786
+ foreach ($other_method_backups as $timestamp => $historyID) {
2787
+ foreach ($current_backups as $time => $value) {
2788
+ if ($time > $timestamp) {
2789
+ $delete_backup[$timestamp] = $timestamp;
2790
+ }
2791
+ break;
2792
+ }
2793
+ }
2794
+ }
2795
+ }
2796
+ if (!empty($delete_backup)) {
2797
+ $total_backups = count($all_backups);
2798
+ if ($total_backups > $limit) {
2799
+ require_once($GLOBALS['iwp_mmb_plugin_dir'].'/backup/backup.core.class.php');
2800
+ iwp_mmb_define_constant();
2801
+ $backup_instance = new IWP_MMB_Backup_Core();
2802
+ foreach ($delete_backup as $timestamp => $historyID) {
2803
+ $total_backups--;
2804
+ $backup_instance->delete_backup(array('result_id' => $historyID));
2805
+ if ($total_backups<= $limit) {
2806
+ return;
2807
+ }
2808
+ }
2809
+ }
2810
+ }
2811
 
2812
  $select_prev_backup = "SELECT historyID, taskResults FROM ".$table_name." WHERE taskName = '".$task_name."' ORDER BY ID DESC LIMIT ".$limit.",100 ";
2813
 
3048
  $num_deleted = 0;
3049
 
3050
  foreach ($files as $file) {
3051
+ if ((!in_array($file, $results) && basename($file) != 'index.php')) {
3052
  @unlink($file);
3053
+ // $deleted[] = basename($file);
3054
  $deleted[] = $file;
3055
  $num_deleted++;
3056
  }
3247
  else
3248
  return true;
3249
  }
3250
+
3251
+ function get_timestamp_by_label($label){
3252
+ $new_backup_keys = array();
3253
+ global $wpdb;
3254
+ $table_name = $wpdb->base_prefix . "iwp_backup_status";
3255
+ $select_prev_backup = "SELECT historyID, lastUpdateTime FROM ".$table_name." WHERE taskName = '".$label."' ORDER BY ID DESC ";
3256
+ $select_prev_backup_res = $wpdb->get_results($select_prev_backup, ARRAY_A);
3257
+ foreach ($select_prev_backup_res as $key => $value) {
3258
+ $new_backup_keys[$value['lastUpdateTime']]= $value['historyID'];
3259
+ }
3260
+ return $new_backup_keys;
3261
+ }
3262
  }
3263
 
3264
  /*if( function_exists('add_filter') ){
backup/backup.core.class.php ADDED
@@ -0,0 +1,4878 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if ( ! defined('ABSPATH') )
4
+ die();
5
+
6
+ class IWP_MMB_Backup_Core {
7
+
8
+ public $errors = array();
9
+ public $nonce;
10
+ public $logfile_name = "";
11
+ public $logfile_handle = false;
12
+ public $backup_time;
13
+ public $job_time_ms;
14
+ public $version;
15
+ public $opened_log_time;
16
+ private $iwp_backup_dir;
17
+ public $blog_name;
18
+
19
+ private $jobdata;
20
+
21
+ public $something_useful_happened = false;
22
+
23
+ // Used to schedule resumption attempts beyond the tenth, if needed
24
+ public $current_resumption;
25
+ public $newresumption_scheduled = false;
26
+
27
+ public $cpanel_quota_readable = false;
28
+
29
+ public $error_reporting_stop_when_logged = false;
30
+
31
+ private $combine_jobs_around;
32
+
33
+ public function __construct() {
34
+
35
+ # The two actions which we schedule upon
36
+ $this->version = IWP_MMB_CLIENT_VERSION;
37
+ add_action('IWP_backup', array($this, 'backup_files'));
38
+ add_action('IWP_backup_database', array($this, 'backup_database'));
39
+ add_filter('IWP_backupable_file_entities_final', array($this, 'backupable_file_entities_final'), 10, 3);
40
+
41
+
42
+ # The three actions that can be called from "Backup Now"
43
+ add_action('IWP_backupnow_backup', array($this, 'backupnow_files'));
44
+ add_action('IWP_backupnow_backup_database', array($this, 'backupnow_database'));
45
+ add_action('IWP_backupnow_backup_all', array($this, 'backup_all'));
46
+ add_action('IWP_backup_resume', array($this, 'backup_resume'), 10, 3);
47
+ # backup_all as an action is legacy (Oct 2013) - there may be some people who wrote cron scripts to use it
48
+ add_action('IWP_backup_all', array($this, 'backup_all'));
49
+
50
+ add_filter('schedule_event', array($this, 'schedule_event'));
51
+ add_filter('IWP_dropbox_modpath', array($this, 'dropbox_modpath'),10, 2);
52
+
53
+ }
54
+
55
+ // Ugly, but necessary to prevent debug output breaking the conversation when the user has debug turned on
56
+ private function no_deprecation_warnings_on_php7() {
57
+ // PHP_MAJOR_VERSION is defined in PHP 5.2.7+
58
+ // We don't test for PHP > 7 because the specific deprecated element will be removed in PHP 8 - and so no warning should come anyway (and we shouldn't suppress other stuff until we know we need to).
59
+ if (defined('PHP_MAJOR_VERSION') && PHP_MAJOR_VERSION == 7) {
60
+ $old_level = error_reporting();
61
+ $new_level = $old_level & ~E_DEPRECATED;
62
+ if ($old_level != $new_level) error_reporting($new_level);
63
+ $this->no_deprecation_warnings = true;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * This converts array-style options (i.e. late 2013-onwards) to
69
+ * 2017-style multi-array-style options.
70
+ *
71
+ * N.B. Don't actually call this on any particular method's options
72
+ * until the functions which read the options can cope!
73
+ *
74
+ * N.B. Until the UI is changed (DOM changed), saving settings will
75
+ * revert to the previous format. But that does not break anything.
76
+ *
77
+ * Don't call for settings that aren't array-style. You may lose
78
+ * the settings if you do.
79
+ *
80
+ * It is safe to call this if you are not sure if the options are
81
+ * already updated.
82
+ *
83
+ * @param String $method - the method identifier
84
+ *
85
+ * @returns Array|WP_Error - returns the new options, or a WP_Error if it failed
86
+ */
87
+ public function update_remote_storage_options_format($method) {
88
+ // Prevent recursion
89
+ static $already_active = false;
90
+
91
+ if ($already_active) return new WP_Error('recursion', 'IWP_MMB_Backup_Core::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');
92
+
93
+ if (!file_exists($GLOBALS['iwp_mmb_plugin_dir'].'/backup/'.$method.'.php')) return new WP_Error('no_such_method', 'Remote storage method not found', $method);
94
+
95
+ // Sanity/inconsistency check
96
+ $settings_keys = $this->get_settings_keys();
97
+
98
+ $method_key = 'IWP_'.$method;
99
+
100
+ if (!in_array($method_key, $settings_keys)) return new WP_Error('no_such_setting', 'Setting not found for this method', $method);
101
+
102
+ $current_setting = IWP_MMB_Backup_Options::get_iwp_backup_option($method_key, array());
103
+
104
+ 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));
105
+
106
+ // Already converted?
107
+ if (isset($current_setting['version'])) return $current_setting;
108
+
109
+ $new_setting = $this->wrap_remote_storage_options($current_setting);
110
+
111
+ $already_active = true;
112
+ $updated = IWP_MMB_Backup_Options::update_iwp_backup_option($method_key, $new_setting);
113
+ $already_active = false;
114
+
115
+ if ($updated) {
116
+ return $new_setting;
117
+ } else {
118
+ return WP_Error('save_failed', 'Saving the options in the new format failed', array('method' => $method, 'current_setting' => $new_setting));
119
+ }
120
+
121
+ }
122
+
123
+ /**
124
+ * 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
125
+ *
126
+ * @param Array $options - The remote storage options settings array
127
+ * @return Array - The updated remote storage options settings array
128
+ */
129
+ public function wrap_remote_storage_options($options) {
130
+ // Already converted?
131
+ if (isset($options['version'])) return $options;
132
+
133
+ // Cryptographic randomness not required. The prefix helps avoid potential for type-juggling issues.
134
+ $uuid = 's-'.md5(rand().uniqid().microtime(true));
135
+
136
+ $new_setting = array(
137
+ 'version' => 1,
138
+ );
139
+
140
+ if (!is_array($options)) $options = array();
141
+
142
+ $new_setting['settings'] = array($uuid => $options);
143
+
144
+ return $new_setting;
145
+ }
146
+
147
+ // Returns the number of bytes free, if it can be detected; otherwise, false
148
+ // Presently, we only detect CPanel. If you know of others, then feel free to contribute!
149
+ public function get_hosting_disk_quota_free() {
150
+ if (!@is_dir('/usr/local/cpanel') || $this->detect_safe_mode() || !function_exists('popen') || (!@is_executable('/usr/local/bin/perl') && !@is_executable('/usr/local/cpanel/3rdparty/bin/perl')) || (defined('IWP_SKIP_CPANEL_QUOTA_CHECK') && IWP_SKIP_CPANEL_QUOTA_CHECK)) return false;
151
+
152
+ $perl = (@is_executable('/usr/local/cpanel/3rdparty/bin/perl')) ? '/usr/local/cpanel/3rdparty/bin/perl' : '/usr/local/bin/perl';
153
+
154
+ $exec = "IWPKEY=IWP $perl ".$GLOBALS['iwp_mmb_plugin_dir']."/lib/get-cpanel-quota-usage.pl";
155
+
156
+ $handle = @popen($exec, 'r');
157
+ if (!is_resource($handle)) return false;
158
+
159
+ $found = false;
160
+ $lines = 0;
161
+ while (false === $found && !feof($handle) && $lines<100) {
162
+ $lines++;
163
+ $w = fgets($handle);
164
+ # Used, limit, remain
165
+ if (preg_match('/RESULT: (\d+) (\d+) (\d+) /', $w, $matches)) { $found = true; }
166
+ }
167
+ $ret = pclose($handle);
168
+ if (false === $found ||$ret != 0) return false;
169
+
170
+ if ((int)$matches[2]<100 || ($matches[1] + $matches[3] != $matches[2])) return false;
171
+
172
+ $this->cpanel_quota_readable = true;
173
+
174
+ return $matches;
175
+ }
176
+
177
+ public function last_modified_log() {
178
+ $iwp_backup_dir = $this->backups_dir_location();
179
+
180
+ $log_file = '';
181
+ $mod_time = false;
182
+ $nonce = '';
183
+
184
+ if ($handle = @opendir($iwp_backup_dir)) {
185
+ while (false !== ($entry = readdir($handle))) {
186
+ // The latter match is for files created internally by zipArchive::addFile
187
+ if (preg_match('/^log\.([a-z0-9]+)\.txt$/i', $entry, $matches)) {
188
+ $mtime = filemtime($iwp_backup_dir.'/'.$entry);
189
+ if ($mtime > $mod_time) {
190
+ $mod_time = $mtime;
191
+ $log_file = $iwp_backup_dir.'/'.$entry;
192
+ $nonce = $matches[1];
193
+ }
194
+ }
195
+ }
196
+ @closedir($handle);
197
+ }
198
+
199
+ return array($mod_time, $log_file, $nonce);
200
+ }
201
+
202
+ public function register_wp_http_option_hooks($register = true) {
203
+ if ($register) {
204
+ add_filter('http_request_args', array($this, 'modify_http_options'));
205
+ add_action('http_api_curl', array($this, 'http_api_curl'));
206
+ } else {
207
+ remove_filter('http_request_args', array($this, 'modify_http_options'));
208
+ remove_action('http_api_curl', array($this, 'http_api_curl'));
209
+ }
210
+ }
211
+
212
+ public function http_api_curl($handle) {
213
+ if (defined('IWP_IPV4_ONLY') && IWP_IPV4_ONLY) {
214
+ curl_setopt($handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
215
+ }
216
+ return $handle;
217
+ }
218
+
219
+ public function modify_http_options($opts) {
220
+
221
+ if (!is_array($opts)) return $opts;
222
+
223
+ if (!IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_useservercerts')) $opts['sslcertificates'] = $GLOBALS['iwp_mmb_plugin_dir'].'/lib/cacert.pem';
224
+
225
+ $opts['sslverify'] = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_disableverify') ? false : true;
226
+
227
+ return $opts;
228
+
229
+ }
230
+
231
+ public function get_table_prefix($allow_override = false) {
232
+ global $wpdb;
233
+ if (is_multisite() && !defined('MULTISITE')) {
234
+ # In this case (which should only be possible on installs upgraded from pre WP 3.0 WPMU), $wpdb->get_blog_prefix() cannot be made to return the right thing. $wpdb->base_prefix is not explicitly marked as public, so we prefer to use get_blog_prefix if we can, for future compatibility.
235
+ $prefix = $wpdb->base_prefix;
236
+ } else {
237
+ $prefix = $wpdb->get_blog_prefix(0);
238
+ }
239
+ return ($allow_override) ? apply_filters('IWP_get_table_prefix', $prefix) : $prefix;
240
+ }
241
+
242
+ public function siteid() {
243
+ $sid = get_site_option('IWP-addons_siteid');
244
+ if (!is_string($sid) || empty($sid)) {
245
+ $sid = md5(rand().microtime(true).home_url());
246
+ update_site_option('IWP-addons_siteid', $sid);
247
+ }
248
+ return $sid;
249
+ }
250
+
251
+ public function plugins_loaded() {
252
+
253
+ // The Google Analyticator plugin does something horrible: loads an old version of the Google SDK on init, always - which breaks us
254
+ if ((defined('DOING_CRON') && DOING_CRON) || (defined('DOING_AJAX') && DOING_AJAX && isset($_REQUEST['subaction']) && 'backupnow' == $_REQUEST['subaction']) ) {
255
+ remove_action('init', 'ganalyticator_stats_init');
256
+ // Appointments+ does the same; but provides a cleaner way to disable it
257
+ @define('APP_GCAL_DISABLE', true);
258
+ }
259
+
260
+ }
261
+
262
+ // Cleans up temporary files found in the InfinteWP directory (and some in the site root - pclzip)
263
+ // Always cleans up temporary files over 12 hours old.
264
+ // With parameters, also cleans up those.
265
+ // Also cleans out old job data older than 12 hours old (immutable value)
266
+ // include_cachelist also looks to match any files of cached file analysis data
267
+ public function clean_temporary_files($match = '', $older_than = 43200, $include_cachelist = false) {
268
+ # Clean out old job data
269
+ if ($older_than > 10000) {
270
+ global $wpdb;
271
+
272
+ $all_jobs = $wpdb->get_results("SELECT option_name, option_value FROM $wpdb->options WHERE option_name LIKE 'IWP_jobdata_%'", ARRAY_A);
273
+ foreach ($all_jobs as $job) {
274
+ $val = maybe_unserialize($job['option_value']);
275
+ # TODO: Can simplify this after a while (now all jobs use job_time_ms) - 1 Jan 2014
276
+ $delete = false;
277
+ if (!empty($val['next_increment_start_scheduled_for'])) {
278
+ if (time() > $val['next_increment_start_scheduled_for'] + 86400) $delete = true;
279
+ } elseif (!empty($val['backup_time_ms']) && time() > $val['backup_time_ms'] + 86400) {
280
+ $delete = true;
281
+ } elseif (!empty($val['job_time_ms']) && time() > $val['job_time_ms'] + 86400) {
282
+ $delete = true;
283
+ } elseif (!empty($val['job_type']) && 'backup' != $val['job_type'] && empty($val['backup_time_ms']) && empty($val['job_time_ms'])) {
284
+ $delete = true;
285
+ }
286
+ if ($delete) delete_option($job['option_name']);
287
+ }
288
+ }
289
+ $iwp_backup_dir = $this->backups_dir_location();
290
+ $now_time=time();
291
+ if ($handle = opendir($iwp_backup_dir)) {
292
+ while (false !== ($entry = readdir($handle))) {
293
+ $manifest_match = preg_match("/^udmanifest$match\.json$/i", $entry);
294
+ // This match is for files created internally by zipArchive::addFile
295
+ $ziparchive_match = preg_match("/$match([0-9]+)?\.zip\.tmp\.([A-Za-z0-9]){6}?$/i", $entry);
296
+ // 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.
297
+ $binzip_match = preg_match("/^zi([A-Za-z0-9]){6}$/", $entry);
298
+ $cachelist_match = ($include_cachelist) ? preg_match("/$match-cachelist-.*.tmp$/i", $entry) : false;
299
+ $browserlog_match = preg_match('/^log\.[0-9a-f]+-browser\.txt$/', $entry);
300
+ # Temporary files from the database dump process - not needed, as is caught by the catch-all
301
+ # $table_match = preg_match("/${match}-table-(.*)\.table(\.tmp)?\.gz$/i", $entry);
302
+ # The gz goes in with the txt, because we *don't* want to reap the raw .txt files
303
+ if ((preg_match("/$match\.(tmp|table|txt\.gz)(\.gz)?$/i", $entry) || $cachelist_match || $ziparchive_match || $binzip_match || $manifest_match || $browserlog_match) && is_file($iwp_backup_dir.'/'.$entry)) {
304
+ // 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
305
+ if (($match && ($ziparchive_match || $binzip_match || $cachelist_match || $manifest_match || 0 == $older_than) && $now_time-filemtime($iwp_backup_dir.'/'.$entry) >= $older_than) || $now_time-filemtime($iwp_backup_dir.'/'.$entry)>43200) {
306
+ $this->log("Deleting old temporary file: $entry");
307
+ @unlink($iwp_backup_dir.'/'.$entry);
308
+ }
309
+ }
310
+ }
311
+ @closedir($handle);
312
+ }
313
+ }
314
+
315
+ public function backup_time_nonce($nonce = false) {
316
+ $this->job_time_ms = microtime(true);
317
+ $this->backup_time = time();
318
+ if (false === $nonce) $nonce = substr(md5(time().rand()), 20);
319
+ $this->nonce = $nonce;
320
+ return $nonce;
321
+ }
322
+
323
+ public function get_wordpress_version() {
324
+ static $got_wp_version = false;
325
+ if (!$got_wp_version) {
326
+ global $wp_version;
327
+ @include(ABSPATH.WPINC.'/version.php');
328
+ $got_wp_version = $wp_version;
329
+ }
330
+ return $got_wp_version;
331
+ }
332
+
333
+ /**
334
+ * Opens the log file, writes a standardised header, and stores the resulting name and handle in the class variables logfile_name/logfile_handle/opened_log_time (and possibly backup_is_already_complete)
335
+ *
336
+ * @param string $nonce - Used in the log file name to distinguish it from other log files. Should be the job nonce.
337
+ * @returns void
338
+ */
339
+ public function logfile_open($nonce) {
340
+
341
+ $iwp_backup_dir = $this->backups_dir_location();
342
+ $this->logfile_name = $iwp_backup_dir."/log.$nonce.txt";
343
+
344
+ if (file_exists($this->logfile_name)) {
345
+ $seek_to = max((filesize($this->logfile_name) - 340), 1);
346
+ $handle = fopen($this->logfile_name, 'r');
347
+ if (is_resource($handle)) {
348
+ // Returns 0 on success
349
+ if (0 === @fseek($handle, $seek_to)) {
350
+ $bytes_back = filesize($this->logfile_name) - $seek_to;
351
+ # Return to the end of the file
352
+ $read_recent = fread($handle, $bytes_back);
353
+ # Move to end of file - ought to be redundant
354
+ if (false !== strpos($read_recent, ') The backup apparently succeeded') && false !== strpos($read_recent, 'and is now complete')) {
355
+ $this->backup_is_already_complete = true;
356
+ }
357
+ }
358
+ fclose($handle);
359
+ }
360
+ }
361
+
362
+ $this->logfile_handle = fopen($this->logfile_name, 'a');
363
+
364
+ $this->opened_log_time = microtime(true);
365
+
366
+ $this->write_log_header(array($this, 'log'));
367
+
368
+ }
369
+
370
+ /**
371
+ * Writes a standardised header to the log file, using the specified logging function, which needs to be compatible with (or to be) InfiniteWP::log()
372
+ *
373
+ * @param callable $logging_function
374
+ */
375
+ public function write_log_header($logging_function) {
376
+
377
+ global $wpdb;
378
+
379
+ $iwp_backup_dir = $this->backups_dir_location();
380
+
381
+ call_user_func($logging_function, 'Opened log file at time: '.date('r').' on '.network_site_url());
382
+
383
+ $wp_version = $this->get_wordpress_version();
384
+ $mysql_version = $wpdb->db_version();
385
+ $safe_mode = $this->detect_safe_mode();
386
+
387
+ $memory_limit = ini_get('memory_limit');
388
+ $memory_usage = round(@memory_get_usage(false)/1048576, 1);
389
+ $memory_usage2 = round(@memory_get_usage(true)/1048576, 1);
390
+
391
+ // Attempt to raise limit to avoid false positives
392
+ @set_time_limit(IWP_SET_TIME_LIMIT);
393
+ $max_execution_time = (int)@ini_get("max_execution_time");
394
+
395
+ $logline = "InfiniteWP WordPress plugin (https://infinitewp.com): ".$this->version." WP: ".$wp_version." PHP: ".phpversion()." (".PHP_SAPI.", ".@php_uname().") MySQL: $mysql_version WPLANG: ".get_locale()." Server: ".$_SERVER["SERVER_SOFTWARE"]." safe_mode: $safe_mode max_execution_time: $max_execution_time memory_limit: $memory_limit (used: ${memory_usage}M | ${memory_usage2}M) multisite: ".(is_multisite() ? 'Y' : 'N')." openssl: ".(defined('OPENSSL_VERSION_TEXT') ? OPENSSL_VERSION_TEXT : 'N')." mcrypt: ".(function_exists('mcrypt_encrypt') ? 'Y' : 'N')." LANG: ".getenv('LANG')." ZipArchive::addFile: ";
396
+
397
+ // method_exists causes some faulty PHP installations to segfault, leading to support requests
398
+ if (version_compare(phpversion(), '5.2.0', '>=') && extension_loaded('zip')) {
399
+ $logline .= 'Y';
400
+ } else {
401
+ $logline .= (class_exists('ZipArchive') && method_exists('ZipArchive', 'addFile')) ? "Y" : "N";
402
+ }
403
+
404
+ if (0 === $this->current_resumption) {
405
+ $memlim = $this->memory_check_current();
406
+ if ($memlim<65 && $memlim>0) {
407
+ $this->log(sprintf(__('The amount of memory (RAM) allowed for PHP is very low (%s Mb) - you should increase it to avoid failures due to insufficient memory (consult your web hosting company for more help)', 'InfiniteWP'), round($memlim, 1)), 'warning', 'lowram');
408
+ }
409
+ if ($max_execution_time>0 && $max_execution_time<20) {
410
+ call_user_func($logging_function, sprintf(__('The amount of time allowed for WordPress plugins to run is very low (%s seconds) - you should increase it to avoid backup failures due to time-outs (consult your web hosting company for more help - it is the max_execution_time PHP setting; the recommended value is %s seconds or more)', 'InfiniteWP'), $max_execution_time, 90), 'warning', 'lowmaxexecutiontime');
411
+ }
412
+
413
+ }
414
+
415
+ call_user_func($logging_function, $logline);
416
+
417
+ $hosting_bytes_free = $this->get_hosting_disk_quota_free();
418
+ if (is_array($hosting_bytes_free)) {
419
+ $perc = round(100*$hosting_bytes_free[1]/(max($hosting_bytes_free[2], 1)), 1);
420
+ $quota_free = ' / '.sprintf('Free disk space in account: %s (%s used)', round($hosting_bytes_free[3]/1048576, 1)." MB", "$perc %");
421
+ if ($hosting_bytes_free[3] < 1048576*50) {
422
+ $quota_free_mb = round($hosting_bytes_free[3]/1048576, 1);
423
+ call_user_func($logging_function, sprintf(__('Your free space in your hosting account is very low - only %s Mb remain', 'InfiniteWP'), $quota_free_mb), 'warning', 'lowaccountspace'.$quota_free_mb);
424
+ }
425
+ } else {
426
+ $quota_free = '';
427
+ }
428
+
429
+ $disk_free_space = @disk_free_space($iwp_backup_dir);
430
+ # == rather than === here is deliberate; support experience shows that a result of (int)0 is not reliable. i.e. 0 can be returned when the real result should be false.
431
+ if ($disk_free_space == false) {
432
+ call_user_func($logging_function, "Free space on disk containing InfiniteWP's temporary directory: Unknown".$quota_free);
433
+ } else {
434
+ call_user_func($logging_function, "Free space on disk containing InfiniteWP's temporary directory: ".round($disk_free_space/1048576, 1)." MB".$quota_free);
435
+ $disk_free_mb = round($disk_free_space/1048576, 1);
436
+ if ($disk_free_space < 50*1048576) call_user_func($logging_function, sprintf(__('Your free disk space is very low - only %s Mb remain', 'InfiniteWP'), round($disk_free_space/1048576, 1)), 'warning', 'lowdiskspace'.$disk_free_mb);
437
+ }
438
+
439
+ }
440
+
441
+ /* Logs the given line, adding (relative) time stamp and newline
442
+ Note these subtleties of log handling:
443
+ - Messages at level 'error' are not logged to file - it is assumed that a separate call to log() at another level will take place. This is because at level 'error', messages are translated; whereas the log file is for developers who may not know the translated language. Messages at level 'error' are for the user.
444
+ - Messages at level 'error' do not persist through the job (they are only saved with save_backup_history(), and never restored from there - so only the final save_backup_history() errors persist); we presume that either a) they will be cleared on the next attempt, or b) they will occur again on the final attempt (at which point they will go to the user). But...
445
+ - ... messages at level 'warning' persist. These are conditions that are unlikely to be cleared, not-fatal, but the user should be informed about. The $uniq_id field (which should not be numeric) can then be used for warnings that should only be logged once
446
+ $skip_dblog = true is suitable when there's a risk of excessive logging, and the information is not important for the user to see in the browser on the settings page
447
+
448
+ The uniq_id field is also used with PHP event detection - it is set then to 'php_event' - which is useful for anything hooking the action to detect
449
+ */
450
+
451
+ public function verify_free_memory($how_many_bytes_needed) {
452
+ // This returns in MB
453
+ $memory_limit = $this->memory_check_current();
454
+ if (!is_numeric($memory_limit)) return false;
455
+ $memory_limit = $memory_limit * 1048576;
456
+ $memory_usage = round(@memory_get_usage(false)/1048576, 1);
457
+ $memory_usage2 = round(@memory_get_usage(true)/1048576, 1);
458
+ if ($memory_limit - $memory_usage > $how_many_bytes_needed && $memory_limit - $memory_usage2 > $how_many_bytes_needed) return true;
459
+ return false;
460
+ }
461
+
462
+ /*
463
+ $line - the log line
464
+ $level - the log level: notice, warning, error. If suffixed with a hypen and a destination, then the default destination is changed too.
465
+ $uniq_id - (string)each of these will only be logged once
466
+ $skip_dblog - if true, then do not write to the database
467
+ */
468
+ public function log($line, $level = 'notice', $uniq_id = false, $skip_dblog = false) {
469
+
470
+ $destination = 'default';
471
+ if (preg_match('/^([a-z]+)-([a-z]+)$/', $level, $matches)) {
472
+ $level = $matches[1];
473
+ $destination = $matches[2];
474
+ }
475
+
476
+ if ('error' == $level || 'warning' == $level) {
477
+ if ('error' == $level && 0 == $this->error_count()) $this->log('An error condition has occurred for the first time during this job');
478
+ if ($uniq_id) {
479
+ $this->errors[$uniq_id] = array('level' => $level, 'message' => $line);
480
+ } else {
481
+ $this->errors[] = array('level' => $level, 'message' => $line);
482
+ }
483
+ # Errors are logged separately
484
+ if ('error' == $level) return;
485
+ # It's a warning
486
+ $warnings = $this->jobdata_get('warnings');
487
+ if (!is_array($warnings)) $warnings = array();
488
+ if ($uniq_id) {
489
+ $warnings[$uniq_id] = $line;
490
+ } else {
491
+ $warnings[] = $line;
492
+ }
493
+ $this->jobdata_set('warnings', $warnings);
494
+ }
495
+
496
+ if (false === ($line = apply_filters('IWP_logline', $line, $this->nonce, $level, $uniq_id, $destination))) return;
497
+
498
+ if ($this->logfile_handle) {
499
+ # Record log file times relative to the backup start, if possible
500
+ $rtime = (!empty($this->job_time_ms)) ? microtime(true)-$this->job_time_ms : microtime(true)-$this->opened_log_time;
501
+ fwrite($this->logfile_handle, sprintf("%08.03f", round($rtime, 3))." (".$this->current_resumption.") ".(('notice' != $level) ? '['.ucfirst($level).'] ' : '').$line."\n");
502
+ }
503
+
504
+ switch ($this->jobdata_get('job_type')) {
505
+ case 'download':
506
+ // Download messages are keyed on the job (since they could be running several), and type
507
+ // The values of the POST array were checked before
508
+ $findex = empty($_POST['findex']) ? 0 : $_POST['findex'];
509
+
510
+ if (!empty($_POST['timestamp']) && !empty($_POST['type'])) $this->jobdata_set('dlmessage_'.$_POST['timestamp'].'_'.$_POST['type'].'_'.$findex, $line);
511
+
512
+ break;
513
+ case 'restore':
514
+ #if ('debug' != $level) echo $line."\n";
515
+ break;
516
+ default:
517
+ if (!$skip_dblog && 'debug' != $level) IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_lastmessage', $line." (".date_i18n('M d H:i:s').")", false);
518
+ break;
519
+ }
520
+
521
+ if (defined('IWP_BROWSERLOG_CONSOLELOG')) print $line."\n";
522
+ if (defined('IWP_BROWSERLOG_BROWSERLOG')) print htmlentities($line)."<br>\n";
523
+ }
524
+
525
+ public function log_removewarning($uniq_id) {
526
+ $warnings = $this->jobdata_get('warnings');
527
+ if (!is_array($warnings)) $warnings=array();
528
+ unset($warnings[$uniq_id]);
529
+ $this->jobdata_set('warnings', $warnings);
530
+ unset($this->errors[$uniq_id]);
531
+ }
532
+
533
+ # For efficiency, you can also feed false or a string into this function
534
+ public function log_wp_error($err, $echo = false, $logerror = false) {
535
+ if (false === $err) return false;
536
+ if (is_string($err)) {
537
+ $this->log("Error message: $err");
538
+ if ($echo) $this->log(sprintf(__('Error: %s', 'InfiniteWP'), $err), 'notice-warning');
539
+ if ($logerror) $this->log($err, 'error');
540
+ return false;
541
+ }
542
+ foreach ($err->get_error_messages() as $msg) {
543
+ $this->log("Error message: $msg");
544
+ if ($echo) $this->log(sprintf(__('Error: %s', 'InfiniteWP'), $msg), 'notice-warning');
545
+ if ($logerror) $this->log($msg, 'error');
546
+ }
547
+ $codes = $err->get_error_codes();
548
+ if (is_array($codes)) {
549
+ foreach ($codes as $code) {
550
+ $data = $err->get_error_data($code);
551
+ if (!empty($data)) {
552
+ $ll = (is_string($data)) ? $data : serialize($data);
553
+ $this->log("Error data (".$code."): ".$ll);
554
+ }
555
+ }
556
+ }
557
+ # Returns false so that callers can return with false more efficiently if they wish
558
+ return false;
559
+ }
560
+
561
+ public function get_max_packet_size() {
562
+ global $wpdb;
563
+ $mp = (int)$wpdb->get_var("SELECT @@session.max_allowed_packet");
564
+ # Default to 1MB
565
+ $mp = (is_numeric($mp) && $mp > 0) ? $mp : 1048576;
566
+ # 32MB
567
+ if ($mp < 33554432) {
568
+ $save = $wpdb->show_errors(false);
569
+ $req = @$wpdb->query("SET GLOBAL max_allowed_packet=33554432");
570
+ $wpdb->show_errors($save);
571
+ if (!$req) $this->log("Tried to raise max_allowed_packet from ".round($mp/1048576,1)." MB to 32 MB, but failed (".$wpdb->last_error.", ".serialize($req).")");
572
+ $mp = (int)$wpdb->get_var("SELECT @@session.max_allowed_packet");
573
+ # Default to 1MB
574
+ $mp = (is_numeric($mp) && $mp > 0) ? $mp : 1048576;
575
+ }
576
+ $this->log("Max packet size: ".round($mp/1048576, 1)." MB");
577
+ return $mp;
578
+ }
579
+
580
+ # Q. Why is this abstracted into a separate function? A. To allow poedit and other parsers to pick up the need to translate strings passed to it (and not pick up all of those passed to log()).
581
+ # 1st argument = the line to be logged (obligatory)
582
+ # Further arguments = parameters for sprintf()
583
+ public function log_e() {
584
+ $args = func_get_args();
585
+ # Get first argument
586
+ $pre_line = array_shift($args);
587
+ # Log it whilst still in English
588
+ if (is_wp_error($pre_line)) {
589
+ $this->log_wp_error($pre_line);
590
+ } else {
591
+ // Now run (v)sprintf on it, using any remaining arguments. vsprintf = sprintf but takes an array instead of individual arguments
592
+ $this->log(vsprintf($pre_line, $args));
593
+ // This is slightly hackish, in that we have no way to use a different level or destination. In that case, the caller should instead call log() twice with different parameters, instead of using this convenience function.
594
+ $this->log(vsprintf(__($pre_line, 'InfiniteWP'), $args), 'notice-restore');
595
+ }
596
+ }
597
+
598
+ // This function is used by cloud methods to provide standardised logging, but more importantly to help us detect that meaningful activity took place during a resumption run, so that we can schedule further resumptions if it is worthwhile
599
+ public function record_uploaded_chunk($percent, $extra = '', $file_path = false, $log_it = true) {
600
+
601
+ // Touch the original file, which helps prevent overlapping runs
602
+ if ($file_path) touch($file_path);
603
+
604
+ // What this means in effect is that at least one of the files touched during the run must reach this percentage (so lapping round from 100 is OK)
605
+ if ($percent > 0.7 * ($this->current_resumption - max($this->jobdata_get('uploaded_lastreset'), 9))) $this->something_useful_happened();
606
+
607
+ // Log it
608
+ global $IWP_backup;
609
+ $log = (!empty($IWP_backup->current_service)) ? ucfirst($IWP_backup->current_service)." chunked upload: $percent % uploaded" : '';
610
+ if ($log && $log_it) $this->log($log.(($extra) ? " ($extra)" : ''));
611
+ // If we are on an 'overtime' resumption run, and we are still meaningfully uploading, then schedule a new resumption
612
+ // Our definition of meaningful is that we must maintain an overall average of at least 0.7% per run, after allowing 9 runs for everything else to get going
613
+ // i.e. Max 100/.7 + 9 = 150 runs = 760 minutes = 12 hrs 40, if spaced at 5 minute intervals. However, our algorithm now decreases the intervals if it can, so this should not really come into play
614
+ // If they get 2 minutes on each run, and the file is 1GB, then that equals 10.2MB/120s = minimum 59KB/s upload speed required
615
+
616
+ $upload_status = $this->jobdata_get('uploading_substatus');
617
+ if (is_array($upload_status)) {
618
+ $upload_status['p'] = $percent/100;
619
+ $this->jobdata_set('uploading_substatus', $upload_status);
620
+ }
621
+
622
+ }
623
+
624
+ /**
625
+ * Method for helping remote storage methods to upload files in chunks without needing to duplicate all the overhead
626
+ *
627
+ * @param string $file the full path to the file
628
+ * @param object $caller the object to call back to do the actual network API calls; needs to have a chunked_upload() method.
629
+ * @param string $cloudpath this is passed back to the callback function; within this function, it is used only for logging
630
+ * @param string $logname the prefix used on log lines. Also passed back to the callback function.
631
+ * @param integer $chunk_size the size, in bytes, of each upload chunk
632
+ * @param integer $uploaded_size how many bytes have already been uploaded. This is passed back to the callback function; within this method, it is only used for logging.
633
+ * @param boolean $singletons when the file, given the chunk size, would only have one chunk, should that be uploaded (true), or instead should 1 be returned (false) ?
634
+ */
635
+ public function chunked_upload($caller, $file, $cloudpath, $logname, $chunk_size, $uploaded_size, $singletons = false) {
636
+
637
+ $fullpath = $this->backups_dir_location().'/'.$file;
638
+ $orig_file_size = filesize($fullpath);
639
+ if ($uploaded_size >= $orig_file_size) return true;
640
+
641
+ $chunks = floor($orig_file_size / $chunk_size);
642
+ // There will be a remnant unless the file size was exactly on a chunk boundary
643
+ if ($orig_file_size % $chunk_size > 0) $chunks++;
644
+
645
+ $this->log("$logname upload: $file (chunks: $chunks, size: $chunk_size) -> $cloudpath ($uploaded_size)");
646
+
647
+ if (0 == $chunks) {
648
+ return 1;
649
+ } elseif ($chunks < 2 && !$singletons) {
650
+ return 1;
651
+ } else {
652
+
653
+ if (false == ($fp = @fopen($fullpath, 'rb'))) {
654
+ $this->log("$logname: failed to open file: $fullpath");
655
+ $this->log("$file: ".sprintf(__('%s Error: Failed to open local file','InfiniteWP'), $logname), 'error');
656
+ return false;
657
+ }
658
+
659
+ $errors_so_far = 0;
660
+ $upload_start = 0;
661
+ $upload_end = -1;
662
+ $chunk_index = 1;
663
+ // The file size minus one equals the byte offset of the final byte
664
+ $upload_end = min($chunk_size - 1, $orig_file_size - 1);
665
+
666
+ while ($upload_start < $orig_file_size) {
667
+
668
+ // Don't forget the +1; otherwise the last byte is omitted
669
+ $upload_size = $upload_end - $upload_start + 1;
670
+
671
+ if ($upload_start) fseek($fp, $upload_start);
672
+
673
+ /*
674
+ * Valid return values for $uploaded are many, as the possibilities have grown over time.
675
+ * This could be cleaned up; but, it works, and it's not hugely complex.
676
+ *
677
+ * WP_Error : an error occured. The only permissible codes are: reduce_chunk_size (only on the first chunk), try_again
678
+ * (bool)true : What was requested was done
679
+ * (int)1 : What was requested was done, but do not log anything
680
+ * (bool)false : There was an error
681
+ * (Object) : Properties:
682
+ * (bool)log: (bool) - if absent, defaults to true
683
+ * (int)new_chunk_size: advisory amount for the chunk size for future chunks
684
+ * NOT IMPLEMENTED: (int)bytes_uploaded: Actual number of bytes uploaded (needs to be positive - o/w, should return an error instead)
685
+ *
686
+ * N.B. Consumers should consult $fp and $upload_start to get data; they should not re-calculate from $chunk_index, which is not an indicator of file position.
687
+ */
688
+ $uploaded = $caller->chunked_upload($file, $fp, $chunk_index, $upload_size, $upload_start, $upload_end, $orig_file_size);
689
+
690
+ // Try again? (Just once - added in 1.12.6 (can make more sophisticated if there is a need))
691
+ if (is_wp_error($uploaded) && 'try_again' == $uploaded->get_error_code()) {
692
+ // Arbitrary wait
693
+ sleep(3);
694
+ $this->log("Re-trying after wait (to allow apparent inconsistency to clear)");
695
+ $uploaded = $caller->chunked_upload($file, $fp, $chunk_index, $upload_size, $upload_start, $upload_end, $orig_file_size);
696
+ }
697
+
698
+ // This is the only other supported case of a WP_Error - otherwise, a boolean must be returned
699
+ // Note that this is only allowed on the first chunk. The caller is responsible to remember its chunk size if it uses this facility.
700
+ if (1 == $chunk_index && is_wp_error($uploaded) && 'reduce_chunk_size' == $uploaded->get_error_code() && false != ($new_chunk_size = $uploaded->get_error_data()) && is_numeric($new_chunk_size)) {
701
+ $this->log("Re-trying with new chunk size: ".$new_chunk_size);
702
+ return $this->chunked_upload($caller, $file, $cloudpath, $logname, $new_chunk_size, $uploaded_size, $singletons);
703
+ }
704
+
705
+ $uploaded_amount = $chunk_size;
706
+
707
+ /*
708
+ // Not using this approach for now. Instead, going to allow the consumers to increase the next chunk size
709
+ if (is_object($uploaded) && isset($uploaded->bytes_uploaded)) {
710
+ if (!$uploaded->bytes_uploaded) {
711
+ $uploaded = false;
712
+ } else {
713
+ $uploaded_amount = $uploaded->bytes_uploaded;
714
+ $uploaded = (!isset($uploaded->log) || $uploaded->log) ? true : 1;
715
+ }
716
+ }
717
+ */
718
+ if (is_object($uploaded) && isset($uploaded->new_chunk_size)) {
719
+ if ($uploaded->new_chunk_size >= 1048576) $new_chunk_size = $uploaded->new_chunk_size;
720
+ $uploaded = (!isset($uploaded->log) || $uploaded->log) ? true : 1;
721
+ }
722
+
723
+ if ($uploaded) {
724
+ $perc = round(100*($upload_end + 1)/max($orig_file_size, 1), 1);
725
+ // Consumers use a return value of (int)1 (rather than (bool)true) to suppress logging
726
+ $log_it = ($uploaded === 1) ? false : true;
727
+ $this->record_uploaded_chunk($perc, $chunk_index, $fullpath, $log_it);
728
+
729
+ // $uploaded_bytes = $upload_end + 1;
730
+
731
+ } else {
732
+ $errors_so_far++;
733
+ if ($errors_so_far >= 3) { @fclose($fp); return false; }
734
+ }
735
+
736
+ $chunk_index++;
737
+ $upload_start = $upload_end + 1;
738
+ $upload_end += isset($new_chunk_size) ? $uploaded_amount + $new_chunk_size - $chunk_size : $uploaded_amount;
739
+ $upload_end = min($upload_end, $orig_file_size - 1);
740
+
741
+ }
742
+
743
+ @fclose($fp);
744
+
745
+ if ($errors_so_far) return false;
746
+
747
+ // All chunks are uploaded - now combine the chunks
748
+ $ret = true;
749
+ if (method_exists($caller, 'chunked_upload_finish')) {
750
+ $ret = $caller->chunked_upload_finish($file);
751
+ if (!$ret) {
752
+ $this->log("$logname - failed to re-assemble chunks (".$e->getMessage().')');
753
+ $this->log(sprintf(__('%s error - failed to re-assemble chunks', 'InfiniteWP'), $logname), 'error');
754
+ }
755
+ }
756
+ if ($ret) {
757
+ $this->log("$logname upload: success");
758
+ # calls this itself
759
+ if (!is_a($caller, 'IWP_MMB_Addons_RemoteStorage_sftp')) $this->uploaded_file($file);
760
+ }
761
+
762
+ return $ret;
763
+
764
+ }
765
+ }
766
+
767
+ /**
768
+ * Provides a convenience function allowing remote storage methods to download a file in chunks, without duplicated overhead.
769
+ *
770
+ * @param string $file - The basename of the file being downloaded
771
+ * @param object $method - This remote storage method object needs to have a chunked_download() method to call back
772
+ * @param integer $remote_size - The size, in bytes, of the object being downloaded
773
+ * @param boolean $manually_break_up - Whether to break the download into multiple network operations (rather than just issuing a GET with a range beginning at the end of the already-downloaded data, and carrying on until it times out)
774
+ * @param * $passback - A value to pass back to the callback function
775
+ * @param integer $chunk_size - Break up the download into chunks of this number of bytes. Should be set if and only if $manually_break_up is true.
776
+ */
777
+ public function chunked_download($file, $method, $remote_size, $manually_break_up = false, $passback = null, $chunk_size = 1048576) {
778
+
779
+ try {
780
+
781
+ $fullpath = $this->backups_dir_location().'/'.$file;
782
+ $start_offset = file_exists($fullpath) ? filesize($fullpath) : 0;
783
+
784
+ if ($start_offset >= $remote_size) {
785
+ $this->log("File is already completely downloaded ($start_offset/$remote_size)");
786
+ return true;
787
+ }
788
+
789
+ // Some more remains to download - so let's do it
790
+ // N.B. We use ftell(), which precludes us from using open in append-only ('a') mode - see https://php.net/manual/en/function.fopen.php
791
+ if (!($fh = fopen($fullpath, 'c'))) {
792
+ $this->log("Error opening local file: $fullpath");
793
+ $this->log($file.": ".__("Error",'InfiniteWP').": ".__('Error opening local file: Failed to download','InfiniteWP'), 'error');
794
+ return false;
795
+ }
796
+
797
+ $last_byte = ($manually_break_up) ? min($remote_size, $start_offset + $chunk_size ) : $remote_size;
798
+
799
+ # This only affects logging
800
+ $expected_bytes_delivered_so_far = true;
801
+
802
+ while ($start_offset < $remote_size) {
803
+ $headers = array();
804
+ // If resuming, then move to the end of the file
805
+
806
+ $requested_bytes = $last_byte-$start_offset;
807
+
808
+ if ($expected_bytes_delivered_so_far) {
809
+ $this->log("$file: local file is status: $start_offset/$remote_size bytes; requesting next $requested_bytes bytes");
810
+ } else {
811
+ $this->log("$file: local file is status: $start_offset/$remote_size bytes; requesting next chunk (${start_offset}-)");
812
+ }
813
+
814
+ if ($start_offset > 0 || $last_byte<$remote_size) {
815
+ fseek($fh, $start_offset);
816
+ // N.B. Don't alter this format without checking what relies upon it
817
+ $last_byte_start = $last_byte - 1;
818
+ $headers['Range'] = "bytes=$start_offset-$last_byte_start";
819
+ }
820
+
821
+ /*
822
+ * The most common method is for the remote storage module to return a string with the results in it. In that case, the final $fh parameter is unused. However, since not all SDKs have that option conveniently, it is also possible to use the file handle and write directly to that; in that case, the method can either return the number of bytes written, or (boolean)true to infer it from the new file *pointer*.
823
+ * The method is free to write/return as much data as it pleases.
824
+ */
825
+ $ret = $method->chunked_download($file, $headers, $passback, $fh);
826
+ if (true === $ret) {
827
+ clearstatcache();
828
+ // Some SDKs (including AWS/S3) close the resource
829
+ // N.B. We use ftell(), which precludes us from using open in append-only ('a') mode - see https://php.net/manual/en/function.fopen.php
830
+ if (is_resource($fh)) {
831
+ $ret = ftell($fh);
832
+ } else {
833
+ $ret = filesize($fullpath);
834
+ // fseek returns - on success
835
+ if (false == ($fh = fopen($fullpath, 'c')) || 0 !== fseek($fh, $ret)) {
836
+ $this->log("Error opening local file: $fullpath");
837
+ $this->log($file.": ".__("Error",'InfiniteWP').": ".__('Error opening local file: Failed to download','InfiniteWP'), 'error');
838
+ return false;
839
+ }
840
+ }
841
+ if (is_integer($ret)) $ret -= $start_offset;
842
+ }
843
+
844
+ // Note that this covers a false code returned either by chunked_download() or by ftell.
845
+ if (false === $ret) return false;
846
+
847
+ $returned_bytes = is_integer($ret) ? $ret : strlen($ret);
848
+
849
+ if ($returned_bytes > $requested_bytes || $returned_bytes < $requested_bytes - 1) $expected_bytes_delivered_so_far = false;
850
+
851
+ if (!is_integer($ret) && !fwrite($fh, $ret)) throw new Exception('Write failure (start offset: '.$start_offset.', bytes: '.strlen($ret).'; requested: '.$requested_bytes.')');
852
+
853
+ clearstatcache();
854
+ $start_offset = ftell($fh);
855
+ $last_byte = ($manually_break_up) ? min($remote_size, $start_offset + $chunk_size) : $remote_size;
856
+
857
+ }
858
+
859
+ } catch(Exception $e) {
860
+ $this->log('Error ('.get_class($e).') - failed to download the file ('.$e->getCode().', '.$e->getMessage().')');
861
+ $this->log("$file: ".__('Error - failed to download the file', 'InfiniteWP').' ('.$e->getCode().', '.$e->getMessage().')' ,'error');
862
+ return false;
863
+ }
864
+
865
+ fclose($fh);
866
+
867
+ return true;
868
+ }
869
+
870
+ /**
871
+ * This will decrypt an encryped db file
872
+ * @param string $fullpath This is the full path to the encrypted file location
873
+ * @param string $key This is the key (satling) to be used when decrypting
874
+ * @param boolean $to_temporary_file Use if the resulting file is not intended to be kept
875
+ * @return array This bring back an array of full decrypted path
876
+ */
877
+ public function decrypt($fullpath, $key, $to_temporary_file = false) {
878
+ $this->ensure_phpseclib('Crypt_Rijndael', 'Crypt/Rijndael');
879
+ if (defined('IWP_DECRYPTION_ENGINE')) {
880
+ if ('openssl' == IWP_DECRYPTION_ENGINE) {
881
+ $rijndael->setPreferredEngine(CRYPT_ENGINE_OPENSSL);
882
+ } elseif ('mcrypt' == IWP_DECRYPTION_ENGINE) {
883
+ $rijndael->setPreferredEngine(CRYPT_ENGINE_MCRYPT);
884
+ } elseif ('internal' == IWP_DECRYPTION_ENGINE) {
885
+ $rijndael->setPreferredEngine(CRYPT_ENGINE_INTERNAL);
886
+ }
887
+ }
888
+
889
+ //open file to read
890
+ if (false === ($file_handle = fopen($fullpath, 'rb'))) return false;
891
+
892
+ $decrypted_path = dirname($fullpath).'/decrypt_'.basename($fullpath).'.tmp';
893
+ //open new file from new path
894
+ if (false === ($decrypted_handle = fopen($decrypted_path, 'wb+'))) return false;
895
+
896
+ //setup encryption
897
+ $rijndael = new Crypt_Rijndael();
898
+ $rijndael->setKey($key);
899
+ $rijndael->disablePadding();
900
+ $rijndael->enableContinuousBuffer();
901
+
902
+ $file_size = filesize($fullpath);
903
+ $bytes_decrypted = 0;
904
+ $buffer_size = defined('IWP_CRYPT_BUFFER_SIZE') ? IWP_CRYPT_BUFFER_SIZE : 2097152;
905
+
906
+ //loop around the file
907
+ while ($bytes_decrypted < $file_size) {
908
+ //read buffer sized amount from file
909
+ if (false === ($file_part = fread($file_handle, $buffer_size))) return false;
910
+ //check to ensure padding is needed before decryption
911
+ $length = strlen($file_part);
912
+ if ($length % 16 != 0) {
913
+ $pad = 16 - ($length % 16);
914
+ $file_part = str_pad($file_part, $length + $pad, chr($pad));
915
+ // $file_part = str_pad($file_part, $length + $pad, chr(0));
916
+ }
917
+
918
+ $decrypted_data = $rijndael->decrypt($file_part);
919
+
920
+ $is_last_block = ($bytes_decrypted + strlen($decrypted_data) >= $file_size);
921
+
922
+ $write_bytes = min($file_size - $bytes_decrypted, strlen($decrypted_data));
923
+ if ($is_last_block) {
924
+ $is_padding = false;
925
+ $last_byte = ord(substr($decrypted_data, -1, 1));
926
+ if ($last_byte < 16) {
927
+ $is_padding = true;
928
+ for ($j = 1 ; $j<=$last_byte; $j++) {
929
+ if (substr($decrypted_data, -$j, 1) != chr($last_byte)) $is_padding = false;
930
+ }
931
+ }
932
+ if ($is_padding) {
933
+ $write_bytes -= $last_byte;
934
+ }
935
+ }
936
+
937
+ if (false === fwrite($decrypted_handle, $decrypted_data, $write_bytes)) return false;
938
+ $bytes_decrypted += $buffer_size;
939
+ }
940
+
941
+ //close the main file handle
942
+ fclose($decrypted_handle);
943
+ //close original file
944
+ fclose($file_handle);
945
+
946
+ //remove the crypt extension from the end as this causes issues when opening
947
+ $fullpath_new = preg_replace('/\.crypt$/', '', $fullpath, 1);
948
+ // //need to replace original file with tmp file
949
+
950
+ $fullpath_basename = basename($fullpath_new);
951
+
952
+ if ($to_temporary_file) {
953
+ return array(
954
+ 'fullpath' => $decrypted_path,
955
+ 'basename' => $fullpath_basename
956
+ );
957
+ }
958
+
959
+ if (false === rename($decrypted_path, $fullpath_new)) return false;
960
+
961
+ //need to send back the new decrypted path
962
+ $decrypt_return = array(
963
+ 'fullpath' => $fullpath_new,
964
+ 'basename' => $fullpath_basename
965
+ );
966
+
967
+ return $decrypt_return;
968
+ }
969
+
970
+ public function detect_safe_mode() {
971
+ return (@ini_get('safe_mode') && strtolower(@ini_get('safe_mode')) != "off") ? 1 : 0;
972
+ }
973
+
974
+ public function find_working_sqldump($logit = true, $cacheit = true) {
975
+
976
+ // The hosting provider may have explicitly disabled the popen or proc_open functions
977
+ if ($this->detect_safe_mode() || !function_exists('popen') || !function_exists('escapeshellarg')) {
978
+ if ($cacheit) $this->jobdata_set('binsqldump', false);
979
+ return false;
980
+ }
981
+ $existing = $this->jobdata_get('binsqldump', null);
982
+ # Theoretically, we could have moved machines, due to a migration
983
+ if (null !== $existing && (!is_string($existing) || @is_executable($existing))) return $existing;
984
+
985
+ $iwp_backup_dir = $this->backups_dir_location();
986
+ global $wpdb;
987
+ $table_name = $wpdb->get_blog_prefix().'options';
988
+ $tmp_file = md5(time().rand()).".sqltest.tmp";
989
+ $pfile = md5(time().rand()).'.tmp';
990
+ file_put_contents($iwp_backup_dir.'/'.$pfile, "[mysqldump]\npassword=".DB_PASSWORD."\n");
991
+
992
+ $result = false;
993
+ foreach (explode(',', IWP_MYSQLDUMP_EXECUTABLE) as $potsql) {
994
+
995
+ if (!@is_executable($potsql)) continue;
996
+
997
+ if ($logit) $this->log("Testing: $potsql");
998
+
999
+ if (strtolower(substr(PHP_OS, 0, 3)) == 'win') {
1000
+ $exec = "cd ".escapeshellarg(str_replace('/', '\\', $iwp_backup_dir))." & ";
1001
+ $siteurl = "'siteurl'";
1002
+ if (false !== strpos($potsql, ' ')) $potsql = '"'.$potsql.'"';
1003
+ } else {
1004
+ $exec = "cd ".escapeshellarg($iwp_backup_dir)."; ";
1005
+ $siteurl = "\\'siteurl\\'";
1006
+ if (false !== strpos($potsql, ' ')) $potsql = "'$potsql'";
1007
+ }
1008
+
1009
+ $exec .= "$potsql --defaults-file=$pfile --max_allowed_packet=1M --quote-names --add-drop-table --skip-comments --skip-set-charset --allow-keywords --dump-date --extended-insert --where=option_name=$siteurl --user=".escapeshellarg(DB_USER)." --host=".escapeshellarg(DB_HOST)." ".DB_NAME." ".escapeshellarg($table_name)."";
1010
+
1011
+ $handle = popen($exec, "r");
1012
+ if ($handle) {
1013
+ if (!feof($handle)) {
1014
+ $output = fread($handle, 8192);
1015
+ if ($output && $logit) {
1016
+ $log_output = (strlen($output) > 512) ? substr($output, 0, 512).' (truncated - '.strlen($output).' bytes total)' : $output;
1017
+ $this->log("Output: ".str_replace("\n", '\\n', trim($log_output)));
1018
+ }
1019
+ } else {
1020
+ $output = '';
1021
+ }
1022
+ $ret = pclose($handle);
1023
+ if ($ret !=0) {
1024
+ if ($logit) {
1025
+ $this->log("Binary mysqldump: error (code: $ret)");
1026
+ }
1027
+ } else {
1028
+ // $dumped = file_get_contents($iwp_backup_dir.'/'.$tmp_file, false, null, 0, 4096);
1029
+ if (stripos($output, 'insert into') !== false) {
1030
+ if ($logit) $this->log("Working binary mysqldump found: $potsql");
1031
+ $result = $potsql;
1032
+ break;
1033
+ }
1034
+ }
1035
+ } else {
1036
+ if ($logit) $this->log("Error: popen failed");
1037
+ }
1038
+ }
1039
+
1040
+ @unlink($iwp_backup_dir.'/'.$pfile);
1041
+ @unlink($iwp_backup_dir.'/'.$tmp_file);
1042
+
1043
+ if ($cacheit) $this->jobdata_set('binsqldump', $result);
1044
+
1045
+ return $result;
1046
+ }
1047
+
1048
+ // We require -@ and -u -r to work - which is the usual Linux binzip
1049
+ public function find_working_bin_zip($logit = true, $cacheit = true) {
1050
+ if ($this->detect_safe_mode()) return false;
1051
+ // The hosting provider may have explicitly disabled the popen or proc_open functions
1052
+ if (!function_exists('popen') || !function_exists('proc_open') || !function_exists('escapeshellarg')) {
1053
+ if ($cacheit) $this->jobdata_set('binzip', false);
1054
+ return false;
1055
+ }
1056
+
1057
+ $existing = $this->jobdata_get('binzip', null);
1058
+ # Theoretically, we could have moved machines, due to a migration
1059
+ if (null !== $existing && (!is_string($existing) || @is_executable($existing))) return $existing;
1060
+
1061
+ $iwp_backup_dir = $this->backups_dir_location();
1062
+ foreach (explode(',', IWP_ZIP_EXECUTABLE) as $potzip) {
1063
+ if (!@is_executable($potzip)) continue;
1064
+ if ($logit) $this->log("Testing: $potzip");
1065
+
1066
+ # Test it, see if it is compatible with Info-ZIP
1067
+ # If you have another kind of zip, then feel free to tell me about it
1068
+ @mkdir($iwp_backup_dir.'/binziptest/subdir1/subdir2', 0777, true);
1069
+
1070
+ if (!file_exists($iwp_backup_dir.'/binziptest/subdir1/subdir2')) return false;
1071
+
1072
+ file_put_contents($iwp_backup_dir.'/binziptest/subdir1/subdir2/test.html', '<html><body><a href="https://infinitewp.com">InfiniteWP is a great backup and restoration plugin for WordPress.</a></body></html>');
1073
+ @unlink($iwp_backup_dir.'/binziptest/test.zip');
1074
+ if (is_file($iwp_backup_dir.'/binziptest/subdir1/subdir2/test.html')) {
1075
+
1076
+ $exec = "cd ".escapeshellarg($iwp_backup_dir)."; $potzip";
1077
+ if (defined('IWP_BINZIP_OPTS') && IWP_BINZIP_OPTS) $exec .= ' '.IWP_BINZIP_OPTS;
1078
+ $exec .= " -v -u -r binziptest/test.zip binziptest/subdir1";
1079
+
1080
+ $all_ok=true;
1081
+ $handle = popen($exec, "r");
1082
+ if ($handle) {
1083
+ while (!feof($handle)) {
1084
+ $w = fgets($handle);
1085
+ if ($w && $logit) $this->log("Output: ".trim($w));
1086
+ }
1087
+ $ret = pclose($handle);
1088
+ if ($ret !=0) {
1089
+ if ($logit) $this->log("Binary zip: error (code: $ret)");
1090
+ $all_ok = false;
1091
+ }
1092
+ } else {
1093
+ if ($logit) $this->log("Error: popen failed");
1094
+ $all_ok = false;
1095
+ }
1096
+
1097
+ # Now test -@
1098
+ if (true == $all_ok) {
1099
+ file_put_contents($iwp_backup_dir.'/binziptest/subdir1/subdir2/test2.html', '<html><body><a href="https://infinitewp.com">InfiniteWP is a really great backup and restoration plugin for WordPress.</a></body></html>');
1100
+
1101
+ $exec = $potzip;
1102
+ if (defined('IWP_BINZIP_OPTS') && IWP_BINZIP_OPTS) $exec .= ' '.IWP_BINZIP_OPTS;
1103
+ $exec .= " -v -@ binziptest/test.zip";
1104
+
1105
+ $all_ok=true;
1106
+
1107
+ $descriptorspec = array(
1108
+ 0 => array('pipe', 'r'),
1109
+ 1 => array('pipe', 'w'),
1110
+ 2 => array('pipe', 'w')
1111
+ );
1112
+ $handle = proc_open($exec, $descriptorspec, $pipes, $iwp_backup_dir);
1113
+ if (is_resource($handle)) {
1114
+ if (!fwrite($pipes[0], "binziptest/subdir1/subdir2/test2.html\n")) {
1115
+ @fclose($pipes[0]);
1116
+ @fclose($pipes[1]);
1117
+ @fclose($pipes[2]);
1118
+ $all_ok = false;
1119
+ } else {
1120
+ fclose($pipes[0]);
1121
+ while (!feof($pipes[1])) {
1122
+ $w = fgets($pipes[1]);
1123
+ if ($w && $logit) $this->log("Output: ".trim($w));
1124
+ }
1125
+ fclose($pipes[1]);
1126
+
1127
+ while (!feof($pipes[2])) {
1128
+ $last_error = fgets($pipes[2]);
1129
+ if (!empty($last_error) && $logit) $this->log("Stderr output: ".trim($w));
1130
+ }
1131
+ fclose($pipes[2]);
1132
+
1133
+ $ret = proc_close($handle);
1134
+ if ($ret !=0) {
1135
+ if ($logit) $this->log("Binary zip: error (code: $ret)");
1136
+ $all_ok = false;
1137
+ }
1138
+
1139
+ }
1140
+
1141
+ } else {
1142
+ if ($logit) $this->log("Error: proc_open failed");
1143
+ $all_ok = false;
1144
+ }
1145
+
1146
+ }
1147
+
1148
+ // Do we now actually have a working zip? Need to test the created object using PclZip
1149
+ // If it passes, then remove dirs and then return $potzip;
1150
+ $found_first = false;
1151
+ $found_second = false;
1152
+ if ($all_ok && file_exists($iwp_backup_dir.'/binziptest/test.zip')) {
1153
+ if (function_exists('gzopen')) {
1154
+ if(!class_exists('PclZip')) require_once(ABSPATH.'/wp-admin/includes/class-pclzip.php');
1155
+ $zip = new PclZip($iwp_backup_dir.'/binziptest/test.zip');
1156
+ $foundit = 0;
1157
+ if (($list = $zip->listContent()) != 0) {
1158
+ foreach ($list as $obj) {
1159
+ if ($obj['filename'] && !empty($obj['stored_filename']) && 'binziptest/subdir1/subdir2/test.html' == $obj['stored_filename'] && $obj['size']==129) $found_first=true;
1160
+ if ($obj['filename'] && !empty($obj['stored_filename']) && 'binziptest/subdir1/subdir2/test2.html' == $obj['stored_filename'] && $obj['size']==136) $found_second=true;
1161
+ }
1162
+ }
1163
+ } else {
1164
+ // PclZip will die() if gzopen is not found
1165
+ // Obviously, this is a kludge - we assume it's working. We could, of course, just return false - but since we already know now that PclZip can't work, that only leaves ZipArchive
1166
+ $this->log("gzopen function not found; PclZip cannot be invoked; will assume that binary zip works if we have a non-zero file");
1167
+ if (filesize($iwp_backup_dir.'/binziptest/test.zip') > 0) {
1168
+ $found_first = true;
1169
+ $found_second = true;
1170
+ }
1171
+ }
1172
+ }
1173
+ $this->remove_binzip_test_files($iwp_backup_dir);
1174
+ if ($found_first && $found_second) {
1175
+ if ($logit) $this->log("Working binary zip found: $potzip");
1176
+ if ($cacheit) $this->jobdata_set('binzip', $potzip);
1177
+ return $potzip;
1178
+ }
1179
+
1180
+ }
1181
+ $this->remove_binzip_test_files($iwp_backup_dir);
1182
+ }
1183
+ if ($cacheit) $this->jobdata_set('binzip', false);
1184
+ return false;
1185
+ }
1186
+
1187
+ private function remove_binzip_test_files($iwp_backup_dir) {
1188
+ @unlink($iwp_backup_dir.'/binziptest/subdir1/subdir2/test.html');
1189
+ @unlink($iwp_backup_dir.'/binziptest/subdir1/subdir2/test2.html');
1190
+ @rmdir($iwp_backup_dir.'/binziptest/subdir1/subdir2');
1191
+ @rmdir($iwp_backup_dir.'/binziptest/subdir1');
1192
+ @unlink($iwp_backup_dir.'/binziptest/test.zip');
1193
+ @rmdir($iwp_backup_dir.'/binziptest');
1194
+ }
1195
+
1196
+ // This function is purely for timing - we just want to know the maximum run-time; not whether we have achieved anything during it
1197
+ public function record_still_alive() {
1198
+ // Update the record of maximum detected runtime on each run
1199
+ $time_passed = $this->jobdata_get('run_times');
1200
+ if (!is_array($time_passed)) $time_passed = array();
1201
+
1202
+ $time_this_run = microtime(true)-$this->opened_log_time;
1203
+ $time_passed[$this->current_resumption] = $time_this_run;
1204
+ $this->jobdata_set('run_times', $time_passed);
1205
+
1206
+ $resume_interval = $this->jobdata_get('resume_interval');
1207
+ if ($time_this_run + 30 > $resume_interval) {
1208
+ $new_interval = ceil($time_this_run + 30);
1209
+ set_site_transient('IWP_initial_resume_interval', (int)$new_interval, 8*86400);
1210
+ $this->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");
1211
+ $this->jobdata_set('resume_interval', $new_interval);
1212
+ }
1213
+
1214
+ }
1215
+
1216
+ public function something_useful_happened() {
1217
+
1218
+ $this->record_still_alive();
1219
+
1220
+ if (!$this->something_useful_happened) {
1221
+ $useful_checkin = $this->jobdata_get('useful_checkin');
1222
+ if (empty($useful_checkin) || $this->current_resumption > $useful_checkin) $this->jobdata_set('useful_checkin', $this->current_resumption);
1223
+ }
1224
+
1225
+ $this->something_useful_happened = true;
1226
+
1227
+ $iwp_backup_dir = $this->backups_dir_location();
1228
+ if (file_exists($iwp_backup_dir.'/deleteflag-'.$this->nonce.'.txt')) {
1229
+ $this->log("User request for abort: backup job will be immediately halted");
1230
+ @unlink($iwp_backup_dir.'/deleteflag-'.$this->nonce.'.txt');
1231
+ $this->backup_finish($this->current_resumption + 1, true, true, $this->current_resumption, true);
1232
+ die;
1233
+ }
1234
+
1235
+ if ($this->current_resumption >= 9 && false == $this->newresumption_scheduled) {
1236
+ $this->log("This is resumption ".$this->current_resumption.", but meaningful activity is still taking place; so a new one will be scheduled");
1237
+ // We just use max here to make sure we get a number at all
1238
+ $resume_interval = max($this->jobdata_get('resume_interval'), 75);
1239
+ // Don't consult the minimum here
1240
+ // if (!is_numeric($resume_interval) || $resume_interval<300) { $resume_interval = 300; }
1241
+ $schedule_for = time()+$resume_interval;
1242
+ $this->newresumption_scheduled = $schedule_for;
1243
+ wp_schedule_single_event($schedule_for, 'IWP_backup_resume', array($this->current_resumption + 1, $this->nonce));
1244
+ } else {
1245
+ $this->reschedule_if_needed();
1246
+ }
1247
+ }
1248
+
1249
+ public function option_filter_get($which) {
1250
+ global $wpdb;
1251
+ $row = $wpdb->get_row($wpdb->prepare("SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", $which));
1252
+ // Has to be get_row instead of get_var because of funkiness with 0, false, null values
1253
+ return (is_object($row)) ? $row->option_value : false;
1254
+ }
1255
+
1256
+ public function parse_filename($filename) {
1257
+ 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)) {
1258
+ return array(
1259
+ 'date' => strtotime($matches[1].' '.$matches[2]),
1260
+ 'nonce' => $matches[3],
1261
+ 'type' => $matches[4],
1262
+ 'index' => (empty($matches[5]) ? 0 : $matches[5]-1),
1263
+ 'extension' => $matches[6]);
1264
+ } else {
1265
+ return false;
1266
+ }
1267
+ }
1268
+
1269
+ /**
1270
+ * Indicate which checksums to take for backup files. Abstracted for extensibilty and future changes.
1271
+ *
1272
+ * @returns array - a list of hashing algorithms, as understood by PHP's hash() function
1273
+ */
1274
+ public function which_checksums() {
1275
+ return apply_filters('IWP_which_checksums', array('sha1', 'sha256'));
1276
+ }
1277
+
1278
+ // Pretty printing
1279
+ public function printfile($description, $history, $entity, $checksums, $jobdata, $smaller=false) {
1280
+
1281
+ if (empty($history[$entity])) return;
1282
+
1283
+ if ($smaller) {
1284
+ $pfiles = "<strong>".$description." (".sprintf(__('files: %s', 'InfiniteWP'), count($history[$entity])).")</strong><br>\n";
1285
+ } else {
1286
+ $pfiles = "<h3>".$description." (".sprintf(__('files: %s', 'InfiniteWP'), count($history[$entity])).")</h3>\n\n";
1287
+ }
1288
+
1289
+ $pfiles .= '<ul>';
1290
+ $files = $history[$entity];
1291
+ if (is_string($files)) $files = array($files);
1292
+
1293
+ foreach ($files as $ind => $file) {
1294
+
1295
+ $op = htmlspecialchars($file)."\n";
1296
+ $skey = $entity.((0 == $ind) ? '' : $ind).'-size';
1297
+
1298
+ $meta = '';
1299
+ if ('db' == substr($entity, 0, 2) && 'db' != $entity) {
1300
+ $dind = substr($entity, 2);
1301
+ if (is_array($jobdata) && !empty($jobdata['backup_database']) && is_array($jobdata['backup_database']) && !empty($jobdata['backup_database'][$dind]) && is_array($jobdata['backup_database'][$dind]['dbinfo']) && !empty($jobdata['backup_database'][$dind]['dbinfo']['host'])) {
1302
+ $dbinfo = $jobdata['backup_database'][$dind]['dbinfo'];
1303
+ $meta .= sprintf(__('External database (%s)', 'InfiniteWP'), $dbinfo['user'].'@'.$dbinfo['host'].'/'.$dbinfo['name'])."<br>";
1304
+ }
1305
+ }
1306
+ if (isset($history[$skey])) $meta .= sprintf(__('Size: %s MB', 'InfiniteWP'), round($history[$skey]/1048576, 1));
1307
+ $ckey = $entity.$ind;
1308
+ foreach ($checksums as $ck) {
1309
+ $ck_plain = false;
1310
+ if (isset($history['checksums'][$ck][$ckey])) {
1311
+ $meta .= (($meta) ? ', ' : '').sprintf(__('%s checksum: %s', 'InfiniteWP'), strtoupper($ck), $history['checksums'][$ck][$ckey]);
1312
+ $ck_plain = true;
1313
+ }
1314
+ if (isset($history['checksums'][$ck][$ckey.'.crypt'])) {
1315
+ if ($ck_plain) $meta .= ' '.__('(when decrypted)');
1316
+ $meta .= (($meta) ? ', ' : '').sprintf(__('%s checksum: %s', 'InfiniteWP'), strtoupper($ck), $history['checksums'][$ck][$ckey.'.crypt']);
1317
+ }
1318
+ }
1319
+
1320
+ $fileinfo = apply_filters("IWP_fileinfo_$entity", array(), $ind);
1321
+ if (is_array($fileinfo) && !empty($fileinfo)) {
1322
+ if (isset($fileinfo['html'])) {
1323
+ $meta .= $fileinfo['html'];
1324
+ }
1325
+ }
1326
+
1327
+ #if ($meta) $meta = " ($meta)";
1328
+ if ($meta) $meta = "<br><em>$meta</em>";
1329
+ $pfiles .= '<li>'.$op.$meta."\n</li>\n";
1330
+ }
1331
+
1332
+ $pfiles .= "</ul>\n";
1333
+
1334
+ return $pfiles;
1335
+
1336
+ }
1337
+
1338
+ // This important function returns a list of file entities that can potentially be backed up (subject to users settings), and optionally further meta-data about them
1339
+ public function get_backupable_file_entities($include_others = true, $full_info = false) {
1340
+
1341
+ $wp_upload_dir = $this->wp_upload_dir();
1342
+
1343
+ if ($full_info) {
1344
+ $arr = array(
1345
+ 'plugins' => array('path' => untrailingslashit(WP_PLUGIN_DIR), 'description' => __('Plugins','IWP')),
1346
+ 'themes' => array('path' => WP_CONTENT_DIR.'/themes', 'description' => __('Themes','IWP')),
1347
+ 'uploads' => array('path' => untrailingslashit($wp_upload_dir['basedir']), 'description' => __('Uploads','IWP'))
1348
+ );
1349
+ } else {
1350
+ $arr = array(
1351
+ 'plugins' => untrailingslashit(WP_PLUGIN_DIR),
1352
+ 'themes' => WP_CONTENT_DIR.'/themes',
1353
+ 'uploads' => untrailingslashit($wp_upload_dir['basedir'])
1354
+ );
1355
+ }
1356
+
1357
+ $arr = apply_filters('IWP_backupable_file_entities', $arr, $full_info);
1358
+
1359
+ // We then add 'others' on to the end
1360
+ if ($include_others) {
1361
+ if ($full_info) {
1362
+ $arr['others'] = array('path' => WP_CONTENT_DIR, 'description' => __('Others', 'IWP'));
1363
+ } else {
1364
+ $arr['others'] = WP_CONTENT_DIR;
1365
+ }
1366
+ }
1367
+
1368
+ // Entries that should be added after 'others'
1369
+ $arr = apply_filters('IWP_backupable_file_entities_final', $arr, $full_info);
1370
+
1371
+ return $arr;
1372
+
1373
+ }
1374
+
1375
+ # This is just a long-winded way of forcing WP to 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)
1376
+ public function filter_IWP_backup_history($v) {
1377
+ global $wpdb;
1378
+ $row = $wpdb->get_row( $wpdb->prepare("SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", 'IWP_backup_history' ) );
1379
+ if (is_object($row )) return maybe_unserialize($row->option_value);
1380
+ return false;
1381
+ }
1382
+
1383
+ public function php_error_to_logline($errno, $errstr, $errfile, $errline) {
1384
+ switch ($errno) {
1385
+ case 1: $e_type = 'E_ERROR'; break;
1386
+ case 2: $e_type = 'E_WARNING'; break;
1387
+ case 4: $e_type = 'E_PARSE'; break;
1388
+ case 8: $e_type = 'E_NOTICE'; break;
1389
+ case 16: $e_type = 'E_CORE_ERROR'; break;
1390
+ case 32: $e_type = 'E_CORE_WARNING'; break;
1391
+ case 64: $e_type = 'E_COMPILE_ERROR'; break;
1392
+ case 128: $e_type = 'E_COMPILE_WARNING'; break;
1393
+ case 256: $e_type = 'E_USER_ERROR'; break;
1394
+ case 512: $e_type = 'E_USER_WARNING'; break;
1395
+ case 1024: $e_type = 'E_USER_NOTICE'; break;
1396
+ case 2048: $e_type = 'E_STRICT'; break;
1397
+ case 4096: $e_type = 'E_RECOVERABLE_ERROR'; break;
1398
+ case 8192: $e_type = 'E_DEPRECATED'; break;
1399
+ case 16384: $e_type = 'E_USER_DEPRECATED'; break;
1400
+ case 30719: $e_type = 'E_ALL'; break;
1401
+ default: $e_type = "E_UNKNOWN ($errno)"; break;
1402
+ }
1403
+
1404
+ if (!is_string($errstr)) $errstr = serialize($errstr);
1405
+
1406
+ if (0 === strpos($errfile, ABSPATH)) $errfile = substr($errfile, strlen(ABSPATH));
1407
+
1408
+ if ('E_DEPRECATED' == $e_type && !empty($this->no_deprecation_warnings)) {
1409
+ return false;
1410
+ }
1411
+
1412
+ return "PHP event: code $e_type: $errstr (line $errline, $errfile)";
1413
+
1414
+ }
1415
+
1416
+ public function php_error($errno, $errstr, $errfile, $errline) {
1417
+ if (0 == error_reporting()) return true;
1418
+ $logline = $this->php_error_to_logline($errno, $errstr, $errfile, $errline);
1419
+ if (false !== $logline) $this->log($logline, 'notice', 'php_event');
1420
+ // Pass it up the chain
1421
+ return $this->error_reporting_stop_when_logged;
1422
+ }
1423
+
1424
+ public function backup_resume($resumption_no, $bnonce, $first_call = false) {
1425
+
1426
+ set_error_handler(array($this, 'php_error'), E_ALL & ~E_STRICT);
1427
+ if ($first_call) {
1428
+ $this->reschedule(10, $first_call);
1429
+ die;
1430
+ }
1431
+ $this->current_resumption = $resumption_no;
1432
+
1433
+ @set_time_limit(IWP_SET_TIME_LIMIT);
1434
+ @ignore_user_abort(true);
1435
+
1436
+ $runs_started = array();
1437
+ $time_now = microtime(true);
1438
+
1439
+ add_filter('pre_option_IWP_backup_history', array($this, 'filter_IWP_backup_history'));
1440
+
1441
+ // Restore state
1442
+ $resumption_extralog = '';
1443
+ $prev_resumption = $resumption_no - 1;
1444
+ $last_successful_resumption = -1;
1445
+ $job_type = 'backup';
1446
+
1447
+ if ($resumption_no > 0) {
1448
+
1449
+ $this->nonce = $bnonce;
1450
+ $this->backup_time = $this->jobdata_get('backup_time');
1451
+ $this->job_time_ms = $this->jobdata_get('job_time_ms');
1452
+
1453
+ # Get the warnings before opening the log file, as opening the log file may generate new ones (which then leads to $this->errors having duplicate entries when they are copied over below)
1454
+ $warnings = $this->jobdata_get('warnings');
1455
+
1456
+ $this->logfile_open($bnonce);
1457
+
1458
+ // Import existing warnings. The purpose of this is so that when save_backup_history() is called, it has a complete set - because job data expires quickly, whilst the warnings of the last backup run need to persist
1459
+ if (is_array($warnings)) {
1460
+ foreach ($warnings as $warning) {
1461
+ $this->errors[] = array('level' => 'warning', 'message' => $warning);
1462
+ }
1463
+ }
1464
+
1465
+ $runs_started = $this->jobdata_get('runs_started');
1466
+ if (!is_array($runs_started)) $runs_started=array();
1467
+ $time_passed = $this->jobdata_get('run_times');
1468
+ if (!is_array($time_passed)) $time_passed = array();
1469
+
1470
+ foreach ($time_passed as $run => $passed) {
1471
+ if (isset($runs_started[$run]) && $runs_started[$run] + $time_passed[$run] + 30 > $time_now) {
1472
+ // We don't want to increase the resumption if WP has started two copies of the same resumption off
1473
+ if ($run && $run == $resumption_no) {
1474
+ $increase_resumption = false;
1475
+ $this->log("It looks like WordPress's scheduler has started multiple instances of this resumption");
1476
+ } else {
1477
+ $increase_resumption = true;
1478
+ }
1479
+ $this->terminate_due_to_activity('check-in', round($time_now, 1), round($runs_started[$run] + $time_passed[$run], 1), $increase_resumption);
1480
+ }
1481
+ }
1482
+
1483
+ for ($i = 0; $i<=$prev_resumption; $i++) {
1484
+ if (isset($time_passed[$i])) $last_successful_resumption = $i;
1485
+ }
1486
+
1487
+ if (isset($time_passed[$prev_resumption])) {
1488
+ $resumption_extralog = ", previous check-in=".round($time_passed[$prev_resumption], 1)."s";
1489
+ } else {
1490
+ $this->no_checkin_last_time = true;
1491
+ }
1492
+
1493
+ // This is just a simple test to catch restorations of old backup sets where the backup includes a resumption of the backup job
1494
+ if ($time_now - $this->backup_time > 172800 && true == apply_filters('IWP_check_obsolete_backup', true, $time_now, $this)) {
1495
+
1496
+ // We have seen cases where the get_site_option() call that self::get_jobdata() relies on returns nothing, even though the data was there in the database. This appears to be sometimes reproducible for the people who get it, but stops being reproducible if they change their backup times - which suggests that they're having failures at times of extreme load. We can attempt to detect this case, and reschedule, instead of aborting.
1497
+ if (empty($this->backup_time) && empty($this->backup_is_already_complete) && !empty($this->logfile_name) && is_readable($this->logfile_name)) {
1498
+ $first_log_bit = file_get_contents($this->logfile_name, false, null, 0, 250);
1499
+ if (preg_match('/\(0\) Opened log file at time: (.*) on /', $first_log_bit, $matches)) {
1500
+ $first_opened = strtotime($matches[1]);
1501
+ // The value of 1000 seconds here is somewhat arbitrary; but allows for the problem to occur in ~ the first 15 minutes. In practice, the problem is extremely rare; if this does not catch it, we can tweak the algorithm.
1502
+ if (time() - $first_opened < 1000) {
1503
+ $this->log("This backup task (".$this->nonce.") failed to load its job data (possible database server malfunction), but appears to be only recently started: scheduling a fresh resumption in order to try again, and then ending this resumption ($time_now, ".$this->backup_time.") (existing jobdata keys: ".implode(', ', array_keys($this->jobdata)).")");
1504
+ $this->reschedule(120);
1505
+ die;
1506
+ }
1507
+ }
1508
+ }
1509
+
1510
+ $this->log("This backup task (".$this->nonce.") is either complete or began over 2 days ago: ending ($time_now, ".$this->backup_time.") (existing jobdata keys: ".implode(', ', array_keys($this->jobdata)).")");
1511
+ die;
1512
+ }
1513
+
1514
+ } else {
1515
+ $label = $this->jobdata_get('label');
1516
+ if ($label) $resumption_extralog = ", label=$label";
1517
+ }
1518
+
1519
+ $this->last_successful_resumption = $last_successful_resumption;
1520
+
1521
+ $runs_started[$resumption_no] = $time_now;
1522
+ if (!empty($this->backup_time)) $this->jobdata_set('runs_started', $runs_started);
1523
+
1524
+ // Schedule again, to run in 5 minutes again, in case we again fail
1525
+ // The actual interval can be increased (for future resumptions) by other code, if it detects apparent overlapping
1526
+ $resume_interval = max(intval($this->jobdata_get('resume_interval')), 100);
1527
+
1528
+ $btime = $this->backup_time;
1529
+
1530
+ $job_type = $this->jobdata_get('job_type');
1531
+
1532
+ do_action('IWP_resume_backup_'.$job_type);
1533
+
1534
+ $iwp_backup_dir = $this->backups_dir_location();
1535
+
1536
+ $time_ago = time()-$btime;
1537
+
1538
+ $this->log("Backup run: resumption=$resumption_no, nonce=$bnonce, begun at=$btime (${time_ago}s ago), job type=$job_type".$resumption_extralog);
1539
+
1540
+ // This works round a bizarre bug seen in one WP install, where delete_transient and wp_clear_scheduled_hook both took no effect, and upon 'resumption' the entire backup would repeat.
1541
+ // Argh. In fact, this has limited effect, as apparently (at least on another install seen), the saving of the updated transient via jobdata_set() also took no effect. Still, it does not hurt.
1542
+ if ($resumption_no >= 1 && 'finished' == $this->jobdata_get('jobstatus')) {
1543
+ $this->log('Terminate: This backup job is already finished (1).');
1544
+ die;
1545
+ } elseif ('backup' == $job_type && !empty($this->backup_is_already_complete)) {
1546
+ $this->jobdata_set('jobstatus', 'finished');
1547
+ $this->log('Terminate: This backup job is already finished (2).');
1548
+ die;
1549
+ }
1550
+
1551
+ if ($resumption_no > 0 && isset($runs_started[$prev_resumption])) {
1552
+ $our_expected_start = $runs_started[$prev_resumption] + $resume_interval;
1553
+ # If the previous run increased the resumption time, then it is timed from the end of the previous run, not the start
1554
+ if (isset($time_passed[$prev_resumption]) && $time_passed[$prev_resumption]>0) $our_expected_start += $time_passed[$prev_resumption];
1555
+ $our_expected_start = apply_filters('IWP_expected_start', $our_expected_start, $job_type);
1556
+ # More than 12 minutes late?
1557
+ if ($time_now > $our_expected_start + 720) {
1558
+ $this->log('Long time past since expected resumption time: approx expected='.round($our_expected_start,1).", now=".round($time_now, 1).", diff=".round($time_now-$our_expected_start,1));
1559
+ $this->log(__('Your website is visited infrequently and InfiniteWP is not getting the resources it hoped for; please set Uptime monitor', 'InfiniteWP'), 'warning', 'infrequentvisits');
1560
+ }
1561
+ }
1562
+
1563
+ $this->jobdata_set('current_resumption', $resumption_no);
1564
+
1565
+ $first_run = apply_filters('IWP_filerun_firstrun', 0);
1566
+
1567
+ // We just do this once, as we don't want to be in permanent conflict with the overlap detector
1568
+ if ($resumption_no >= $first_run + 8 && $resumption_no < $first_run + 15 && $resume_interval >= 300) {
1569
+
1570
+ // $time_passed is set earlier
1571
+ list($max_time, $timings_string, $run_times_known) = $this->max_time_passed($time_passed, $resumption_no - 1, $first_run);
1572
+
1573
+ # Do this on resumption 8, or the first time that we have 6 data points
1574
+ if (($first_run + 8 == $resumption_no && $run_times_known >= 6) || (6 == $run_times_known && !empty($time_passed[$prev_resumption]))) {
1575
+ $this->log("Time passed on previous resumptions: $timings_string (known: $run_times_known, max: $max_time)");
1576
+ // Remember that 30 seconds is used as the 'perhaps something is still running' detection threshold, and that 45 seconds is used as the 'the next resumption is approaching - reschedule!' interval
1577
+ if ($max_time + 52 < $resume_interval) {
1578
+ $resume_interval = round($max_time + 52);
1579
+ $this->log("Based on the available data, we are bringing the resumption interval down to: $resume_interval seconds");
1580
+ $this->jobdata_set('resume_interval', $resume_interval);
1581
+ }
1582
+ // This next condition was added in response to HS#9174, a case where on one resumption, PHP was allowed to run for >3000 seconds - but other than that, up to 500 seconds. As a result, the resumption interval got stuck at a large value, whilst resumptions were only allowed to run for a much smaller amount.
1583
+ // This detects whether our last run was less than half the resume interval, but was non-trivial (at least 50 seconds - so, indicating it didn't just error out straight away), but with a resume interval of over 300 seconds. In this case, it is reduced.
1584
+ } elseif (isset($time_passed[$prev_resumption]) && $time_passed[$prev_resumption] > 50 && $resume_interval > 300 && $time_passed[$prev_resumption] < $resume_interval/2 && 'clouduploading' == $this->jobdata_get('jobstatus')) {
1585
+ $resume_interval = round($time_passed[$prev_resumption] + 52);
1586
+ $this->log("Time passed on previous resumptions: $timings_string (known: $run_times_known, max: $max_time). Based on the available data, we are bringing the resumption interval down to: $resume_interval seconds");
1587
+ $this->jobdata_set('resume_interval', $resume_interval);
1588
+ }
1589
+
1590
+ }
1591
+
1592
+ // A different argument than before is needed otherwise the event is ignored
1593
+ $next_resumption = $resumption_no+1;
1594
+ if ($next_resumption < $first_run + 10) {
1595
+ if (true === $this->jobdata_get('one_shot')) {
1596
+ if (true === $this->jobdata_get('reschedule_before_upload') && 1 == $next_resumption) {
1597
+ $this->log('A resumption will be scheduled for the cloud backup stage');
1598
+ $schedule_resumption = true;
1599
+ } else {
1600
+ $this->log('We are in "one shot" mode - no resumptions will be scheduled');
1601
+ }
1602
+ } else {
1603
+ $schedule_resumption = true;
1604
+ }
1605
+ } else {
1606
+ // We're in over-time - we only reschedule if something useful happened last time (used to be that we waited for it to happen this time - but that meant that temporary errors, e.g. Google 400s on uploads, scuppered it all - we'd do better to have another chance
1607
+ $useful_checkin = $this->jobdata_get('useful_checkin');
1608
+ $last_resumption = $resumption_no-1;
1609
+
1610
+ if (empty($useful_checkin) || $useful_checkin < $last_resumption) {
1611
+ $this->log(sprintf('The current run is resumption number %d, and there was nothing useful done on the last run (last useful run: %s) - will not schedule a further attempt until we see something useful happening this time', $resumption_no, $useful_checkin));
1612
+ } else {
1613
+ $schedule_resumption = true;
1614
+ }
1615
+ }
1616
+
1617
+ // Sanity check
1618
+ if (empty($this->backup_time)) {
1619
+ $this->log('The backup_time parameter appears to be empty (usually caused by resuming an already-complete backup).');
1620
+ return false;
1621
+ }
1622
+
1623
+ if (isset($schedule_resumption)) {
1624
+ $schedule_for = time()+$resume_interval;
1625
+ $this->log("Scheduling a resumption ($next_resumption) after $resume_interval seconds ($schedule_for) in case this run gets aborted");
1626
+ wp_schedule_single_event($schedule_for, 'IWP_backup_resume', array($next_resumption, $bnonce));
1627
+ $this->newresumption_scheduled = $schedule_for;
1628
+ }
1629
+
1630
+ $backup_files = $this->jobdata_get('backup_files');
1631
+
1632
+ global $IWP_backup;
1633
+ // Bring in all the backup routines
1634
+ require_once($GLOBALS['iwp_mmb_plugin_dir'].'/backup/backup.php');
1635
+ $IWP_backup = new IWP_MMB_Backup($backup_files, apply_filters('IWP_files_altered_since', -1, $job_type));
1636
+
1637
+ $undone_files = array();
1638
+
1639
+ if ('no' == $backup_files) {
1640
+ $this->log("This backup run is not intended for files - skipping");
1641
+ $our_files = array();
1642
+ } else {
1643
+
1644
+ // This should be always called; if there were no files in this run, it returns us an empty array
1645
+ $backup_array = $IWP_backup->resumable_backup_of_files($resumption_no);
1646
+
1647
+ // This save, if there was something, is then immediately picked up again
1648
+ if (is_array($backup_array)) {
1649
+ $this->log('Saving backup status to database (elements: '.count($backup_array).")");
1650
+ $this->save_backup_history($backup_array);
1651
+ }
1652
+
1653
+ // Switch of variable name is purely vestigial
1654
+ $our_files = $backup_array;
1655
+ if (!is_array($our_files)) $our_files = array();
1656
+
1657
+ }
1658
+
1659
+ $backup_databases = $this->jobdata_get('backup_database');
1660
+
1661
+ if (!is_array($backup_databases)) $backup_databases = array('wp' => $backup_databases);
1662
+
1663
+ foreach ($backup_databases as $whichdb => $backup_database) {
1664
+
1665
+ if (is_array($backup_database)) {
1666
+ $dbinfo = $backup_database['dbinfo'];
1667
+ $backup_database = $backup_database['status'];
1668
+ } else {
1669
+ $dbinfo = array();
1670
+ }
1671
+
1672
+ $tindex = ('wp' == $whichdb) ? 'db' : 'db'.$whichdb;
1673
+
1674
+ if ('begun' == $backup_database || 'finished' == $backup_database || 'encrypted' == $backup_database) {
1675
+
1676
+ if ('wp' == $whichdb) {
1677
+ $db_descrip = 'WordPress DB';
1678
+ } else {
1679
+ if (!empty($dbinfo) && is_array($dbinfo) && !empty($dbinfo['host'])) {
1680
+ $db_descrip = "External DB $whichdb - ".$dbinfo['user'].'@'.$dbinfo['host'].'/'.$dbinfo['name'];
1681
+ } else {
1682
+ $db_descrip = "External DB $whichdb - details appear to be missing";
1683
+ }
1684
+ }
1685
+
1686
+ if ('begun' == $backup_database) {
1687
+ if ($resumption_no > 0) {
1688
+ $this->log("Resuming creation of database dump ($db_descrip)");
1689
+ } else {
1690
+ $this->log("Beginning creation of database dump ($db_descrip)");
1691
+ }
1692
+ } elseif ('encrypted' == $backup_database) {
1693
+ $this->log("Database dump ($db_descrip): Creation and encryption were completed already");
1694
+ } else {
1695
+ $this->log("Database dump ($db_descrip): Creation was completed already");
1696
+ }
1697
+
1698
+ if ('wp' != $whichdb && (empty($dbinfo) || !is_array($dbinfo) || empty($dbinfo['host']))) {
1699
+ unset($backup_databases[$whichdb]);
1700
+ $this->jobdata_set('backup_database', $backup_databases);
1701
+ continue;
1702
+ }
1703
+
1704
+ $db_backup = $IWP_backup->backup_db($backup_database, $whichdb, $dbinfo);
1705
+
1706
+ if(is_array($our_files) && is_string($db_backup)) $our_files[$tindex] = $db_backup;
1707
+
1708
+ if ('encrypted' != $backup_database) {
1709
+ $backup_databases[$whichdb] = array('status' => 'finished', 'dbinfo' => $dbinfo);
1710
+ $this->jobdata_set('backup_database', $backup_databases);
1711
+ }
1712
+ } elseif ('no' == $backup_database) {
1713
+ $this->log("No database backup ($whichdb) - not part of this run");
1714
+ } else {
1715
+ $this->log("Unrecognised data when trying to ascertain if the database ($whichdb) was backed up (".serialize($backup_database).")");
1716
+ }
1717
+
1718
+ // This is done before cloud despatch, because we want a record of what *should* be in the backup. Whether it actually makes it there or not is not yet known.
1719
+ $this->save_backup_history($our_files);
1720
+
1721
+ // Potentially encrypt the database if it is not already
1722
+ if ('no' != $backup_database && isset($our_files[$tindex]) && !preg_match("/\.crypt$/", $our_files[$tindex])) {
1723
+ $our_files[$tindex] = $IWP_backup->encrypt_file($our_files[$tindex]);
1724
+ // No need to save backup history now, as it will happen in a few lines time
1725
+ if (preg_match("/\.crypt$/", $our_files[$tindex])) {
1726
+ $backup_databases[$whichdb] = array('status' => 'encrypted', 'dbinfo' => $dbinfo);
1727
+ $this->jobdata_set('backup_database', $backup_databases);
1728
+ }
1729
+ }
1730
+
1731
+ if ('no' != $backup_database && isset($our_files[$tindex]) && file_exists($iwp_backup_dir.'/'.$our_files[$tindex])) {
1732
+ $our_files[$tindex.'-size'] = filesize($iwp_backup_dir.'/'.$our_files[$tindex]);
1733
+ $this->save_backup_history($our_files);
1734
+ }
1735
+
1736
+ }
1737
+
1738
+ $backupable_entities = $this->get_backupable_file_entities(true);
1739
+
1740
+ $checksum_list = $this->which_checksums();
1741
+
1742
+ $checksums = array();
1743
+
1744
+ foreach ($checksum_list as $checksum) {
1745
+ $checksums[$checksum] = array();
1746
+ }
1747
+
1748
+ $total_size = 0;
1749
+
1750
+ // Queue files for upload
1751
+ foreach ($our_files as $key => $files) {
1752
+ // Only continue if the stored info was about a dump
1753
+ if (!isset($backupable_entities[$key]) && ('db' != substr($key, 0, 2) || '-size' == substr($key, -5, 5))) continue;
1754
+ if (is_string($files)) $files = array($files);
1755
+ foreach ($files as $findex => $file) {
1756
+
1757
+ $size_key = (0 == $findex) ? $key.'-size' : $key.$findex.'-size';
1758
+ $total_size = (false === $total_size || !isset($our_files[$size_key]) || !is_numeric($our_files[$size_key])) ? false : $total_size + $our_files[$size_key];
1759
+
1760
+ foreach ($checksum_list as $checksum) {
1761
+
1762
+ $cksum = $this->jobdata_get($checksum.'-'.$key.$findex);
1763
+ if ($cksum) $checksums[$checksum][$key.$findex] = $cksum;
1764
+ $cksum = $this->jobdata_get($checksum.'-'.$key.$findex.'.crypt');
1765
+ if ($cksum) $checksums[$checksum][$key.$findex.".crypt"] = $cksum;
1766
+
1767
+ }
1768
+
1769
+ if ($this->is_uploaded($file)) {
1770
+ $this->log("$file: $key: This file has already been successfully uploaded");
1771
+ } elseif (is_file($iwp_backup_dir.'/'.$file)) {
1772
+ if (!in_array($file, $undone_files)) {
1773
+ $this->log("$file: $key: This file has not yet been successfully uploaded: will queue");
1774
+ $undone_files[$key.$findex] = $file;
1775
+ } else {
1776
+ $this->log("$file: $key: This file was already queued for upload (this condition should never be seen)");
1777
+ }
1778
+ } else {
1779
+ $this->log("$file: $key: Note: This file was not marked as successfully uploaded, but does not exist on the local filesystem ($iwp_backup_dir/$file)");
1780
+ $this->uploaded_file($file, true);
1781
+ }
1782
+ }
1783
+ }
1784
+ $our_files['checksums'] = $checksums;
1785
+
1786
+ // Save again (now that we have checksums)
1787
+ $size_description = (false === $total_size) ? 'Unknown' : $this->convert_numeric_size_to_text($total_size);
1788
+ $this->log("Saving backup history. Total backup size: $size_description");
1789
+ $backup_meta_file = $this->createBackupMetaFile($our_files);
1790
+ if ($backup_meta_file) {
1791
+ $our_files['backup_file_basename'] = $backup_meta_file;
1792
+ $undone_files['backup_file_basename'] = $backup_meta_file;
1793
+ }
1794
+ $this->save_backup_history($our_files);
1795
+ do_action('IWP_final_backup_history', $our_files);
1796
+ // We finished; so, low memory was not a problem
1797
+ $this->log_removewarning('lowram');
1798
+
1799
+ if (0 == count($undone_files)) {
1800
+ $this->log("Resume backup ($bnonce, $resumption_no): finish run");
1801
+ if (is_array($our_files)) $this->save_last_backup($our_files);
1802
+ $this->log("There were no more files that needed uploading");
1803
+ // No email, as the user probably already got one if something else completed the run
1804
+ $allow_email = false;
1805
+ if ('begun' == $this->jobdata_get('prune')) {
1806
+ // Begun, but not finished
1807
+ $this->log("Restarting backup prune operation");
1808
+ $IWP_backup->do_prune_standalone();
1809
+ $allow_email = true;
1810
+ }
1811
+ $this->backup_finish($next_resumption, true, $allow_email, $resumption_no);
1812
+ restore_error_handler();
1813
+ return;
1814
+ }
1815
+
1816
+ $this->error_count_before_cloud_backup = $this->error_count();
1817
+
1818
+ // This is intended for one-shot backups, where we do want a resumption if it's only for uploading
1819
+ if (empty($this->newresumption_scheduled) && 0 == $resumption_no && 0 == $this->error_count_before_cloud_backup && true === $this->jobdata_get('reschedule_before_upload')) {
1820
+ $this->log("Cloud backup stage reached on one-shot backup: scheduling resumption for the cloud upload");
1821
+ $this->reschedule(60);
1822
+ $this->record_still_alive();
1823
+ }
1824
+
1825
+ $this->log("Requesting upload of the files that have not yet been successfully uploaded (".count($undone_files).")");
1826
+
1827
+ $IWP_backup->cloud_backup($undone_files);
1828
+
1829
+ $this->log("Resume backup ($bnonce, $resumption_no): finish run");
1830
+ if (is_array($our_files)) $this->save_last_backup($our_files);
1831
+ $this->backup_finish($next_resumption, true, true, $resumption_no);
1832
+
1833
+ restore_error_handler();
1834
+
1835
+ }
1836
+
1837
+ public function convert_numeric_size_to_text($size) {
1838
+ if ($size > 1073741824) {
1839
+ return round($size / 1073741824, 1).' GB';
1840
+ } elseif ($size > 1048576) {
1841
+ return round($size / 1048576, 1).' MB';
1842
+ } elseif ($size > 1024) {
1843
+ return round($size / 1024, 1).' KB';
1844
+ } else {
1845
+ return round($size, 1).' B';
1846
+ }
1847
+ }
1848
+
1849
+ public function max_time_passed($time_passed, $upto, $first_run) {
1850
+ $max_time = 0;
1851
+ $timings_string = "";
1852
+ $run_times_known=0;
1853
+ for ($i=$first_run; $i<=$upto; $i++) {
1854
+ $timings_string .= "$i:";
1855
+ if (isset($time_passed[$i])) {
1856
+ $timings_string .= round($time_passed[$i], 1).' ';
1857
+ $run_times_known++;
1858
+ if ($time_passed[$i] > $max_time) $max_time = round($time_passed[$i]);
1859
+ } else {
1860
+ $timings_string .= '? ';
1861
+ }
1862
+ }
1863
+ return array($max_time, $timings_string, $run_times_known);
1864
+ }
1865
+
1866
+ public function jobdata_getarray($non) {
1867
+ return get_site_option("IWP_jobdata_".$non, array());
1868
+ }
1869
+
1870
+ public function jobdata_set_from_array($array) {
1871
+ $this->jobdata = $array;
1872
+ if (!empty($this->nonce)) update_site_option("IWP_jobdata_".$this->nonce, $this->jobdata);
1873
+ }
1874
+
1875
+ // This works with any amount of settings, but we provide also a jobdata_set for efficiency as normally there's only one setting
1876
+ public function jobdata_set_multi() {
1877
+ if (!is_array($this->jobdata)) $this->jobdata = array();
1878
+
1879
+ $args = func_num_args();
1880
+
1881
+ for ($i=1; $i<=$args/2; $i++) {
1882
+ $key = func_get_arg($i*2-2);
1883
+ $value = func_get_arg($i*2-1);
1884
+ $this->jobdata[$key] = $value;
1885
+ }
1886
+ if (!empty($this->nonce)) update_site_option("IWP_jobdata_".$this->nonce, $this->jobdata);
1887
+ }
1888
+
1889
+ public function jobdata_set($key, $value) {
1890
+ if (empty($this->jobdata)) {
1891
+ $this->jobdata = empty($this->nonce) ? array() : get_site_option("IWP_jobdata_".$this->nonce);
1892
+ if (!is_array($this->jobdata)) $this->jobdata = array();
1893
+ }
1894
+ $this->jobdata[$key] = $value;
1895
+ if ($this->nonce) update_site_option("IWP_jobdata_".$this->nonce, $this->jobdata);
1896
+ }
1897
+
1898
+ public function jobdata_delete($key) {
1899
+ if (!is_array($this->jobdata)) {
1900
+ $this->jobdata = empty($this->nonce) ? array() : get_site_option("IWP_jobdata_".$this->nonce);
1901
+ if (!is_array($this->jobdata)) $this->jobdata = array();
1902
+ }
1903
+ unset($this->jobdata[$key]);
1904
+ if ($this->nonce) update_site_option("IWP_jobdata_".$this->nonce, $this->jobdata);
1905
+ }
1906
+
1907
+ public function get_job_option($opt) {
1908
+ // These are meant to be read-only
1909
+ if (empty($this->jobdata['option_cache']) || !is_array($this->jobdata['option_cache'])) {
1910
+ if (!is_array($this->jobdata)) $this->jobdata = get_site_option("IWP_jobdata_".$this->nonce, array());
1911
+ $this->jobdata['option_cache'] = array();
1912
+ }
1913
+ return isset($this->jobdata['option_cache'][$opt]) ? $this->jobdata['option_cache'][$opt] : IWP_MMB_Backup_Options::get_iwp_backup_option($opt);
1914
+ }
1915
+
1916
+ public function jobdata_get($key, $default = null, $all_data = false) {
1917
+ if (empty($this->jobdata)) {
1918
+ $this->jobdata = empty($this->nonce) ? array() : get_site_option("IWP_jobdata_".$this->nonce, array());
1919
+ if ($all_data) return $this->jobdata;
1920
+ if (!is_array($this->jobdata)) return $default;
1921
+ }
1922
+ if ($all_data) return $this->jobdata;
1923
+ return isset($this->jobdata[$key]) ? $this->jobdata[$key] : $default;
1924
+ }
1925
+
1926
+ public function jobdata_reset() {
1927
+ $this->jobdata = null;
1928
+ }
1929
+
1930
+ private function ensure_semaphore_exists($semaphore) {
1931
+ // Make sure the options for semaphores exist
1932
+ global $wpdb;
1933
+ $results = $wpdb->get_results("
1934
+ SELECT option_id
1935
+ FROM $wpdb->options
1936
+ WHERE option_name IN ('IWP_locked_$semaphore', 'IWP_unlocked_$semaphore', 'IWP_last_lock_time_$semaphore', 'IWP_semaphore_$semaphore')
1937
+ ");
1938
+
1939
+ if (!is_array($results) || count($results) < 3) {
1940
+
1941
+ if (is_array($results) && count($results) > 0) {
1942
+ $this->log("Semaphore ($semaphore, ".$wpdb->options.") in an impossible/broken state - fixing (".count($results).")");
1943
+ } else {
1944
+ $this->log("Semaphore ($semaphore, ".$wpdb->options.") being initialised");
1945
+ }
1946
+
1947
+ $wpdb->query("
1948
+ DELETE FROM $wpdb->options
1949
+ WHERE option_name IN ('IWP_locked_$semaphore', 'IWP_unlocked_$semaphore', 'IWP_last_lock_time_$semaphore', 'IWP_semaphore_$semaphore')
1950
+ ");
1951
+
1952
+ $wpdb->query($wpdb->prepare("
1953
+ INSERT INTO $wpdb->options (option_name, option_value, autoload)
1954
+ VALUES
1955
+ ('IWP_unlocked_$semaphore', '1', 'no'),
1956
+ ('IWP_last_lock_time_$semaphore', '%s', 'no'),
1957
+ ('IWP_semaphore_$semaphore', '0', 'no')
1958
+ ", current_time('mysql', 1)));
1959
+ }
1960
+ }
1961
+
1962
+ public function backup_files() {
1963
+ # Note that the "false" for database gets over-ridden automatically if they turn out to have the same schedules
1964
+ $this->boot_backup(true, false);
1965
+ }
1966
+
1967
+ public function backup_database() {
1968
+ # Note that nothing will happen if the file backup had the same schedule
1969
+ $this->boot_backup(false, true);
1970
+ }
1971
+
1972
+ public function backup_all($options) {
1973
+ $skip_cloud = empty($options['nocloud']) ? false : false;
1974
+ $this->boot_backup(1, 1, false, false, ($skip_cloud) ? 'none' : false, $options);
1975
+ }
1976
+
1977
+ public function backupnow_files($options) {
1978
+ $skip_cloud = empty($options['nocloud']) ? false : true;
1979
+ $this->boot_backup(1, 0, false, false, ($skip_cloud) ? 'none' : false, $options);
1980
+ }
1981
+
1982
+ public function backupnow_database($options) {
1983
+ $skip_cloud = empty($options['nocloud']) ? false : true;
1984
+ $this->boot_backup(0, 1, false, false, ($skip_cloud) ? 'none' : false, $options);
1985
+ }
1986
+
1987
+ // This procedure initiates a backup run
1988
+ // $backup_files/$backup_database: true/false = yes/no (over-write allowed); 1/0 = yes/no (force)
1989
+ public function boot_backup($backup_files, $backup_database, $restrict_files_to_override = false, $one_shot = false, $service = false, $options = array()) {
1990
+
1991
+ @ignore_user_abort(true);
1992
+ @set_time_limit(IWP_SET_TIME_LIMIT);
1993
+
1994
+ if (false === $restrict_files_to_override && isset($options['restrict_files_to_override'])) $restrict_files_to_override = $options['restrict_files_to_override'];
1995
+ // Generate backup information
1996
+ $use_nonce = (empty($options['use_nonce'])) ? false : $options['use_nonce'];
1997
+ $this->backup_time_nonce($use_nonce);
1998
+ // The current_resumption is consulted within logfile_open()
1999
+ $this->current_resumption = 0;
2000
+ $this->logfile_open($this->nonce);
2001
+ $iwp_backup_dir = $this->backups_dir_location();
2002
+ if (!is_file($this->logfile_name)) {
2003
+ $this->log('Failed to open log file ('.$this->logfile_name.') - the directory ('.$iwp_backup_dir.') for creating files in is not writable, or you ran out of disk space). Backup aborted.');
2004
+ $this->log(__('Could not create files in the backup directory. Backup aborted','InfiniteWP'), 'error');
2005
+ return false;
2006
+ }
2007
+
2008
+ // Some house-cleaning
2009
+ $this->clean_temporary_files();
2010
+
2011
+ // Log some information that may be helpful
2012
+ $this->log("Tasks: Backup files: $backup_files (schedule: ".IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_interval', 'unset').") Backup DB: $backup_database (schedule: ".IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_interval_database', 'unset').")");
2013
+
2014
+ $semaphore = (($backup_files) ? 'f' : '') . (($backup_database) ? 'd' : '');
2015
+ $this->ensure_semaphore_exists($semaphore);
2016
+
2017
+ if (!is_string($service) && !is_array($service)) $service = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_service');
2018
+ $service = $this->just_one($service);
2019
+ if (is_string($service)) $service = array($service);
2020
+ if (!is_array($service)) $service = array('none');
2021
+
2022
+ if (!empty($options['extradata']) && preg_match('#services=remotesend/(\d+)#', $options['extradata'])) {
2023
+ if ($service === array('none')) $service = array();
2024
+ $service[] = 'remotesend';
2025
+ }
2026
+
2027
+ $option_cache = array();
2028
+ foreach ($service as $serv) {
2029
+ if ('' == $serv || 'none' == $serv) continue;
2030
+ include_once($GLOBALS['iwp_mmb_plugin_dir'].'/backup/'.$serv.'.php');
2031
+ $cclass = 'IWP_MMB_UploadModule_'.$serv;
2032
+ if (!class_exists($cclass)) {
2033
+ error_log("InfiniteWP: backup class does not exist: $cclass");
2034
+ continue;
2035
+ }
2036
+ $obj = new $cclass;
2037
+
2038
+ if (is_callable(array($obj, 'get_credentials'))) {
2039
+ $opts = $obj->get_credentials();
2040
+ if (is_array($opts)) {
2041
+ foreach ($opts as $opt) $option_cache[$opt] = IWP_MMB_Backup_Options::get_iwp_backup_option($opt);
2042
+ }
2043
+ }
2044
+ }
2045
+
2046
+ // If nothing to be done, then just finish
2047
+ if (!$backup_files && !$backup_database) {
2048
+ $ret = $this->backup_finish(1, false, false, 0);
2049
+ // Don't keep useless log files
2050
+ if (!IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_debug_mode') && !empty($this->logfile_name) && file_exists($this->logfile_name)) {
2051
+ unlink($this->logfile_name);
2052
+ }
2053
+ return $ret;
2054
+ }
2055
+
2056
+ // Are we doing an action called by the WP scheduler? If so, we want to check when that last happened; the point being that the dodgy WP scheduler, when overloaded, can call the event multiple times - and sometimes, it evades the semaphore because it calls a second run after the first has finished, or > 3 minutes (our semaphore lock time) later
2057
+ // doing_action() was added in WP 3.9
2058
+ // wp_cron() can be called from the 'init' action
2059
+
2060
+ if (function_exists('doing_action') && (doing_action('init') || @constant('DOING_CRON')) && (doing_action('IWP_backup_database') || doing_action('IWP_backup'))) {
2061
+ $last_scheduled_action_called_at = get_option("IWP_last_scheduled_$semaphore");
2062
+ // 11 minutes - so, we're assuming that they haven't custom-modified their schedules to run scheduled backups more often than that. If they have, they need also to use the filter to over-ride this check.
2063
+ $seconds_ago = time() - $last_scheduled_action_called_at;
2064
+ if ($last_scheduled_action_called_at && $seconds_ago < 660 && apply_filters('IWP_check_repeated_scheduled_backups', true)) {
2065
+ $this->log(sprintf('Scheduled backup aborted - another backup of this type was apparently invoked by the WordPress scheduler only %d seconds ago - the WordPress scheduler invoking events multiple times usually indicates a very overloaded server (or other plugins that mis-use the scheduler)', $seconds_ago));
2066
+ return;
2067
+ }
2068
+ }
2069
+ update_option("IWP_last_scheduled_$semaphore", time());
2070
+
2071
+ require_once($GLOBALS['iwp_mmb_plugin_dir'].'/backup/class.semaphore.php');
2072
+ $this->semaphore = IWP_MMB_Semaphore::factory();
2073
+ $this->semaphore->lock_name = $semaphore;
2074
+
2075
+ $semaphore_log_message = 'Requesting semaphore lock ('.$semaphore.')';
2076
+ if (!empty($last_scheduled_action_called_at)) {
2077
+ $semaphore_log_message .= " (apparently via scheduler: last_scheduled_action_called_at=$last_scheduled_action_called_at, seconds_ago=$seconds_ago)";
2078
+ } else {
2079
+ $semaphore_log_message .= " (apparently not via scheduler)";
2080
+ }
2081
+
2082
+ $this->log($semaphore_log_message);
2083
+ if (!$this->semaphore->lock()) {
2084
+ $this->log('Failed to gain semaphore lock ('.$semaphore.') - another backup of this type is apparently already active - aborting (if this is wrong - i.e. if the other backup crashed without removing the lock, then another can be started after 3 minutes)');
2085
+ return;
2086
+ }
2087
+
2088
+ // Allow the resume interval to be more than 300 if last time we know we went beyond that - but never more than 600
2089
+ if (defined('IWP_INITIAL_RESUME_INTERVAL') && is_numeric(IWP_INITIAL_RESUME_INTERVAL)) {
2090
+ $resume_interval = IWP_INITIAL_RESUME_INTERVAL;
2091
+ } else {
2092
+ $resume_interval = (int)min(max(300, get_site_transient('IWP_initial_resume_interval')), 600);
2093
+ }
2094
+ # We delete it because we only want to know about behaviour found during the very last backup run (so, if you move servers then old data is not retained)
2095
+ delete_site_transient('IWP_initial_resume_interval');
2096
+
2097
+ $job_file_entities = array();
2098
+ if ($backup_files) {
2099
+ $possible_backups = $this->get_backupable_file_entities(true);
2100
+ foreach ($possible_backups as $youwhat => $whichdir) {
2101
+ if ((false === $restrict_files_to_override && IWP_MMB_Backup_Options::get_iwp_backup_option("IWP_include_$youwhat", apply_filters("IWP_defaultoption_include_$youwhat", true))) || (is_array($restrict_files_to_override) && !in_array($youwhat, $restrict_files_to_override))) {
2102
+ // The 0 indicates the zip file index
2103
+ $job_file_entities[$youwhat] = array(
2104
+ 'index' => 0
2105
+ );
2106
+ }
2107
+ }
2108
+ }
2109
+
2110
+ $followups_allowed = (((!$one_shot && defined('DOING_CRON') && DOING_CRON)) || (defined('IWP_FOLLOWUPS_ALLOWED') && IWP_FOLLOWUPS_ALLOWED));
2111
+
2112
+ $split_every = max(intval(IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_split_every', 200)), IWP_SPLIT_MIN);
2113
+
2114
+ $initial_jobdata = array(
2115
+ 'resume_interval', $resume_interval,
2116
+ 'job_type', 'backup',
2117
+ 'jobstatus', 'begun',
2118
+ 'backup_time', $this->backup_time,
2119
+ 'job_time_ms', $this->job_time_ms,
2120
+ 'service', $service,
2121
+ 'split_every', $split_every,
2122
+ 'maxzipbatch', 26214400, #25MB
2123
+ 'job_file_entities', $job_file_entities,
2124
+ 'option_cache', $option_cache,
2125
+ 'uploaded_lastreset', 9,
2126
+ 'one_shot', $one_shot,
2127
+ 'followsups_allowed', $followups_allowed
2128
+ );
2129
+
2130
+
2131
+
2132
+ if (!empty($options['extradata']) && 'autobackup' == $options['extradata']) array_push($initial_jobdata, 'is_autobackup', true);
2133
+
2134
+ // Save what *should* be done, to make it resumable from this point on
2135
+ if ($backup_database) {
2136
+ $dbs = apply_filters('IWP_backup_databases', array('wp' => 'begun'));
2137
+ if (is_array($dbs)) {
2138
+ foreach ($dbs as $key => $db) {
2139
+ if ('wp' != $key && (!is_array($db) || empty($db['dbinfo']) || !is_array($db['dbinfo']) || empty($db['dbinfo']['host']))) unset($dbs[$key]);
2140
+ }
2141
+ }
2142
+ } else {
2143
+ $dbs = "no";
2144
+ }
2145
+
2146
+ array_push($initial_jobdata, 'backup_database', $dbs);
2147
+ array_push($initial_jobdata, 'backup_files', (($backup_files) ? 'begun' : 'no'));
2148
+
2149
+ if (is_array($options) && !empty($options['label'])) array_push($initial_jobdata, 'label', $options['label']);
2150
+ if (is_array($options) && !empty($options['backup_name'])) array_push($initial_jobdata, 'backup_name', $options['backup_name']);
2151
+ try {
2152
+ // Use of jobdata_set_multi saves around 200ms
2153
+ call_user_func_array(array($this, 'jobdata_set_multi'), apply_filters('IWP_initial_jobdata', $initial_jobdata, $options, $split_every));
2154
+ } catch (Exception $e) {
2155
+ $this->log($e->getMessage());
2156
+ return false;
2157
+ }
2158
+
2159
+ // Everything is set up; now go
2160
+ if (!empty($options['cron_start'])) {
2161
+ $this->backup_resume(0, $this->nonce, 1);
2162
+ }else{
2163
+ $this->backup_resume(0, $this->nonce);
2164
+ }
2165
+
2166
+ }
2167
+
2168
+ // This function examines inside the InfiniteWP 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').
2169
+ // If $remotescan is set, then remote storage is also scanned
2170
+ // $only_add_this_file : an array with keys 'name' and (optionally) 'label'
2171
+ public function rebuild_backup_history($remotescan = false, $only_add_this_file = false) {
2172
+
2173
+ # TODO: Make compatible with incremental naming scheme
2174
+
2175
+ $messages = array();
2176
+ $gmt_offset = get_option('gmt_offset');
2177
+
2178
+ // Array of nonces keyed by filename
2179
+ $known_files = array();
2180
+ // Array of backup times keyed by nonce
2181
+ $known_nonces = array();
2182
+ $changes = false;
2183
+
2184
+ $backupable_entities = $this->get_backupable_file_entities(true, false);
2185
+
2186
+ $backup_history = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_backup_history');
2187
+ if (!is_array($backup_history)) $backup_history = array();
2188
+ $iwp_backup_dir = $this->backups_dir_location();
2189
+ if (!is_dir($iwp_backup_dir)) return;
2190
+
2191
+ $accept = apply_filters('IWP_accept_archivename', array());
2192
+ if (!is_array($accept)) $accept = array();
2193
+ // Process what is known from the database backup history; this means populating $known_files and $known_nonces
2194
+ foreach ($backup_history as $btime => $bdata) {
2195
+ $found_file = false;
2196
+ foreach ($bdata as $key => $values) {
2197
+ if ('db' != $key && !isset($backupable_entities[$key])) continue;
2198
+ // Record which set this file is found in
2199
+ if (!is_array($values)) $values=array($values);
2200
+ foreach ($values as $val) {
2201
+ if (!is_string($val)) continue;
2202
+ if (preg_match('/^backup_([\-0-9]{15})_.*_([0-9a-f]{12})-[\-a-z]+([0-9]+)?+(\.(zip|gz|gz\.crypt))?$/i', $val, $matches)) {
2203
+ $nonce = $matches[2];
2204
+ if (isset($bdata['service']) && ($bdata['service'] === 'none' || (is_array($bdata['service']) && (array('none') === $bdata['service'] || (1 == count($bdata['service']) && isset($bdata['service'][0]) && empty($bdata['service'][0]))))) && !is_file($iwp_backup_dir.'/'.$val)) {
2205
+ # File without remote storage is no longer present
2206
+ } else {
2207
+ $found_file = true;
2208
+ $known_files[$val] = $nonce;
2209
+ $known_nonces[$nonce] = (empty($known_nonces[$nonce]) || $known_nonces[$nonce]<100) ? $btime : min($btime, $known_nonces[$nonce]);
2210
+ }
2211
+ } else {
2212
+ $accepted = false;
2213
+ foreach ($accept as $fkey => $acc) {
2214
+ if (preg_match('/'.$acc['pattern'].'/i', $val)) $accepted = $fkey;
2215
+ }
2216
+ if (!empty($accepted) && (false != ($btime = apply_filters('IWP_foreign_gettime', false, $accepted, $val))) && $btime > 0) {
2217
+ $found_file = true;
2218
+ # Generate a nonce; this needs to be deterministic and based on the filename only
2219
+ $nonce = substr(md5($val), 0, 12);
2220
+ $known_files[$val] = $nonce;
2221
+ $known_nonces[$nonce] = (empty($known_nonces[$nonce]) || $known_nonces[$nonce]<100) ? $btime : min($btime, $known_nonces[$nonce]);
2222
+ }
2223
+ }
2224
+ }
2225
+ }
2226
+ if (!$found_file) {
2227
+ # File recorded as being without remote storage is no longer present - though it may in fact exist in remote storage, and this will be picked up later
2228
+ unset($backup_history[$btime]);
2229
+ $changes = true;
2230
+ }
2231
+ }
2232
+
2233
+ $remotefiles = array();
2234
+ $remotesizes = array();
2235
+
2236
+ if (!$handle = opendir($iwp_backup_dir)) return;
2237
+
2238
+ // See if there are any more files in the local directory than the ones already known about
2239
+ while (false !== ($entry = readdir($handle))) {
2240
+ $accepted_foreign = false;
2241
+ $potmessage = false;
2242
+
2243
+ if ($only_add_this_file !== false && $entry != $only_add_this_file['file']) continue;
2244
+
2245
+ if ('.' == $entry || '..' == $entry) continue;
2246
+
2247
+ # TODO: Make compatible with Incremental naming
2248
+ if (preg_match('/^backup_([\-0-9]{15})_.*_([0-9a-f]{12})-([\-a-z]+)([0-9]+)?(\.(zip|gz|gz\.crypt))?$/i', $entry, $matches)) {
2249
+ // Interpret the time as one from the blog's local timezone, rather than as UTC
2250
+ # $matches[1] is YYYY-MM-DD-HHmm, to be interpreted as being the local timezone
2251
+ $btime2 = strtotime($matches[1]);
2252
+ $btime = (!empty($gmt_offset)) ? $btime2 - $gmt_offset*3600 : $btime2;
2253
+ $nonce = $matches[2];
2254
+ $type = $matches[3];
2255
+ if ('db' == $type) {
2256
+ $type .= (!empty($matches[4])) ? $matches[4] : '';
2257
+ $index = 0;
2258
+ } else {
2259
+ $index = (empty($matches[4])) ? '0' : (max((int)$matches[4]-1,0));
2260
+ }
2261
+ $itext = ($index == 0) ? '' : $index;
2262
+ } elseif (false != ($accepted_foreign = apply_filters('IWP_accept_foreign', false, $entry)) && false !== ($btime = apply_filters('IWP_foreign_gettime', false, $accepted_foreign, $entry))) {
2263
+ $nonce = substr(md5($entry), 0, 12);
2264
+ $type = (preg_match('/\.sql(\.(bz2|gz))?$/i', $entry) || preg_match('/-database-([-0-9]+)\.zip$/i', $entry) || preg_match('/backup_db_/', $entry)) ? 'db' : 'wpcore';
2265
+ $index = apply_filters('IWP_accepted_foreign_index', 0, $entry, $accepted_foreign);
2266
+ $itext = $index ? $index : '';
2267
+ $potmessage = array(
2268
+ 'code' => 'foundforeign_'.md5($entry),
2269
+ 'desc' => $entry,
2270
+ 'method' => '',
2271
+ 'message' => sprintf(__('Backup created by: %s.', 'InfiniteWP'), $accept[$accepted_foreign]['desc'])
2272
+ );
2273
+ } elseif ('.zip' == strtolower(substr($entry, -4, 4)) || preg_match('/\.sql(\.(bz2|gz))?$/i', $entry)) {
2274
+ $potmessage = array(
2275
+ 'code' => 'possibleforeign_'.md5($entry),
2276
+ 'desc' => $entry,
2277
+ 'method' => '',
2278
+ 'message' => __('This file does not appear to be an InfiniteWP backup archive (such files are .zip or .gz files which have a name like: backup_(time)_(site name)_(code)_(type).(zip|gz)).', 'InfiniteWP')
2279
+ );
2280
+ $messages[$potmessage['code']] = $potmessage;
2281
+ continue;
2282
+ } else {
2283
+ continue;
2284
+ }
2285
+ // The time from the filename does not include seconds. Need to identify the seconds to get the right time
2286
+ if (isset($known_nonces[$nonce])) {
2287
+ $btime_exact = $known_nonces[$nonce];
2288
+ # TODO: 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)
2289
+ if ($btime > 100 && $btime_exact - $btime > 60 && !empty($backup_history[$btime_exact])) {
2290
+ # TODO: This needs testing
2291
+ # The code below assumes that $backup_history[$btime] is presently empty
2292
+ # Re-key array, indicating the newly-found time to be the start of the backup set
2293
+ $backup_history[$btime] = $backup_history[$btime_exact];
2294
+ unset($backup_history[$btime_exact]);
2295
+ $btime_exact = $btime;
2296
+ }
2297
+ $btime = $btime_exact;
2298
+ }
2299
+ if ($btime <= 100) continue;
2300
+ $fs = @filesize($iwp_backup_dir.'/'.$entry);
2301
+
2302
+ if (!isset($known_files[$entry])) {
2303
+ $changes = true;
2304
+ if (is_array($potmessage)) $messages[$potmessage['code']] = $potmessage;
2305
+ if (is_array($only_add_this_file)) {
2306
+ if (isset($only_add_this_file['label'])) $backup_history[$btime]['label'] = $only_add_this_file['label'];
2307
+ $backup_history[$btime]['native'] = false;
2308
+ } elseif ('db' == $type && !$accepted_foreign) {
2309
+ list ($mess, $warn, $err, $info) = $this->analyse_db_file(false, array(), $iwp_backup_dir.'/'.$entry, true);
2310
+ if (!empty($info['label'])) {
2311
+ $backup_history[$btime]['label'] = $info['label'];
2312
+ }
2313
+ if (!empty($info['created_by_version'])) {
2314
+ $backup_history[$btime]['created_by_version'] = $info['created_by_version'];
2315
+ }
2316
+ }
2317
+ }
2318
+
2319
+ # TODO: Code below here has not been reviewed or adjusted for compatibility with incremental backups
2320
+ # Make sure we have the right list of services
2321
+ $current_services = (!empty($backup_history[$btime]) && !empty($backup_history[$btime]['service'])) ? $backup_history[$btime]['service'] : array();
2322
+ if (is_string($current_services)) $current_services = array($current_services);
2323
+ if (!is_array($current_services)) $current_services = array();
2324
+ if (!empty($remotefiles[$entry])) {
2325
+ if (0 == count(array_diff($current_services, $remotefiles[$entry]))) {
2326
+ $backup_history[$btime]['service'] = $remotefiles[$entry];
2327
+ $changes = true;
2328
+ }
2329
+ # Get the right size (our local copy may be too small)
2330
+ foreach ($remotefiles[$entry] as $rem) {
2331
+ if (!empty($rem['size']) && $rem['size'] > $fs) {
2332
+ $fs = $rem['size'];
2333
+ $changes = true;
2334
+ }
2335
+ }
2336
+ # Remove from $remotefiles, so that we can later see what was left over
2337
+ unset($remotefiles[$entry]);
2338
+ } else {
2339
+ # Not known remotely
2340
+ if (!empty($backup_history[$btime])) {
2341
+ if (empty($backup_history[$btime]['service']) || ('none' !== $backup_history[$btime]['service'] && '' !== $backup_history[$btime]['service'] && array('none') !== $backup_history[$btime]['service'])) {
2342
+ $backup_history[$btime]['service'] = 'none';
2343
+ $changes = true;
2344
+ }
2345
+ } else {
2346
+ $backup_history[$btime]['service'] = 'none';
2347
+ $changes = true;
2348
+ }
2349
+ }
2350
+
2351
+ $backup_history[$btime][$type][$index] = $entry;
2352
+ if ($fs > 0) $backup_history[$btime][$type.$itext.'-size'] = $fs;
2353
+ $backup_history[$btime]['nonce'] = $nonce;
2354
+ if (!empty($accepted_foreign)) $backup_history[$btime]['meta_foreign'] = $accepted_foreign;
2355
+ }
2356
+
2357
+ # Any found in remote storage that we did not previously know about?
2358
+ # Compare $remotefiles with $known_files / $known_nonces, and adjust $backup_history
2359
+ if (count($remotefiles) > 0) {
2360
+
2361
+ # $backup_history[$btime]['nonce'] = $nonce
2362
+ foreach ($remotefiles as $file => $services) {
2363
+ if (!preg_match('/^backup_([\-0-9]{15})_.*_([0-9a-f]{12})-([\-a-z]+)([0-9]+)?(\.(zip|gz|gz\.crypt))?$/i', $file, $matches)) continue;
2364
+ $nonce = $matches[2];
2365
+ $type = $matches[3];
2366
+ if ('db' == $type) {
2367
+ $index = 0;
2368
+ $type .= !empty($matches[4]) ? $matches[4] : '';
2369
+ } else {
2370
+ $index = (empty($matches[4])) ? '0' : (max((int)$matches[4]-1,0));
2371
+ }
2372
+ $itext = ($index == 0) ? '' : $index;
2373
+ $btime2 = strtotime($matches[1]);
2374
+ $btime = (!empty($gmt_offset)) ? $btime2 - $gmt_offset*3600 : $btime2;
2375
+
2376
+ if (isset($known_nonces[$nonce])) $btime = $known_nonces[$nonce];
2377
+ if ($btime <= 100) continue;
2378
+ # Remember that at this point, we already know that the file is not known about locally
2379
+ if (isset($backup_history[$btime])) {
2380
+ 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']))) {
2381
+ $changes = true;
2382
+ $backup_history[$btime]['service'] = $services;
2383
+ $backup_history[$btime]['nonce'] = $nonce;
2384
+ }
2385
+ if (!isset($backup_history[$btime][$type][$index])) {
2386
+ $changes = true;
2387
+ $backup_history[$btime][$type][$index] = $file;
2388
+ $backup_history[$btime]['nonce'] = $nonce;
2389
+ if (!empty($remotesizes[$file])) $backup_history[$btime][$type.$itext.'-size'] = $remotesizes[$file];
2390
+ }
2391
+ } else {
2392
+ $changes = true;
2393
+ $backup_history[$btime]['service'] = $services;
2394
+ $backup_history[$btime][$type][$index] = $file;
2395
+ $backup_history[$btime]['nonce'] = $nonce;
2396
+ if (!empty($remotesizes[$file])) $backup_history[$btime][$type.$itext.'-size'] = $remotesizes[$file];
2397
+ $backup_history[$btime]['native'] = false;
2398
+ $messages['nonnative'] = array(
2399
+ '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.', 'InfiniteWP'),
2400
+ 'code' => 'nonnative',
2401
+ 'desc' => '',
2402
+ 'method' => ''
2403
+ );
2404
+ }
2405
+
2406
+ }
2407
+ }
2408
+
2409
+ if ($changes) IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_backup_history', $backup_history);
2410
+
2411
+ return $messages;
2412
+
2413
+ }
2414
+
2415
+ private function backup_finish($cancel_event, $do_cleanup, $allow_email, $resumption_no, $force_abort = false) {
2416
+
2417
+ if (!empty($this->semaphore)) $this->semaphore->unlock();
2418
+
2419
+ $delete_jobdata = false;
2420
+
2421
+ // The valid use of $do_cleanup is to indicate if in fact anything exists to clean up (if no job really started, then there may be nothing)
2422
+
2423
+ // In fact, leaving the hook to run (if debug is set) is harmless, as the resume job should only do tasks that were left unfinished, which at this stage is none.
2424
+ if (0 == $this->error_count() || $force_abort) {
2425
+ if ($do_cleanup) {
2426
+ $this->log("There were no errors in the uploads, so the 'resume' event ($cancel_event) is being unscheduled");
2427
+ # This apparently-worthless setting of metadata before deleting it is for the benefit of a WP install seen where wp_clear_scheduled_hook() and delete_transient() apparently did nothing (probably a faulty cache)
2428
+ $this->jobdata_set('jobstatus', 'finished');
2429
+ wp_clear_scheduled_hook('IWP_backup_resume', array($cancel_event, $this->nonce));
2430
+ # This should be unnecessary - even if it does resume, all should be detected as finished; but I saw one very strange case where it restarted, and repeated everything; so, this will help
2431
+ wp_clear_scheduled_hook('IWP_backup_resume', array($cancel_event+1, $this->nonce));
2432
+ wp_clear_scheduled_hook('IWP_backup_resume', array($cancel_event+2, $this->nonce));
2433
+ wp_clear_scheduled_hook('IWP_backup_resume', array($cancel_event+3, $this->nonce));
2434
+ wp_clear_scheduled_hook('IWP_backup_resume', array($cancel_event+4, $this->nonce));
2435
+ $delete_jobdata = true;
2436
+ }
2437
+ } else {
2438
+ $this->log("There were errors in the uploads, so the 'resume' event is remaining scheduled");
2439
+ $this->jobdata_set('jobstatus', 'resumingforerrors');
2440
+ # If there were no errors before moving to the upload stage, on the first run, then bring the resumption back very close. Since this is only attempted on the first run, it is really only an efficiency thing for a quicker finish if there was an unexpected networking event. We don't want to do it straight away every time, as it may be that the cloud service is down - and might be up in 5 minutes time. This was added after seeing a case where resumption 0 got to run for 10 hours... and the resumption 7 that should have picked up the uploading of 1 archive that failed never occurred.
2441
+ if (isset($this->error_count_before_cloud_backup) && 0 === $this->error_count_before_cloud_backup) {
2442
+ if (0 == $resumption_no) {
2443
+ $this->reschedule(60);
2444
+ } else {
2445
+ // Added 27/Feb/2016 - though the cloud service seems to be down, we still don't want to wait too long
2446
+ $resume_interval = $this->jobdata_get('resume_interval');
2447
+
2448
+ // 15 minutes + 2 for each resumption (a modest back-off)
2449
+ $max_interval = 900 + $resumption_no * 120;
2450
+ if ($resume_interval > $max_interval) {
2451
+ $this->reschedule($max_interval);
2452
+ }
2453
+ }
2454
+ }
2455
+ }
2456
+
2457
+ // Send the results email if appropriate, which means:
2458
+ // - The caller allowed it (which is not the case in an 'empty' run)
2459
+ // - And: An email address was set (which must be so in email mode)
2460
+ // And one of:
2461
+ // - Debug mode
2462
+ // - There were no errors (which means we completed and so this is the final run - time for the final report)
2463
+ // - It was the tenth resumption; everything failed
2464
+ # Save the jobdata's state for the reporting - because it might get changed (e.g. incremental backup is scheduled)
2465
+ $jobdata_as_was = $this->jobdata;
2466
+
2467
+ // Make sure that the final status is shown
2468
+ if ($force_abort) {
2469
+ $send_an_email = true;
2470
+ $final_message = __('The backup was aborted by the user', 'InfiniteWP');
2471
+ } elseif (0 == $this->error_count()) {
2472
+ $send_an_email = true;
2473
+ $service = $this->jobdata_get('service');
2474
+ $remote_sent = (!empty($service) && ((is_array($service) && in_array('remotesend', $service)) || 'remotesend' === $service)) ? true : false;
2475
+ $userid = get_current_user_id();
2476
+ $backup_files = $this->jobdata_get('backup_files');
2477
+ $backup_database = $this->jobdata_get('backup_database');
2478
+ $what = 'full';
2479
+ if ($backup_files == 'no') {
2480
+ $what = 'db';
2481
+ }elseif($backup_database == 'no'){
2482
+ $what = 'files';
2483
+ }
2484
+ if (0 == $this->error_count('warning')) {
2485
+ $final_message = __('The backup apparently succeeded and is now complete', 'InfiniteWP');
2486
+ # Ensure it is logged in English. Not hugely important; but helps with a tiny number of really broken setups in which the options cacheing is broken
2487
+ if ('The backup apparently succeeded and is now complete' != $final_message) {
2488
+ $this->log('The backup apparently succeeded and is now complete');
2489
+ }
2490
+ delete_option('IWP_jobdata_'.$this->nonce);
2491
+ update_option('IWP_backup_status', '0');
2492
+ $GLOBALS['iwp_mmb_activities_log']->iwp_mmb_save_iwp_activities('backup', 'multiCallNow', 'direct', array('what' => $what), $userid);
2493
+ } else {
2494
+ $final_message = __('The backup apparently succeeded (with warnings) and is now complete','InfiniteWP');
2495
+ if ('The backup apparently succeeded (with warnings) and is now complete' != $final_message) {
2496
+ $this->log('The backup apparently succeeded (with warnings) and is now complete');
2497
+ }
2498
+ $GLOBALS['iwp_mmb_activities_log']->iwp_mmb_save_iwp_activities('backup', 'multiCallNow', 'direct', array('what' => $what), $userid);
2499
+ delete_option('IWP_jobdata_'.$this->nonce);
2500
+ update_option('IWP_backup_status', '0');
2501
+ }
2502
+ if ($remote_sent && !$force_abort) $final_message .= '. '.__('To complete your migration/clone, you should now log in to the remote site and restore the backup set.', 'InfiniteWP');
2503
+ if ($do_cleanup) $delete_jobdata = apply_filters('IWP_backup_complete', $delete_jobdata);
2504
+ } elseif (false == $this->newresumption_scheduled) {
2505
+ $send_an_email = true;
2506
+ wp_clear_scheduled_hook('IWP_backup_resume');
2507
+ $this->kill_new_backup(array('result_id'=>$this->nonce));
2508
+ $final_message = __('The backup attempt has finished, apparently unsuccessfully', 'InfiniteWP');
2509
+ } else {
2510
+ // There are errors, but a resumption will be attempted
2511
+ $final_message = __('The backup has not finished; a resumption is scheduled', 'InfiniteWP');
2512
+ }
2513
+
2514
+ global $IWP_backup;
2515
+
2516
+ if ($force_abort) $jobdata_as_was['aborted'] = true;
2517
+
2518
+ # Make sure this is the final message logged (so it remains on the dashboard)
2519
+ $this->log($final_message);
2520
+
2521
+ @fclose($this->logfile_handle);
2522
+ $this->logfile_handle = null;
2523
+
2524
+ // This is left until last for the benefit of the front-end UI, which then gets maximum chance to display the 'finished' status
2525
+ if ($delete_jobdata) {
2526
+ delete_option('IWP_jobdata_'.$this->nonce);
2527
+ update_option('IWP_backup_status', '0');
2528
+ }
2529
+
2530
+ }
2531
+
2532
+ // This function returns 'true' if mod_rewrite could be detected as unavailable; a 'false' result may mean it just couldn't find out the answer
2533
+ public function mod_rewrite_unavailable($check_if_in_use_first = true) {
2534
+ if (function_exists('apache_get_modules')) {
2535
+ global $wp_rewrite;
2536
+ $mods = apache_get_modules();
2537
+ if ((!$check_if_in_use_first || $wp_rewrite->using_mod_rewrite_permalinks()) && ((in_array('core', $mods) || in_array('http_core', $mods)) && !in_array('mod_rewrite', $mods))) {
2538
+ return true;
2539
+ }
2540
+ }
2541
+ return false;
2542
+ }
2543
+
2544
+ public function error_count($level = 'error') {
2545
+ $count = 0;
2546
+ foreach ($this->errors as $err) {
2547
+ if (('error' == $level && (is_string($err) || is_wp_error($err))) || (is_array($err) && $level == $err['level']) ) { $count++; }
2548
+ }
2549
+ return $count;
2550
+ }
2551
+
2552
+ public function list_errors() {
2553
+ echo '<ul style="list-style: disc inside;">';
2554
+ foreach ($this->errors as $err) {
2555
+ if (is_wp_error($err)) {
2556
+ foreach ($err->get_error_messages() as $msg) {
2557
+ echo '<li>'.htmlspecialchars($msg).'<li>';
2558
+ }
2559
+ } elseif (is_array($err) && ('error' == $err['level'] || 'warning' == $err['level'])) {
2560
+ echo "<li>".htmlspecialchars($err['message'])."</li>";
2561
+ } elseif (is_string($err)) {
2562
+ echo "<li>".htmlspecialchars($err)."</li>";
2563
+ } else {
2564
+ print "<li>".print_r($err,true)."</li>";
2565
+ }
2566
+ }
2567
+ echo '</ul>';
2568
+ }
2569
+
2570
+ private function save_last_backup($backup_array) {
2571
+ $success = ($this->error_count() == 0) ? 1 : 0;
2572
+ $last_backup = apply_filters('IWP_save_last_backup', array(
2573
+ 'backup_time' => $this->backup_time,
2574
+ 'backup_array' => $backup_array,
2575
+ 'success' => $success,
2576
+ 'errors' => $this->errors,
2577
+ 'backup_nonce' => $this->nonce
2578
+ ));
2579
+ IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_last_backup', $last_backup, false);
2580
+ }
2581
+
2582
+ # $handle must be either false or a WPDB class (or extension thereof). Other options are not yet fully supported.
2583
+ public function check_db_connection($handle = false, $logit = false, $reschedule = false) {
2584
+
2585
+ $type = false;
2586
+ if (false === $handle || is_a($handle, 'wpdb')) {
2587
+ $type='wpdb';
2588
+ } elseif (is_resource($handle)) {
2589
+ # Expected: string(10) "mysql link"
2590
+ $type=get_resource_type($handle);
2591
+ } elseif (is_object($handle) && is_a($handle, 'mysqli')) {
2592
+ $type='mysqli';
2593
+ }
2594
+
2595
+ if (false === $type) return -1;
2596
+
2597
+ $db_connected = -1;
2598
+
2599
+ if ('mysql link' == $type || 'mysqli' == $type) {
2600
+ if ('mysql link' == $type && @mysql_ping($handle)) return true;
2601
+ if ('mysqli' == $type && @mysqli_ping($handle)) return true;
2602
+
2603
+ for ( $tries = 1; $tries <= 5; $tries++ ) {
2604
+ # to do, if ever needed
2605
+ // if ( $this->db_connect( false ) ) return true;
2606
+ // sleep( 1 );
2607
+ }
2608
+
2609
+ } elseif ('wpdb' == $type) {
2610
+ if (false === $handle || (is_object($handle) && 'wpdb' == get_class($handle))) {
2611
+ global $wpdb;
2612
+ $handle = $wpdb;
2613
+ }
2614
+ if (method_exists($handle, 'check_connection') && (!defined('IWP_SUPPRESS_CONNECTION_CHECKS') || !IWP_SUPPRESS_CONNECTION_CHECKS)) {
2615
+ if (!$handle->check_connection(false)) {
2616
+ if ($logit) $this->log("The database went away, and could not be reconnected to");
2617
+ # Almost certainly a no-op
2618
+ if ($reschedule) $this->reschedule(60);
2619
+ $db_connected = false;
2620
+ } else {
2621
+ $db_connected = true;
2622
+ }
2623
+ }
2624
+ }
2625
+
2626
+ return $db_connected;
2627
+
2628
+ }
2629
+
2630
+ // This should be called whenever a file is successfully uploaded
2631
+ public function uploaded_file($file, $force = false) {
2632
+
2633
+ global $IWP_backup;
2634
+
2635
+ $db_connected = $this->check_db_connection(false, true, true);
2636
+
2637
+ $service = empty($IWP_backup->current_service) ? '' : $IWP_backup->current_service;
2638
+ $shash = $service.'-'.md5($file);
2639
+
2640
+ $this->jobdata_set("uploaded_".$shash, 'yes');
2641
+
2642
+ if ($force || !empty($IWP_backup->last_service)) {
2643
+ $hash = md5($file);
2644
+ $this->log("Recording as successfully uploaded: $file ($hash)");
2645
+ $this->jobdata_set('uploaded_lastreset', $this->current_resumption);
2646
+ $this->jobdata_set("uploaded_".$hash, 'yes');
2647
+ } else {
2648
+ $this->log("Recording as successfully uploaded: $file (".$IWP_backup->current_service.", more services to follow)");
2649
+ }
2650
+
2651
+ $upload_status = $this->jobdata_get('uploading_substatus');
2652
+ if (is_array($upload_status) && isset($upload_status['i'])) {
2653
+ $upload_status['i']++;
2654
+ $upload_status['p']=0;
2655
+ $this->jobdata_set('uploading_substatus', $upload_status);
2656
+ }
2657
+
2658
+ # Really, we could do this immediately when we realise the DB has gone away. This is just for the probably-impossible case that a DB write really can still succeed. But, we must abort before calling delete_local(), as the removal of the local file can cause it to be recreated if the DB is out of sync with the fact that it really is already uploaded
2659
+ if (false === $db_connected) {
2660
+ $this->record_still_alive();
2661
+ die;
2662
+ }
2663
+
2664
+ // Delete local files immediately if the option is set
2665
+ // Where we are only backing up locally, only the "prune" function should do deleting
2666
+ $service = $this->jobdata_get('service');
2667
+ if (!empty($IWP_backup->last_service) && ($service !== '' && ((is_array($service) && count($service)>0 && (count($service) > 1 || ($service[0] != '' && $service[0] != 'none'))) || (is_string($service) && $service !== 'none')))) {
2668
+ $this->delete_local($file);
2669
+ }
2670
+ }
2671
+
2672
+ public function is_uploaded($file, $service = '') {
2673
+ $hash = $service.(('' == $service) ? '' : '-').md5($file);
2674
+ return ($this->jobdata_get("uploaded_$hash") === "yes") ? true : false;
2675
+ }
2676
+
2677
+ private function delete_local($file) {
2678
+ $log = "Deleting local file: $file: ";
2679
+ if (IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_delete_local')) {
2680
+ $fullpath = $this->backups_dir_location().'/'.$file;
2681
+
2682
+ //check to make sure it exists before removing
2683
+ if(realpath($fullpath)){
2684
+ $deleted = unlink($fullpath);
2685
+ $this->log($log.(($deleted) ? 'OK' : 'failed'));
2686
+ return $deleted;
2687
+ }
2688
+ } else {
2689
+ $this->log($log."skipped: user has unchecked IWP_delete_local option");
2690
+ }
2691
+ return true;
2692
+ }
2693
+
2694
+ // This function is not needed for backup success, according to the design, but it helps with efficient scheduling
2695
+ private function reschedule_if_needed() {
2696
+ // If nothing is scheduled, then return
2697
+ if (empty($this->newresumption_scheduled)) return;
2698
+ $time_now = time();
2699
+ $time_away = $this->newresumption_scheduled - $time_now;
2700
+ // 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)
2701
+ if ($time_away >1 && $time_away <= 45) {
2702
+ $this->log('The scheduled resumption is within 45 seconds - will reschedule');
2703
+ // Push 45 seconds into the future
2704
+ // $this->reschedule(60);
2705
+ // Increase interval generally by 45 seconds, on the assumption that our prior estimates were innaccurate (i.e. not just 45 seconds *this* time)
2706
+ $this->increase_resume_and_reschedule(45);
2707
+ }
2708
+ }
2709
+
2710
+ public function reschedule($how_far_ahead, $first_call=false) {
2711
+ // Reschedule - remove presently scheduled event
2712
+ $next_resumption = $this->current_resumption + 1;
2713
+ wp_clear_scheduled_hook('IWP_backup_resume', array($next_resumption, $this->nonce));
2714
+ // Add new event
2715
+ # This next line may be too cautious; but until 14-Aug-2014, it was 300.
2716
+ # Update 20-Mar-2015 - lowered from 180
2717
+ if ($how_far_ahead < 120 && !$first_call) $how_far_ahead=120;
2718
+ $schedule_for = time() + $how_far_ahead;
2719
+ $this->log("Rescheduling resumption $next_resumption: moving to $how_far_ahead seconds from now ($schedule_for)");
2720
+ wp_schedule_single_event($schedule_for, 'IWP_backup_resume', array($next_resumption, $this->nonce));
2721
+ $this->newresumption_scheduled = $schedule_for;
2722
+ }
2723
+
2724
+ private function increase_resume_and_reschedule($howmuch = 120, $force_schedule = false) {
2725
+
2726
+ $resume_interval = max(intval($this->jobdata_get('resume_interval')), ($howmuch === 0) ? 120 : 300);
2727
+
2728
+ if (empty($this->newresumption_scheduled) && $force_schedule) {
2729
+ $this->log("A new resumption will be scheduled to prevent the job ending");
2730
+ }
2731
+
2732
+ $new_resume = $resume_interval + $howmuch;
2733
+ # 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
2734
+ if ($this->opened_log_time > 100 && microtime(true)-$this->opened_log_time > $new_resume) {
2735
+ $new_resume = ceil(microtime(true)-$this->opened_log_time)+45;
2736
+ $howmuch = $new_resume-$resume_interval;
2737
+ }
2738
+
2739
+ # 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.
2740
+ # Actually, let's not try this yet. I think it is safe, but think there is a more conservative solution available.
2741
+ #$how_far_ahead = min($new_resume, 600);
2742
+ $how_far_ahead = $new_resume;
2743
+ # If it is very long-running, then that would normally be known soon.
2744
+ # 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.
2745
+ if ($this->current_resumption <= 1 && $new_resume > 720) $how_far_ahead = 600;
2746
+
2747
+ if (!empty($this->newresumption_scheduled) || $force_schedule) $this->reschedule($how_far_ahead);
2748
+ $this->jobdata_set('resume_interval', $new_resume);
2749
+
2750
+ $this->log("To decrease the likelihood of overlaps, increasing resumption interval to: $resume_interval + $howmuch = $new_resume");
2751
+ }
2752
+
2753
+ // For detecting another run, and aborting if one was found
2754
+ public function check_recent_modification($file) {
2755
+ if (file_exists($file)) {
2756
+ $time_mod = (int)@filemtime($file);
2757
+ $time_now = time();
2758
+ if ($time_mod>100 && ($time_now-$time_mod)<30) {
2759
+ $this->terminate_due_to_activity($file, $time_now, $time_mod);
2760
+ }
2761
+ }
2762
+ }
2763
+
2764
+ public function get_exclude($whichone) {
2765
+ if ('uploads' == $whichone) {
2766
+ $exclude = explode(',', IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_include_uploads_exclude', IWP_DEFAULT_UPLOADS_EXCLUDE));
2767
+ } elseif ('others' == $whichone) {
2768
+ $exclude = explode(',', IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_include_others_exclude', IWP_DEFAULT_OTHERS_EXCLUDE));
2769
+ } else {
2770
+ $exclude = apply_filters('IWP_include_'.$whichone.'_exclude', array());
2771
+ }
2772
+ return (empty($exclude) || !is_array($exclude)) ? array() : $exclude;
2773
+ }
2774
+
2775
+ public function really_is_writable($dir) {
2776
+ // Suppress warnings, since if the user is dumping warnings to screen, then invalid JavaScript results and the screen breaks.
2777
+ if (!@is_writable($dir)) return false;
2778
+ // Found a case - GoDaddy server, Windows, PHP 5.2.17 - where is_writable returned true, but writing failed
2779
+ $rand_file = "$dir/test-".md5(rand().time()).".txt";
2780
+ while (file_exists($rand_file)) {
2781
+ $rand_file = "$dir/test-".md5(rand().time()).".txt";
2782
+ }
2783
+ $ret = @file_put_contents($rand_file, 'testing...');
2784
+ @unlink($rand_file);
2785
+ return ($ret > 0);
2786
+ }
2787
+
2788
+ public function wp_upload_dir() {
2789
+ if (is_multisite()) {
2790
+ global $current_site;
2791
+ switch_to_blog($current_site->blog_id);
2792
+ }
2793
+
2794
+ $wp_upload_dir = wp_upload_dir();
2795
+
2796
+ if (is_multisite()) restore_current_blog();
2797
+
2798
+ return $wp_upload_dir;
2799
+ }
2800
+
2801
+ public function backup_uploads_dirlist($logit = false) {
2802
+ # Create an array of directories to be skipped
2803
+ # Make the values into the keys
2804
+ $exclude = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_include_uploads_exclude', IWP_DEFAULT_UPLOADS_EXCLUDE);
2805
+ if ($logit) $this->log("Exclusion option setting (uploads): ".$exclude);
2806
+ $skip = array_flip(preg_split("/,/", $exclude));
2807
+ $wp_upload_dir = $this->wp_upload_dir();
2808
+ $uploads_dir = $wp_upload_dir['basedir'];
2809
+ return $this->compile_folder_list_for_backup($uploads_dir, array(), $skip);
2810
+ }
2811
+
2812
+ public function backup_others_dirlist($logit = false) {
2813
+ # Create an array of directories to be skipped
2814
+ # Make the values into the keys
2815
+ $exclude = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_include_others_exclude', IWP_DEFAULT_OTHERS_EXCLUDE);
2816
+ if ($logit) $this->log("Exclusion option setting (others): ".$exclude);
2817
+ $skip = array_flip(preg_split("/,/", $exclude));
2818
+ $file_entities = $this->get_backupable_file_entities(false);
2819
+
2820
+ # Keys = directory names to avoid; values = the label for that directory (used only in log files)
2821
+ #$avoid_these_dirs = array_flip($file_entities);
2822
+ $avoid_these_dirs = array();
2823
+ foreach ($file_entities as $type => $dirs) {
2824
+ if (is_string($dirs)) {
2825
+ $avoid_these_dirs[$dirs] = $type;
2826
+ } elseif (is_array($dirs)) {
2827
+ foreach ($dirs as $dir) {
2828
+ $avoid_these_dirs[$dir] = $type;
2829
+ }
2830
+ }
2831
+ }
2832
+ return $this->compile_folder_list_for_backup(WP_CONTENT_DIR, $avoid_these_dirs, $skip);
2833
+ }
2834
+
2835
+ public function backup_more_dirlist($whichdir = false) {
2836
+ # Create an array of directories to be skipped
2837
+ # Make the values into the keys
2838
+
2839
+
2840
+
2841
+ # Keys = directory names to avoid; values = the label for that directory (used only in log files)
2842
+ #$avoid_these_dirs = array_flip($file_entities);
2843
+ $avoid_these_dirs = array();
2844
+ $skip = array();
2845
+ $dir_list = $this->compile_folder_list_for_backup($whichdir, $avoid_these_dirs, $skip);
2846
+ $include = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_default_includes', IWP_DEFAULT_INCLUDES);
2847
+ foreach ($dir_list as $key => $value) {
2848
+ if (!in_array(str_replace(ABSPATH, '', $value), explode(',',$include))) {
2849
+ unset($dir_list[$key]);
2850
+ }
2851
+ }
2852
+ return $dir_list;
2853
+ }
2854
+
2855
+ // Add backquotes to tables and db-names in SQL queries. Taken from phpMyAdmin.
2856
+ public function backquote($a_name) {
2857
+ if (!empty($a_name) && $a_name != '*') {
2858
+ if (is_array($a_name)) {
2859
+ $result = array();
2860
+ reset($a_name);
2861
+ while(list($key, $val) = each($a_name))
2862
+ $result[$key] = '`'.$val.'`';
2863
+ return $result;
2864
+ } else {
2865
+ return '`'.$a_name.'`';
2866
+ }
2867
+ } else {
2868
+ return $a_name;
2869
+ }
2870
+ }
2871
+
2872
+ public function strip_dirslash($string) {
2873
+ return preg_replace('#/+(,|$)#', '$1', $string);
2874
+ }
2875
+
2876
+ public function remove_empties($list) {
2877
+ if (!is_array($list)) return $list;
2878
+ foreach ($list as $ind => $entry) {
2879
+ if (empty($entry)) unset($list[$ind]);
2880
+ }
2881
+ return $list;
2882
+ }
2883
+
2884
+ // avoid_these_dirs and skip_these_dirs ultimately do the same thing; but avoid_these_dirs takes full paths whereas skip_these_dirs takes basenames; and they are logged differently (dirs in avoid are potentially dangerous to include; skip is just a user-level preference). They are allowed to overlap.
2885
+ public function compile_folder_list_for_backup($backup_from_inside_dir, $avoid_these_dirs, $skip_these_dirs) {
2886
+
2887
+ // Entries in $skip_these_dirs are allowed to end in *, which means "and anything else as a suffix". It's not a full shell glob, but it covers what is needed to-date.
2888
+
2889
+ $dirlist = array();
2890
+ $added = 0;
2891
+
2892
+ $this->log('Looking for candidates to back up in: '.$backup_from_inside_dir);
2893
+ $iwp_backup_dir = $this->backups_dir_location();
2894
+
2895
+ if (is_file($backup_from_inside_dir)) {
2896
+ array_push($dirlist, $backup_from_inside_dir);
2897
+ $added++;
2898
+ $this->log("finding files: $backup_from_inside_dir: adding to list ($added)");
2899
+ } elseif ($handle = opendir($backup_from_inside_dir)) {
2900
+
2901
+ while (false !== ($entry = readdir($handle))) {
2902
+ // $candidate: full path; $entry = one-level
2903
+ $candidate = $backup_from_inside_dir.'/'.$entry;
2904
+ if ($entry != "." && $entry != "..") {
2905
+ if (isset($avoid_these_dirs[$candidate])) {
2906
+ $this->log("finding files: $entry: skipping: this is the ".$avoid_these_dirs[$candidate]." directory");
2907
+ } elseif ($candidate == $iwp_backup_dir) {
2908
+ $this->log("finding files: $entry: skipping: this is the InfiniteWP directory");
2909
+ } elseif (isset($skip_these_dirs[$entry])) {
2910
+ $this->log("finding files: $entry: skipping: excluded by options");
2911
+ } else {
2912
+ $add_to_list = true;
2913
+ // Now deal with entries in $skip_these_dirs ending in * or starting with *
2914
+ foreach ($skip_these_dirs as $skip => $sind) {
2915
+ if ('*' == substr($skip, -1, 1) && '*' == substr($skip, 0, 1) && strlen($skip) > 2) {
2916
+ if (strpos($entry, substr($skip, 1, strlen($skip-2))) !== false) {
2917
+ $this->log("finding files: $entry: skipping: excluded by options (glob)");
2918
+ $add_to_list = false;
2919
+ }
2920
+ } elseif ('*' == substr($skip, -1, 1) && strlen($skip) > 1) {
2921
+ if (substr($entry, 0, strlen($skip)-1) == substr($skip, 0, strlen($skip)-1)) {
2922
+ $this->log("finding files: $entry: skipping: excluded by options (glob)");
2923
+ $add_to_list = false;
2924
+ }
2925
+ } elseif ('*' == substr($skip, 0, 1) && strlen($skip) > 1) {
2926
+ if (strlen($entry) >= strlen($skip)-1 && substr($entry, (strlen($skip)-1)*-1) == substr($skip, 1)) {
2927
+ $this->log("finding files: $entry: skipping: excluded by options (glob)");
2928
+ $add_to_list = false;
2929
+ }
2930
+ }
2931
+ }
2932
+ if ($add_to_list) {
2933
+ array_push($dirlist, $candidate);
2934
+ $added++;
2935
+ $skip_dblog = (($added > 50 && 0 != $added % 100) || ($added > 2000 && 0 != $added % 500));
2936
+ $this->log("finding files: $entry: adding to list ($added)", 'notice', false, $skip_dblog);
2937
+ }
2938
+ }
2939
+ }
2940
+ }
2941
+ @closedir($handle);
2942
+ } else {
2943
+ $this->log('ERROR: Could not read the directory: '.$backup_from_inside_dir);
2944
+ $this->log(__('Could not read the directory', 'InfiniteWP').': '.$backup_from_inside_dir, 'error');
2945
+ }
2946
+
2947
+ return $dirlist;
2948
+
2949
+ }
2950
+
2951
+ private function save_backup_history($backup_array) {
2952
+ if(is_array($backup_array)) {
2953
+ $backup_history = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_backup_history');
2954
+ $backup_history = (is_array($backup_history)) ? $backup_history : array();
2955
+ $backup_array['nonce'] = $this->nonce;
2956
+ $backup_array['service'] = $this->jobdata_get('service');
2957
+ if (!empty($backup_array['service'][0]) && $backup_array['service'][0] != 'none') {
2958
+ $service = 'IWP_'.$backup_array['service'][0];
2959
+ $backup_array['service_setting'] = IWP_MMB_Backup_Options::get_iwp_backup_option($service);
2960
+ }
2961
+ if ('' != ($label = $this->jobdata_get('label', ''))) $backup_array['label'] = $label;
2962
+ if ('' != ($backup_name = $this->jobdata_get('backup_name', ''))) $backup_array['backup_name'] = $backup_name;
2963
+ $backup_array['created_by_version'] = $this->version;
2964
+ $backup_array['is_multisite'] = is_multisite() ? true : false;
2965
+ $backup_array['wp_content_url'] = content_url();
2966
+ $backup_array['wp_content_path'] = WP_CONTENT_DIR;
2967
+ $backup_array['old_url'] = get_option('siteurl');
2968
+ $backup_array['old_file_path'] = ABSPATH;
2969
+ $remotesend_info = $this->jobdata_get('remotesend_info');
2970
+ if (is_array($remotesend_info) && !empty($remotesend_info['url'])) $backup_array['remotesend_url'] = $remotesend_info['url'];
2971
+ if (false != ($autobackup = $this->jobdata_get('is_autobackup', false))) $backup_array['autobackup'] = true;
2972
+ $backup_history[$this->backup_time] = $backup_array;
2973
+ IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_backup_history', $backup_history, false);
2974
+ } else {
2975
+ $this->log('Could not save backup history because we have no backup array. Backup probably failed.');
2976
+ $this->log(__('Could not save backup history because we have no backup array. Backup probably failed.','InfiniteWP'), 'error');
2977
+ }
2978
+ }
2979
+
2980
+ public function is_db_encrypted($file) {
2981
+ return preg_match('/\.crypt$/i', $file);
2982
+ }
2983
+
2984
+ public function get_backup_history($timestamp = false) {
2985
+ $backup_history = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_backup_history');
2986
+ // The line below actually *introduces* a race condition
2987
+ // global $wpdb;
2988
+ // $backup_history = @unserialize($wpdb->get_var($wpdb->prepare("SELECT option_value from $wpdb->options WHERE option_name='IWP_backup_history'")));
2989
+ if (is_array($backup_history)) {
2990
+ krsort($backup_history); //reverse sort so earliest backup is last on the array. Then we can array_pop.
2991
+ } else {
2992
+ $backup_history = array();
2993
+ }
2994
+ if (!$timestamp) return $backup_history;
2995
+ return (isset($backup_history[$timestamp])) ? $backup_history[$timestamp] : array();
2996
+ }
2997
+
2998
+ public function terminate_due_to_activity($file, $time_now, $time_mod, $increase_resumption = true) {
2999
+ # We check-in, to avoid 'no check in last time!' detectors firing
3000
+ $this->record_still_alive();
3001
+ $file_size = file_exists($file) ? round(filesize($file)/1024,1). 'KB' : 'n/a';
3002
+ $this->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 InfiniteWP run is at work; so we will exit.");
3003
+ $increase_by = ($increase_resumption) ? 120 : 0;
3004
+ $this->increase_resume_and_reschedule($increase_by, true);
3005
+ if (!defined('IWP_ALLOW_RECENT_ACTIVITY') || true != IWP_ALLOW_RECENT_ACTIVITY) die;
3006
+ }
3007
+
3008
+ # Replace last occurence
3009
+ public function str_lreplace($search, $replace, $subject) {
3010
+ $pos = strrpos($subject, $search);
3011
+ if($pos !== false) $subject = substr_replace($subject, $replace, $pos, strlen($search));
3012
+ return $subject;
3013
+ }
3014
+
3015
+ public function str_replace_once($needle, $replace, $haystack) {
3016
+ $pos = strpos($haystack, $needle);
3017
+ return ($pos !== false) ? substr_replace($haystack,$replace,$pos,strlen($needle)) : $haystack;
3018
+ }
3019
+
3020
+ /*
3021
+ If files + db are on different schedules but are scheduled for the same time, then combine them
3022
+ $event = (object) array( 'hook' => $hook, 'timestamp' => $timestamp, 'schedule' => $recurrence, 'args' => $args, 'interval' => $schedules[$recurrence]['interval'] );
3023
+ See wp_schedule_single_event() and wp_schedule_event() in wp-includes/cron.php
3024
+ */
3025
+ public function schedule_event($event) {
3026
+
3027
+ static $scheduled = array();
3028
+
3029
+
3030
+ if (is_object($event) && ('IWP_backup' == $event->hook || 'IWP_backup_database' == $event->hook)) {
3031
+
3032
+ // Reset the option - but make sure it is saved first so that we can used it (since this hook may be called just before our actual cron task)
3033
+ $this->combine_jobs_around = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_combine_jobs_around');
3034
+
3035
+ IWP_MMB_Backup_Options::delete_iwp_backup_option('IWP_combine_jobs_around');
3036
+
3037
+ $scheduled[$event->hook] = true;
3038
+
3039
+ // This next fragment is wrong: there's only a 'second call' when saving all settings; otherwise, the WP scheduler might just be updating one event. So, there's some inefficieny as the option is wiped and set uselessly at least once when saving settings.
3040
+ // We only want to take action on the second call (otherwise, our information is out-of-date already)
3041
+ // If there is no second call, then that's fine - nothing to do
3042
+ //if (count($scheduled) < 2) {
3043
+ // return $event;
3044
+ //}
3045
+
3046
+ $backup_scheduled_for = ('IWP_backup' == $event->hook) ? $event->timestamp : wp_next_scheduled('IWP_backup');
3047
+ $db_scheduled_for = ('IWP_backup_database' == $event->hook) ? $event->timestamp : wp_next_scheduled('IWP_backup_database');
3048
+
3049
+ $diff = absint($backup_scheduled_for - $db_scheduled_for);
3050
+
3051
+ $margin = (defined('IWP_COMBINE_MARGIN') && is_numeric(IWP_COMBINE_MARGIN)) ? IWP_COMBINE_MARGIN : 600;
3052
+
3053
+ if ($backup_scheduled_for && $db_scheduled_for && $diff < $margin) {
3054
+ // We could change the event parameters; however, this would complicate other code paths (because the WP cron system uses a hash of the parameters as a key, and you must supply the exact parameters to look up events). So, we just set a marker that boot_backup() can pick up on.
3055
+ IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_combine_jobs_around', min($backup_scheduled_for, $db_scheduled_for));
3056
+ }
3057
+
3058
+ }
3059
+
3060
+ return $event;
3061
+
3062
+ }
3063
+
3064
+ /*
3065
+ This function is both the backup scheduler and a filter callback for saving the option.
3066
+ It is called in the register_setting for the IWP_interval, which means when the
3067
+ admin settings are saved it is called.
3068
+ */
3069
+ public function schedule_backup($interval) {
3070
+ $previous_time = wp_next_scheduled('IWP_backup');
3071
+
3072
+ // Clear schedule so that we don't stack up scheduled backups
3073
+ wp_clear_scheduled_hook('IWP_backup');
3074
+ if ('manual' == $interval) return 'manual';
3075
+ $previous_interval = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_interval');
3076
+
3077
+ $valid_schedules = wp_get_schedules();
3078
+ if (empty($valid_schedules[$interval])) $interval = 'daily';
3079
+
3080
+ // Try to avoid changing the time is one was already scheduled. This is fairly conservative - we could do more, e.g. check if a backup already happened today.
3081
+ $default_time = ($interval == $previous_interval && $previous_time>0) ? $previous_time : time()+120;
3082
+ $first_time = apply_filters('IWP_schedule_firsttime_files', $default_time);
3083
+
3084
+ wp_schedule_event($first_time, $interval, 'IWP_backup');
3085
+
3086
+ return $interval;
3087
+ }
3088
+
3089
+ public function schedule_backup_database($interval) {
3090
+ $previous_time = wp_next_scheduled('IWP_backup_database');
3091
+
3092
+ // Clear schedule so that we don't stack up scheduled backups
3093
+ wp_clear_scheduled_hook('IWP_backup_database');
3094
+ if ('manual' == $interval) return 'manual';
3095
+
3096
+ $previous_interval = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_interval_database');
3097
+
3098
+ $valid_schedules = wp_get_schedules();
3099
+ if (empty($valid_schedules[$interval])) $interval = 'daily';
3100
+
3101
+ // Try to avoid changing the time is one was already scheduled. This is fairly conservative - we could do more, e.g. check if a backup already happened today.
3102
+ $default_time = ($interval == $previous_interval && $previous_time>0) ? $previous_time : time()+120;
3103
+
3104
+ $first_time = apply_filters('IWP_schedule_firsttime_db', $default_time);
3105
+ wp_schedule_event($first_time, $interval, 'IWP_backup_database');
3106
+
3107
+ return $interval;
3108
+ }
3109
+
3110
+ public function ftp_sanitise($ftp) {
3111
+ if (is_array($ftp) && !empty($ftp['host']) && preg_match('#ftp(es|s)?://(.*)#i', $ftp['host'], $matches)) {
3112
+ $ftp['host'] = untrailingslashit($matches[2]);
3113
+ }
3114
+ return $ftp;
3115
+ }
3116
+
3117
+ public function s3_sanitise($s3) {
3118
+ if (is_array($s3) && !empty($s3['path']) && '/' == substr($s3['path'], 0, 1)) {
3119
+ $s3['path'] = substr($s3['path'], 1);
3120
+ }
3121
+ return $s3;
3122
+ }
3123
+
3124
+ public function remove_local_directory($dir, $contents_only = false) {
3125
+ // PHP 5.3+ only
3126
+ //foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST) as $path) {
3127
+ // $path->isFile() ? unlink($path->getPathname()) : rmdir($path->getPathname());
3128
+ //}
3129
+ //return rmdir($dir);
3130
+
3131
+ if ($handle = @opendir($dir)) {
3132
+ while (false !== ($entry = readdir($handle))) {
3133
+ if ('.' !== $entry && '..' !== $entry) {
3134
+ if (is_dir($dir.'/'.$entry)) {
3135
+ $this->remove_local_directory($dir.'/'.$entry, false);
3136
+ } else {
3137
+ @unlink($dir.'/'.$entry);
3138
+ }
3139
+ }
3140
+ }
3141
+ @closedir($handle);
3142
+ }
3143
+
3144
+ return ($contents_only) ? true : rmdir($dir);
3145
+ }
3146
+
3147
+ // Returns without any trailing slash
3148
+ public function backups_dir_location($allow_cache = true) {
3149
+
3150
+ if ($allow_cache && !empty($this->backup_dir)) return $this->backup_dir;
3151
+
3152
+ if(!file_exists(IWP_BACKUP_DIR) && !is_dir(IWP_BACKUP_DIR)){
3153
+ $mkdir = @mkdir(IWP_BACKUP_DIR, 0755, true);
3154
+ if(!$mkdir){
3155
+ return array('error' => 'Permission denied; Make sure you have write permission for the wp-content folder.', 'error_code' => 'permission_denied_make_sure_you_have_write_permission_for_the_wp_content_folder');
3156
+ }
3157
+ }
3158
+ if(is_writable(IWP_BACKUP_DIR)){
3159
+ @file_put_contents(IWP_BACKUP_DIR . '/index.php', ''); //safe
3160
+
3161
+ }
3162
+
3163
+ $this->backup_dir = IWP_BACKUP_DIR;
3164
+
3165
+ return IWP_BACKUP_DIR;
3166
+ }
3167
+ /**
3168
+ * This function creates the correct header when download files
3169
+ * @param string $fullpath This is the full path to the encrypted file
3170
+ * @param string $encryption This is the key (salting) used to decrypt the file
3171
+ * @return heder This will download the fila when via the browser
3172
+ */
3173
+ private function spool_crypted_file($fullpath, $encryption) {
3174
+ if ('' == $encryption) $encryption = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_encryptionphrase');
3175
+ if ('' == $encryption) {
3176
+ header('Content-type: text/plain');
3177
+ _e("Decryption failed. The database file is encrypted, but you have no encryption key entered.", 'InfiniteWP');
3178
+ $this->log('Decryption of database failed: the database file is encrypted, but you have no encryption key entered.', 'error');
3179
+ } else {
3180
+
3181
+
3182
+ //now decrypt the file and return array
3183
+ $decrypted_file = $this->decrypt($fullpath, $encryption, true);
3184
+
3185
+ //check to ensure there is a response back
3186
+ if (is_array($decrypted_file)) {
3187
+ header('Content-type: application/x-gzip');
3188
+ header("Content-Disposition: attachment; filename=\"".$decrypted_file['basename']."\";");
3189
+ header("Content-Length: ".filesize($decrypted_file['fullpath']));
3190
+ readfile($decrypted_file['fullpath']);
3191
+
3192
+ //need to remove the file as this is no longer needed on the local server
3193
+ unlink($decrypted_file['fullpath']);
3194
+ } else {
3195
+ header('Content-type: text/plain');
3196
+ echo __("Decryption failed. The most likely cause is that you used the wrong key.", 'InfiniteWP')." ".__('The decryption key used:', 'InfiniteWP').' '.$encryption;
3197
+
3198
+ }
3199
+ }
3200
+ }
3201
+
3202
+ public function get_mime_type_from_filename($filename, $allow_gzip = true) {
3203
+ if ('.zip' == substr($filename, -4, 4)) {
3204
+ return 'application/zip';
3205
+ } elseif ('.tar' == substr($filename, -4, 4)) {
3206
+ return 'application/x-tar';
3207
+ } elseif ('.tar.gz' == substr($filename, -7, 7)) {
3208
+ return 'application/x-tgz';
3209
+ } elseif ('.tar.bz2' == substr($filename, -8, 8)) {
3210
+ return 'application/x-bzip-compressed-tar';
3211
+ } elseif ($allow_gzip && '.gz' == substr($filename, -3, 3)) {
3212
+ // 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)
3213
+ return 'application/x-gzip';
3214
+ } else {
3215
+ return 'application/octet-stream';
3216
+ }
3217
+ }
3218
+
3219
+
3220
+ public function retain_range($input) {
3221
+ $input = (int)$input;
3222
+ return ($input > 0) ? min($input, 9999) : 1;
3223
+ }
3224
+
3225
+ public function just_one_email($input, $required = false) {
3226
+ $x = $this->just_one($input, 'saveemails', (empty($input) && false === $required) ? '' : get_bloginfo('admin_email'));
3227
+ if (is_array($x)) {
3228
+ foreach ($x as $ind => $val) {
3229
+ if (empty($val)) unset($x[$ind]);
3230
+ }
3231
+ if (empty($x)) $x = '';
3232
+ }
3233
+ return $x;
3234
+ }
3235
+
3236
+ public function just_one($input, $filter = 'savestorage', $rinput = false) {
3237
+ $oinput = $input;
3238
+ if (false === $rinput) $rinput = (is_array($input)) ? array_pop($input) : $input;
3239
+ if (is_string($rinput) && false !== strpos($rinput, ',')) $rinput = substr($rinput, 0, strpos($rinput, ','));
3240
+ return apply_filters('IWP_'.$filter, $rinput, $oinput);
3241
+ }
3242
+
3243
+ public function memory_check_current($memory_limit = false) {
3244
+ # Returns in megabytes
3245
+ if ($memory_limit == false) $memory_limit = ini_get('memory_limit');
3246
+ $memory_limit = rtrim($memory_limit);
3247
+ $memory_unit = $memory_limit[strlen($memory_limit)-1];
3248
+ if ((int)$memory_unit == 0 && $memory_unit !== '0') {
3249
+ $memory_limit = substr($memory_limit,0,strlen($memory_limit)-1);
3250
+ } else {
3251
+ $memory_unit = '';
3252
+ }
3253
+ switch($memory_unit) {
3254
+ case '':
3255
+ $memory_limit = floor($memory_limit/1048576);
3256
+ break;
3257
+ case 'K':
3258
+ case 'k':
3259
+ $memory_limit = floor($memory_limit/1024);
3260
+ break;
3261
+ case 'G':
3262
+ $memory_limit = $memory_limit*1024;
3263
+ break;
3264
+ case 'M':
3265
+ //assumed size, no change needed
3266
+ break;
3267
+ }
3268
+ return $memory_limit;
3269
+ }
3270
+
3271
+ public function memory_check($memory, $check_using = false) {
3272
+ $memory_limit = $this->memory_check_current($check_using);
3273
+ return ($memory_limit >= $memory)?true:false;
3274
+ }
3275
+
3276
+ public function analyse_db_file($timestamp, $res, $db_file = false, $header_only = false) {
3277
+
3278
+ $mess = array(); $warn = array(); $err = array(); $info = array();
3279
+
3280
+ $wp_version = $this->get_wordpress_version();
3281
+ global $wpdb;
3282
+
3283
+ $iwp_backup_dir = $this->backups_dir_location();
3284
+
3285
+ if (false === $db_file) {
3286
+ # This attempts to raise the maximum packet size. This can't be done within the session, only globally. Therefore, it has to be done before the session starts; in our case, during the pre-analysis.
3287
+ $this->get_max_packet_size();
3288
+
3289
+ $backup = $this->get_backup_history($timestamp);
3290
+ if (!isset($backup['nonce']) || !isset($backup['db'])) return array($mess, $warn, $err, $info);
3291
+
3292
+ $db_file = (is_string($backup['db'])) ? $iwp_backup_dir.'/'.$backup['db'] : $iwp_backup_dir.'/'.$backup['db'][0];
3293
+ }
3294
+
3295
+ if (!is_readable($db_file)) return array($mess, $warn, $err, $info);
3296
+
3297
+ // Encrypted - decrypt it
3298
+ if ($this->is_db_encrypted($db_file)) {
3299
+
3300
+ $encryption = empty($res['IWP_encryptionphrase']) ? IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_encryptionphrase') : $res['IWP_encryptionphrase'];
3301
+
3302
+ if (!$encryption) {
3303
+ if (class_exists('IWP_MMB_Addon_MoreDatabase')) {
3304
+ $err[] = sprintf(__('Error: %s', 'InfiniteWP'), __('Decryption failed. The database file is encrypted, but you have no encryption key entered.', 'InfiniteWP'));
3305
+ } else {
3306
+ $err[] = sprintf(__('Error: %s', 'InfiniteWP'), __('Decryption failed. The database file is encrypted.', 'InfiniteWP'));
3307
+ }
3308
+ return array($mess, $warn, $err, $info);
3309
+ }
3310
+
3311
+ $decrypted_file = $this->decrypt($db_file, $encryption);
3312
+
3313
+ if (is_array($decrypted_file)) {
3314
+ $db_file = $decrypted_file['fullpath'];
3315
+ } else {
3316
+ $err[] = __('Decryption failed. The most likely cause is that you used the wrong key.','InfiniteWP');
3317
+ return array($mess, $warn, $err, $info);
3318
+ }
3319
+
3320
+
3321
+ }
3322
+
3323
+ # Even the empty schema when gzipped comes to 1565 bytes; a blank WP 3.6 install at 5158. But we go low, in case someone wants to share single tables.
3324
+ if (filesize($db_file) < 1000) {
3325
+ $err[] = sprintf(__('The database is too small to be a valid WordPress database (size: %s Kb).','InfiniteWP'), round(filesize($db_file)/1024, 1));
3326
+ return array($mess, $warn, $err, $info);
3327
+ }
3328
+
3329
+ $is_plain = ('.gz' == substr($db_file, -3, 3)) ? false : true;
3330
+
3331
+ $dbhandle = ($is_plain) ? fopen($db_file, 'r') : $this->gzopen_for_read($db_file, $warn, $err);
3332
+ if (!is_resource($dbhandle)) {
3333
+ $err[] = __('Failed to open database file.', 'InfiniteWP');
3334
+ return array($mess, $warn, $err, $info);
3335
+ }
3336
+
3337
+ $info['timestamp'] = $timestamp;
3338
+
3339
+ # Analyse the file, print the results.
3340
+
3341
+ $line = 0;
3342
+ $old_siteurl = '';
3343
+ $old_home = '';
3344
+ $old_table_prefix = '';
3345
+ $old_siteinfo = array();
3346
+ $gathering_siteinfo = true;
3347
+ $old_wp_version = '';
3348
+ $old_php_version = '';
3349
+
3350
+ $tables_found = array();
3351
+
3352
+ // TODO: If the backup is the right size/checksum, then we could restore the $line <= 100 in the 'while' condition and not bother scanning the whole thing? Or better: sort the core tables to be first so that this usually terminates early
3353
+
3354
+ $wanted_tables = array('terms', 'term_taxonomy', 'term_relationships', 'commentmeta', 'comments', 'links', 'options', 'postmeta', 'posts', 'users', 'usermeta');
3355
+
3356
+ $migration_warning = false;
3357
+ $processing_create = false;
3358
+ $db_version = $wpdb->db_version();
3359
+
3360
+ // Don't set too high - we want a timely response returned to the browser
3361
+ // Until April 2015, this was always 90. But we've seen a few people with ~1GB databases (uncompressed), and 90s is not enough. Note that we don't bother checking here if it's compressed - having a too-large timeout when unexpected is harmless, as it won't be hit. On very large dbs, they're expecting it to take a while.
3362
+ // "120 or 240" is a first attempt at something more useful than just fixed at 90 - but should be sufficient (as 90 was for everyone without ~1GB databases)
3363
+ $default_dbscan_timeout = (filesize($db_file) < 31457280) ? 120 : 240;
3364
+ $dbscan_timeout = (defined('IWP_DBSCAN_TIMEOUT') && is_numeric(IWP_DBSCAN_TIMEOUT)) ? IWP_DBSCAN_TIMEOUT : $default_dbscan_timeout;
3365
+ @set_time_limit($dbscan_timeout);
3366
+
3367
+ while ((($is_plain && !feof($dbhandle)) || (!$is_plain && !gzeof($dbhandle))) && ($line<100 || (!$header_only && count($wanted_tables)>0))) {
3368
+ $line++;
3369
+ // Up to 1MB
3370
+ $buffer = ($is_plain) ? rtrim(fgets($dbhandle, 1048576)) : rtrim(gzgets($dbhandle, 1048576));
3371
+ // Comments are what we are interested in
3372
+ if (substr($buffer, 0, 1) == '#') {
3373
+ $processing_create = false;
3374
+ if ('' == $old_siteurl && preg_match('/^\# Backup of: (http(.*))$/', $buffer, $matches)) {
3375
+ $old_siteurl = untrailingslashit($matches[1]);
3376
+ $mess[] = __('Backup of:', 'InfiniteWP').' '.htmlspecialchars($old_siteurl).((!empty($old_wp_version)) ? ' '.sprintf(__('(version: %s)', 'InfiniteWP'), $old_wp_version) : '');
3377
+ // Check for should-be migration
3378
+ if ($old_siteurl != untrailingslashit(site_url())) {
3379
+ if (!$migration_warning) {
3380
+ $migration_warning = true;
3381
+ $powarn = apply_filters('IWP_dbscan_urlchange', sprintf(__('Warning: %s', 'InfiniteWP'), 'URL not matching'), $old_siteurl, $res);
3382
+ if (!empty($powarn)) $warn[] = $powarn;
3383
+ }
3384
+ // Explicitly set it, allowing the consumer to detect when the result was unknown
3385
+ $info['same_url'] = false;
3386
+
3387
+ if ($this->mod_rewrite_unavailable(false)) {
3388
+ $warn[] = sprintf(__('You are using the %s webserver, but do not seem to have the %s module loaded.', 'InfiniteWP'), 'Apache', 'mod_rewrite').' '.sprintf(__('You should enable %s to make any pretty permalinks (e.g. %s) work', 'InfiniteWP'), 'mod_rewrite', 'http://example.com/my-page/');
3389
+ }
3390
+
3391
+ } else {
3392
+ $info['same_url'] = true;
3393
+ }
3394
+ } elseif ('' == $old_home && preg_match('/^\# Home URL: (http(.*))$/', $buffer, $matches)) {
3395
+ $old_home = untrailingslashit($matches[1]);
3396
+ // Check for should-be migration
3397
+ if (!$migration_warning && $old_home != home_url()) {
3398
+ $migration_warning = true;
3399
+ $powarn = apply_filters('IWP_dbscan_urlchange', sprintf(__('Warning: %s', 'InfiniteWP'), 'URL not matching'), $old_siteurl, $res);
3400
+ if (!empty($powarn)) $warn[] = $powarn;
3401
+ }
3402
+ } elseif (!isset($info['created_by_version']) && preg_match('/^\# Created by InfiniteWP version ([\d\.]+)/', $buffer, $matches)) {
3403
+ $info['created_by_version'] = trim($matches[1]);
3404
+ } elseif ('' == $old_wp_version && preg_match('/^\# WordPress Version: ([0-9]+(\.[0-9]+)+)(-[-a-z0-9]+,)?(.*)$/', $buffer, $matches)) {
3405
+ $old_wp_version = $matches[1];
3406
+ if (!empty($matches[3])) $old_wp_version .= substr($matches[3], 0, strlen($matches[3])-1);
3407
+ if (version_compare($old_wp_version, $wp_version, '>')) {
3408
+ $warn[] = sprintf(__('You are importing from a newer version of WordPress (%s) into an older one (%s). There are no guarantees that WordPress can handle this.', 'InfiniteWP'), $old_wp_version, $wp_version);
3409
+ }
3410
+ if (preg_match('/running on PHP ([0-9]+\.[0-9]+)(\s|\.)/', $matches[4], $nmatches) && preg_match('/^([0-9]+\.[0-9]+)(\s|\.)/', PHP_VERSION, $cmatches)) {
3411
+ $old_php_version = $nmatches[1];
3412
+ $current_php_version = $cmatches[1];
3413
+ if (version_compare($old_php_version, $current_php_version, '>')) {
3414
+ $warn[] = sprintf(__('The site in this backup was running on a webserver with version %s of %s. ', 'InfiniteWP'), $old_php_version, 'PHP').' '.sprintf(__('This is significantly newer than the server which you are now restoring onto (version %s).', 'InfiniteWP'), PHP_VERSION).' '.sprintf(__('You should only proceed if you cannot update the current server and are confident (or willing to risk) that your plugins/themes/etc. are compatible with the older %s version.', 'InfiniteWP'), 'PHP').' '.sprintf(__('Any support requests to do with %s should be raised with your web hosting company.', 'InfiniteWP'), 'PHP');
3415
+ }
3416
+ }
3417
+ } elseif ('' == $old_table_prefix && (preg_match('/^\# Table prefix: (\S+)$/', $buffer, $matches) || preg_match('/^-- Table prefix: (\S+)$/i', $buffer, $matches))) {
3418
+ $old_table_prefix = $matches[1];
3419
+ } elseif (empty($info['label']) && preg_match('/^\# Label: (.*)$/', $buffer, $matches)) {
3420
+ $info['label'] = $matches[1];
3421
+ $mess[] = __('Backup label:', 'InfiniteWP').' '.htmlspecialchars($info['label']);
3422
+ } elseif ($gathering_siteinfo && preg_match('/^\# Site info: (\S+)$/', $buffer, $matches)) {
3423
+ if ('end' == $matches[1]) {
3424
+ $gathering_siteinfo = false;
3425
+ // Sanity checks
3426
+ if (isset($old_siteinfo['multisite']) && !$old_siteinfo['multisite'] && is_multisite()) {
3427
+ $warn[] = __('You are running on WordPress multisite - but your backup is not of a multisite site.', 'InfiniteWP').' '.__('It will be imported as a new site.', 'InfiniteWP').' <a href="https://InfiniteWP.com/information-on-importing-a-single-site-wordpress-backup-into-a-wordpress-network-i-e-multisite/">'.__('Please read this link for important information on this process.', 'InfiniteWP').'</a>';
3428
+
3429
+ if (!class_exists('IWP_MMBAddOn_MultiSite') || !class_exists('IWP_MMB_Addons_Migrator')) {
3430
+ $err[] = sprintf(__('Error: %s', 'InfiniteWP'), sprintf(__('To import an ordinary WordPress site into a multisite installation requires %s.', 'InfiniteWP'), 'InfiniteWP Premium'));
3431
+ return array($mess, $warn, $err, $info);
3432
+ }
3433
+ } elseif (isset($old_siteinfo['multisite']) && $old_siteinfo['multisite'] && !is_multisite()) {
3434
+ $warn[] = __('Warning:', 'InfiniteWP').' '.__('Your backup is of a WordPress multisite install; but this site is not. Only the first site of the network will be accessible.', 'InfiniteWP').' <a href="https://codex.wordpress.org/Create_A_Network">'.__('If you want to restore a multisite backup, you should first set up your WordPress installation as a multisite.', 'InfiniteWP').'</a>';
3435
+ }
3436
+ } elseif (preg_match('/^([^=]+)=(.*)$/', $matches[1], $kvmatches)) {
3437
+ $key = $kvmatches[1];
3438
+ $val = $kvmatches[2];
3439
+ if ('multisite' == $key) {
3440
+ $info['multisite'] = $val ? true : false;
3441
+ if ($val) $mess[] = '<strong>'.__('Site information:', 'InfiniteWP').'</strong> '.'backup is of a WordPress Network';
3442
+ }
3443
+ $old_siteinfo[$key]=$val;
3444
+ }
3445
+ } elseif (preg_match('/^\# Skipped tables: (.*)$/', $buffer, $matches)) {
3446
+ $skipped_tables = explode(',', $matches[1]);
3447
+ }
3448
+
3449
+ } elseif (preg_match('/^\s*create table \`?([^\`\(]*)\`?\s*\(/i', $buffer, $matches)) {
3450
+ $table = $matches[1];
3451
+ $tables_found[] = $table;
3452
+ if ($old_table_prefix) {
3453
+ // Remove prefix
3454
+ $table = $this->str_replace_once($old_table_prefix, '', $table);
3455
+ if (in_array($table, $wanted_tables)) {
3456
+ $wanted_tables = array_diff($wanted_tables, array($table));
3457
+ }
3458
+ }
3459
+ if (substr($buffer, -1, 1) != ';') $processing_create = true;
3460
+ } elseif ($processing_create) {
3461
+ if (substr($buffer, -1, 1) == ';') $processing_create = false;
3462
+ static $mysql_version_warned = false;
3463
+ if (!$mysql_version_warned && version_compare($db_version, '5.2.0', '<') && preg_match('/(CHARSET|COLLATE)[= ]utf8mb4/', $buffer)) {
3464
+ $mysql_version_warned = true;
3465
+ $err[] = sprintf(__('Error: %s', 'InfiniteWP'), sprintf(__('The database backup uses MySQL features not available in the old MySQL version (%s) that this site is running on.', 'InfiniteWP'), $db_version).' '.__('You must upgrade MySQL to be able to use this database.', 'InfiniteWP'));
3466
+ }
3467
+ }
3468
+ }
3469
+
3470
+ if ($is_plain) {
3471
+ @fclose($dbhandle);
3472
+ } else {
3473
+ @gzclose($dbhandle);
3474
+ }
3475
+
3476
+ /* $blog_tables = "CREATE TABLE $wpdb->terms (
3477
+ CREATE TABLE $wpdb->term_taxonomy (
3478
+ CREATE TABLE $wpdb->term_relationships (
3479
+ CREATE TABLE $wpdb->commentmeta (
3480
+ CREATE TABLE $wpdb->comments (
3481
+ CREATE TABLE $wpdb->links (
3482
+ CREATE TABLE $wpdb->options (
3483
+ CREATE TABLE $wpdb->postmeta (
3484
+ CREATE TABLE $wpdb->posts (
3485
+ $users_single_table = "CREATE TABLE $wpdb->users (
3486
+ $users_multi_table = "CREATE TABLE $wpdb->users (
3487
+ $usermeta_table = "CREATE TABLE $wpdb->usermeta (
3488
+ $ms_global_tables = "CREATE TABLE $wpdb->blogs (
3489
+ CREATE TABLE $wpdb->blog_versions (
3490
+ CREATE TABLE $wpdb->registration_log (
3491
+ CREATE TABLE $wpdb->site (
3492
+ CREATE TABLE $wpdb->sitemeta (
3493
+ CREATE TABLE $wpdb->signups (
3494
+ */
3495
+ if (!isset($skipped_tables)) $skipped_tables = array();
3496
+ $missing_tables = array();
3497
+ if ($old_table_prefix) {
3498
+ if (!$header_only) {
3499
+ foreach ($wanted_tables as $table) {
3500
+ if (!in_array($old_table_prefix.$table, $tables_found)) {
3501
+ $missing_tables[] = $table;
3502
+ }
3503
+ }
3504
+
3505
+ foreach ($missing_tables as $key => $value) {
3506
+ if (in_array($old_table_prefix.$value, $skipped_tables)) {
3507
+ unset($missing_tables[$key]);
3508
+ }
3509
+ }
3510
+
3511
+ if (count($missing_tables)>0) {
3512
+ $warn[] = sprintf(__('This database backup is missing core WordPress tables: %s', 'InfiniteWP'), implode(', ', $missing_tables));
3513
+ }
3514
+ if (count($skipped_tables)>0) {
3515
+ $warn[] = sprintf(__('This database backup has the following WordPress tables excluded: %s', 'InfiniteWP'), implode(', ', $skipped_tables));
3516
+ }
3517
+ }
3518
+ } else {
3519
+ if (empty($backup['meta_foreign'])) {
3520
+ $warn[] = __('InfiniteWP was unable to find the table prefix when scanning the database backup.', 'InfiniteWP');
3521
+ }
3522
+ }
3523
+
3524
+ // //need to make sure that we reset the file back to .crypt before clean temp files
3525
+ // $db_file = $decrypted_file['fullpath'].'.crypt';
3526
+ // unlink($decrypted_file['fullpath']);
3527
+
3528
+ return array($mess, $warn, $err, $info);
3529
+
3530
+ }
3531
+
3532
+ private function gzopen_for_read($file, &$warn, &$err) {
3533
+ if (!function_exists('gzopen') || !function_exists('gzread')) {
3534
+ $missing = '';
3535
+ if (!function_exists('gzopen')) $missing .= 'gzopen';
3536
+ if (!function_exists('gzread')) $missing .= ($missing) ? ', gzread' : 'gzread';
3537
+ $err[] = sprintf(__("Your web server's PHP installation has these functions disabled: %s.", 'InfiniteWP'), $missing).' '.sprintf(__('Your hosting company must enable these functions before %s can work.', 'InfiniteWP'), __('restoration', 'InfiniteWP'));
3538
+ return false;
3539
+ }
3540
+ if (false === ($dbhandle = gzopen($file, 'r'))) return false;
3541
+
3542
+ if (!function_exists('gzseek')) return $dbhandle;
3543
+
3544
+ if (false === ($bytes = gzread($dbhandle, 3))) return false;
3545
+ # Double-gzipped?
3546
+ if ('H4sI' != base64_encode($bytes)) {
3547
+ if (0 === gzseek($dbhandle, 0)) {
3548
+ return $dbhandle;
3549
+ } else {
3550
+ @gzclose($dbhandle);
3551
+ return gzopen($file, 'r');
3552
+ }
3553
+ }
3554
+ # Yes, it's double-gzipped
3555
+
3556
+ $what_to_return = false;
3557
+ $mess = __('The database file appears to have been compressed twice - probably the website you downloaded it from had a mis-configured webserver.', 'InfiniteWP');
3558
+ $messkey = 'doublecompress';
3559
+ $err_msg = '';
3560
+
3561
+ if (false === ($fnew = fopen($file.".tmp", 'w')) || !is_resource($fnew)) {
3562
+
3563
+ @gzclose($dbhandle);
3564
+ $err_msg = __('The attempt to undo the double-compression failed.', 'InfiniteWP');
3565
+
3566
+ } else {
3567
+
3568
+ @fwrite($fnew, $bytes);
3569
+ $emptimes = 0;
3570
+ while (!gzeof($dbhandle)) {
3571
+ $bytes = @gzread($dbhandle, 262144);
3572
+ if (empty($bytes)) {
3573
+ $emptimes++;
3574
+ $this->log("Got empty gzread ($emptimes times)");
3575
+ if ($emptimes>2) break;
3576
+ } else {
3577
+ @fwrite($fnew, $bytes);
3578
+ }
3579
+ }
3580
+
3581
+ gzclose($dbhandle);
3582
+ fclose($fnew);
3583
+ # On some systems (all Windows?) you can't rename a gz file whilst it's gzopened
3584
+ if (!rename($file.".tmp", $file)) {
3585
+ $err_msg = __('The attempt to undo the double-compression failed.', 'InfiniteWP');
3586
+ } else {
3587
+ $mess .= ' '.__('The attempt to undo the double-compression succeeded.', 'InfiniteWP');
3588
+ $messkey = 'doublecompressfixed';
3589
+ $what_to_return = gzopen($file, 'r');
3590
+ }
3591
+
3592
+ }
3593
+
3594
+ $warn[$messkey] = $mess;
3595
+ if (!empty($err_msg)) $err[] = $err_msg;
3596
+ return $what_to_return;
3597
+ }
3598
+
3599
+ # TODO: Remove legacy storage setting keys from here
3600
+ // These are used in 4 places (Feb 2016 - of course, you should re-scan the code to check if relying on this): showing current settings on the debug modal, wiping all current settings, getting a settings bundle to restore when migrating, and for relevant keys in POST-ed data when saving settings over AJAX
3601
+ public function get_settings_keys() {
3602
+ return array('IWP_autobackup_default', 'IWP_dropbox', 'IWP_googledrive', 'IWP_tmp_googledrive_access_token', 'IWP_dismissedautobackup', 'dismissed_general_notices_until', 'dismissed_season_notices_until', 'IWP_dismissedexpiry', 'IWP_dismisseddashnotice', 'IWP_interval', 'IWP_interval_increments', 'IWP_interval_database', 'IWP_retain', 'IWP_retain_db', 'IWP_encryptionphrase', 'IWP_service', 'IWP_googledrive_clientid', 'IWP_googledrive_secret', 'IWP_googledrive_remotepath', 'IWP_ftp', 'IWP_server_address', 'IWP_dir', 'IWP_email', 'IWP_delete_local', 'IWP_debug_mode', 'IWP_include_plugins', 'IWP_include_themes', 'IWP_include_uploads', 'IWP_include_others', 'IWP_include_wpcore', 'IWP_include_wpcore_exclude', 'IWP_include_more', 'IWP_include_blogs', 'IWP_include_mu-plugins',
3603
+ 'IWP_include_others_exclude', 'IWP_include_uploads_exclude', 'IWP_lastmessage', 'IWP_googledrive_token', 'IWP_dropboxtk_request_token', 'IWP_dropboxtk_access_token', 'IWP_adminlocking', 'IWP_IWPvault', 'IWP_remotesites', 'IWP_migrator_localkeys', 'IWP_central_localkeys', 'IWP_retain_extrarules', 'IWP_googlecloud', 'IWP_include_more_path', 'IWP_split_every', 'IWP_ssl_nossl', 'IWP_backupdb_nonwp', 'IWP_extradbs', 'IWP_combine_jobs_around',
3604
+ 'IWP_last_backup', 'IWP_starttime_files', 'IWP_starttime_db', 'IWP_startday_db', 'IWP_startday_files', 'IWP_sftp', 'IWP_s3', 'IWP_s3generic', 'IWP_dreamhost', 'IWP_s3generic_login', 'IWP_s3generic_pass', 'IWP_s3generic_remote_path', 'IWP_s3generic_endpoint', 'IWP_webdav', 'IWP_openstack', 'IWP_onedrive', 'IWP_azure', 'IWP_cloudfiles', 'IWP_cloudfiles_user', 'IWP_cloudfiles_apikey', 'IWP_cloudfiles_path', 'IWP_cloudfiles_authurl', 'IWP_ssl_useservercerts', 'IWP_ssl_disableverify', 'IWP_s3_login', 'IWP_s3_pass', 'IWP_s3_remote_path', 'IWP_dreamobjects_login', 'IWP_dreamobjects_pass', 'IWP_dreamobjects_remote_path', 'IWP_dreamobjects', 'IWP_report_warningsonly', 'IWP_report_wholebackup', 'IWP_log_syslog', 'IWP_extradatabases');
3605
+ }
3606
+
3607
+ /**
3608
+ * Returns the member of the array with key (int)0, as a new array. This function is used as a callback for array_map().
3609
+ *
3610
+ * @param Array $a - the array
3611
+ *
3612
+ * @return Array - with keys 'name' and 'type'
3613
+ */
3614
+ private function cb_get_name_base_type($a) {
3615
+ return array('name' => $a[0], 'type' => 'BASE TABLE');
3616
+ }
3617
+
3618
+ /**
3619
+ * Returns the members of the array with keys (int)0 and (int)1, as part of a new array.
3620
+ *
3621
+ * @param Array $a - the array
3622
+ *
3623
+ * @return Array - keys are 'name' and 'type'
3624
+ */
3625
+ private function cb_get_name_type($a) {
3626
+ return array('name' => $a[0], 'type' => $a[1]);
3627
+ }
3628
+
3629
+ /**
3630
+ * Returns the member of the array with key (string)'name'. This function is used as a callback for array_map().
3631
+ *
3632
+ * @param Array $a - the array
3633
+ *
3634
+ * @return Mixed - the value with key (string)'name'
3635
+ */
3636
+ private function cb_get_name($a) {
3637
+ return $a['name'];
3638
+ }
3639
+
3640
+ public function get_backup_stats()
3641
+ {
3642
+ global $wpdb;
3643
+
3644
+ $stats = array();
3645
+ $new_backup_method = $this->get_backup_history();
3646
+ $task_res = array();
3647
+ if (!empty($new_backup_method)) {
3648
+ foreach ($new_backup_method as $time => $value) {
3649
+ $task_res[$value['label']][$time]=$value;
3650
+ }
3651
+ }
3652
+ $stats = $task_res;
3653
+
3654
+
3655
+ return $stats;
3656
+
3657
+ }
3658
+
3659
+ public function getRunningBackupStatus($params){
3660
+ $result = get_option('IWP_backup_status');
3661
+ $job_id = $params['params']['backup_id'];
3662
+ $job_data = $this->jobdata_getarray($job_id);
3663
+ if ($result == '1') {
3664
+ return array('success'=>array('status' => 'partiallyCompleted', 'params' => $params['params'], 'jobdata'=>$job_data));
3665
+ } elseif ($result == '0') {
3666
+ $cron = $this->get_cron($job_id);
3667
+ if ($cron == false) {
3668
+ $last_backup = $this->last_backup_staus();
3669
+ if (empty($last_backup) || empty($last_backup['error'])) {
3670
+ $IWP_last_backup = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_last_backup');
3671
+ return array('success'=>array('status' => 'completed', 'last_backup' => $IWP_last_backup, 'wp_content_url' => content_url(), 'backup_id' => $job_id));
3672
+ }
3673
+ $errorMsg = 'Backup Failed';
3674
+ if (!empty($last_backup['error'])) {
3675
+ $errorMsg = $last_backup['error'];
3676
+ }
3677
+ return array('error' => array('error_code' => 'backup_failed', 'error' => $errorMsg, 'jobdata' => $job_data, 'wp_content_url' => content_url(), 'backup_id' => $job_id));
3678
+ }
3679
+ if (!empty($cron)) {
3680
+ if (time()> $cron[0]) {
3681
+ wp_cron();
3682
+ }
3683
+ }
3684
+ return array('success'=>array('status' => 'partiallyCompleted', 'params' => $params['params'], 'jobdata'=>$job_data, 'cron_data' => $cron), 'wp_content_url' => content_url() );
3685
+ }
3686
+
3687
+ }
3688
+
3689
+ public function ensure_phpseclib($classes = false, $class_paths = false) {
3690
+
3691
+ $this->no_deprecation_warnings_on_php7();
3692
+
3693
+ if ($classes) {
3694
+ $any_missing = false;
3695
+ if (is_string($classes)) $classes = array($classes);
3696
+ foreach ($classes as $cl) {
3697
+ if (!class_exists($cl)) $any_missing = true;
3698
+ }
3699
+ if (!$any_missing) return;
3700
+ }
3701
+
3702
+ if ($class_paths) {
3703
+ $phpseclib_dir = $GLOBALS['iwp_mmb_plugin_dir'].'/lib/phpseclib/phpseclib/phpseclib';
3704
+ if (false === strpos(get_include_path(), $phpseclib_dir)) set_include_path(get_include_path().PATH_SEPARATOR.$phpseclib_dir);
3705
+ if (is_string($class_paths)) $class_paths = array($class_paths);
3706
+ foreach ($class_paths as $cp) {
3707
+ include_once($phpseclib_dir.'/'.$cp.'.php');
3708
+ }
3709
+ }
3710
+ }
3711
+
3712
+ public function fetch_log($backup_nonce = '', $log_pointer = 0, $output_format = 'html') {
3713
+ global $iwp_backup_core;
3714
+
3715
+ if (empty($backup_nonce)) {
3716
+ list($mod_time, $log_file, $nonce) = $iwp_backup_core->last_modified_log();
3717
+ } else {
3718
+ $nonce = $backup_nonce;
3719
+ }
3720
+
3721
+ if (!preg_match('/^[0-9a-f]+$/', $nonce)) die('Security check');
3722
+
3723
+ $log_content = '';
3724
+ $new_pointer = $log_pointer;
3725
+
3726
+ if (!empty($nonce)) {
3727
+ $iwp_backup_dir = $iwp_backup_core->backups_dir_location();
3728
+
3729
+ $potential_log_file = $iwp_backup_dir."/log.".$nonce.".txt";
3730
+
3731
+ if (is_readable($potential_log_file)){
3732
+
3733
+ $templog_array = array();
3734
+ $log_file = fopen($potential_log_file, "r");
3735
+ if ($log_pointer > 0) fseek($log_file, $log_pointer);
3736
+
3737
+ while (($buffer = fgets($log_file, 4096)) !== false) {
3738
+ $templog_array[] = $buffer;
3739
+ }
3740
+ if (!feof($log_file)) {
3741
+ $templog_array[] = __('Error: unexpected file read fail', 'InfiniteWP');
3742
+ }
3743
+
3744
+ $new_pointer = ftell($log_file);
3745
+ $log_content = implode("", $templog_array);
3746
+
3747
+
3748
+ } else {
3749
+ $log_content .= __('The log file could not be read.', 'InfiniteWP');
3750
+ }
3751
+
3752
+ } else {
3753
+ $log_content .= __('The log file could not be read.', 'InfiniteWP');
3754
+ }
3755
+
3756
+ if ('html' == $output_format) $log_content = htmlspecialchars($log_content);
3757
+
3758
+ $ret_array = array(
3759
+ 'log' => $log_content,
3760
+ 'nonce' => $nonce,
3761
+ 'pointer' => $new_pointer
3762
+ );
3763
+
3764
+ return $ret_array;
3765
+ }
3766
+
3767
+ public function get_cron($job_id = false) {
3768
+
3769
+ $cron = get_option('cron');
3770
+ if (!is_array($cron)) $cron = array();
3771
+ if (false === $job_id) return $cron;
3772
+
3773
+ foreach ($cron as $time => $job) {
3774
+ if (isset($job['IWP_backup_resume'])) {
3775
+ foreach ($job['IWP_backup_resume'] as $hook => $info) {
3776
+ if (isset($info['args'][1]) && $job_id == $info['args'][1]) {
3777
+ $jobdata = $this->jobdata_getarray($job_id);
3778
+ return (!is_array($jobdata)) ? false : array($time);
3779
+ }
3780
+ }
3781
+ }
3782
+ }
3783
+ }
3784
+
3785
+ public function last_backup_staus() {
3786
+ $last_backup = array();
3787
+ $IWP_last_backup = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_last_backup');
3788
+
3789
+ if ($IWP_last_backup) {
3790
+
3791
+ // Show errors + warnings
3792
+ if (is_array($IWP_last_backup['errors'])) {
3793
+ foreach ($IWP_last_backup['errors'] as $err) {
3794
+ $level = (is_array($err)) ? $err['level'] : 'error';
3795
+ $message = (is_array($err)) ? $err['message'] : $err;
3796
+
3797
+ if ('warning' == $level) {
3798
+ $last_backup['warning'] = $message;
3799
+ } else {
3800
+ $last_backup['error'] = $message;
3801
+ }
3802
+
3803
+ }
3804
+ }
3805
+
3806
+ }
3807
+
3808
+ return $last_backup;
3809
+
3810
+ }
3811
+
3812
+ public function backupable_file_entities_final($arr, $full_info) {
3813
+ $path = ABSPATH;
3814
+ if (is_array($path)) {
3815
+ $path = array_map('untrailingslashit', $path);
3816
+ if (1 == count($path)) $path = array_shift($path);
3817
+ } else {
3818
+ $path = untrailingslashit($path);
3819
+ }
3820
+ if ($full_info) {
3821
+ $arr['more'] = array(
3822
+ 'path' => $path,
3823
+ 'description' => __('Any other file/directory on your server that you wish to back up', 'InfiniteWP'),
3824
+ 'shortdescription' => __('More Files', 'InfiniteWP'),
3825
+ 'restorable' => false
3826
+ );
3827
+ } else {
3828
+ $arr['more'] = $path;
3829
+ }
3830
+ return $arr;
3831
+ }
3832
+
3833
+ public function set_backup_task_option($params){
3834
+ if (empty($params)) {
3835
+ return false;
3836
+ }
3837
+ $exclude_others = '';
3838
+ $exclude_uploads = '';
3839
+ $IWP_service = false;
3840
+ update_option('IWP_delete_local', 1);
3841
+ if (!empty($params['args']['exclude'])) {
3842
+ if (defined('IWP_DEFAULT_OTHERS_EXCLUDE')) {
3843
+ $exclude_others = IWP_DEFAULT_OTHERS_EXCLUDE.',';
3844
+ }
3845
+ $exclude_others.= $params['args']['exclude'];
3846
+ $exclude_uploads.= $params['args']['exclude'];
3847
+ update_option('IWP_include_others_exclude', $exclude_others);
3848
+ update_option('IWP_include_uploads_exclude', $exclude_uploads);
3849
+ }
3850
+ if (!empty($params['args']['include'])) {
3851
+ if (defined('IWP_DEFAULT_INCLUDES')) {
3852
+ $include = IWP_DEFAULT_INCLUDES.',';
3853
+ }
3854
+ $include.= implode(",", $params['args']['include']);
3855
+ update_option('IWP_default_includes', $include);
3856
+ }
3857
+ if (!empty($params['args']['exclude_extensions']) && !defined('IWP_EXCLUDE_EXTENSIONS')) {
3858
+ define('IWP_EXCLUDE_EXTENSIONS', $params['args']['exclude_extensions']);
3859
+ }
3860
+
3861
+ if (!empty($params['account_info'])) {
3862
+ if (!empty($params['account_info']['iwp_ftp'])) {
3863
+ $ftp_details = $params['account_info']['iwp_ftp'];
3864
+ $opts = array(
3865
+ 'user' => $ftp_details['ftp_username'],
3866
+ 'pass' => $ftp_details['ftp_password'],
3867
+ 'host' => $ftp_details['ftp_hostname'],
3868
+ 'path' => $ftp_details['ftp_remote_folder'],
3869
+ 'ftp_site_folder' => $ftp_details['ftp_site_folder'],
3870
+ 'passive' => $ftp_details['ftp_passive']?true:false
3871
+ );
3872
+ if ($ftp_details['use_sftp']) {
3873
+ update_option('IWP_service', 'sftp');
3874
+ IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_sftp', $opts);
3875
+ }else{
3876
+ update_option('IWP_service', 'ftp');
3877
+ IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_ftp', $opts);
3878
+ }
3879
+ }elseif (!empty($params['account_info']['iwp_amazon_s3'])) {
3880
+ update_option('IWP_service', 's3');
3881
+ $s3_details = $params['account_info']['iwp_amazon_s3'];
3882
+ if (!empty($s3_details['as3_directory'])) {
3883
+ $path = trim($s3_details['as3_bucket'],'/').'/'.trim($s3_details['as3_directory'],'/');
3884
+ }else{
3885
+ $path = $s3_details['as3_bucket'];
3886
+ }
3887
+ $opts = array(
3888
+ 'endpoint' => '',
3889
+ 'accesskey' => $s3_details['as3_access_key'],
3890
+ 'secretkey' => $s3_details['as3_secure_key'],
3891
+ 'path' => $path,
3892
+ 'as3_site_folder' => $s3_details['as3_site_folder'],
3893
+ 'server_side_encryption' => $s3_details['server_side_encryption']?true:false
3894
+ );
3895
+ IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_s3', $opts);
3896
+
3897
+ }elseif (!empty($params['account_info']['iwp_dropbox'])) {
3898
+ update_option('IWP_service', 'dropbox');
3899
+ $dropbox_details = $params['account_info']['iwp_dropbox'];
3900
+
3901
+ $opts = array(
3902
+ 'appkey' => $dropbox_details['dropbox_app_key'],
3903
+ 'secret' => $dropbox_details['dropbox_app_secure_key'],
3904
+ 'tk_access_token' => $dropbox_details['dropbox_access_token'],
3905
+ 'folder' => $dropbox_details['dropbox_destination'],
3906
+ 'ownername' => '',
3907
+ 'CSRF' => '',
3908
+ 'dropbox_site_folder' => $dropbox_details['dropbox_site_folder']
3909
+ );
3910
+ IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_dropbox', $opts);
3911
+ }elseif (!empty($params['account_info']['iwp_gdrive'])) {
3912
+ update_option('IWP_service', 'googledrive');
3913
+ $google_details = $params['account_info']['iwp_gdrive'];
3914
+ $opts = array(
3915
+ 'clientid' => $google_details['clientID'],
3916
+ 'secret' => $google_details['clientSecretKey'],
3917
+ 'token' => $google_details['token']['refresh_token'],
3918
+ 'tmp_access_token' => $google_details['token']['access_token'],
3919
+ 'gdrive_site_folder' => $google_details['gdrive_site_folder'],
3920
+ 'ownername' => ''
3921
+ );
3922
+ IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_googledrive', $opts);
3923
+ }
3924
+ }else{
3925
+ delete_option('IWP_service');
3926
+ }
3927
+
3928
+ if (!empty($params['args']['limit'])) {
3929
+ update_option('IWP_retain', $params['args']['limit']);
3930
+ update_option('IWP_retain_db', $params['args']['limit']);
3931
+ }
3932
+ }
3933
+
3934
+ public function get_remote_file($services, $file, $timestamp, $restore = false) {
3935
+ global $iwp_backup_core;
3936
+
3937
+ $fullpath = $iwp_backup_core->backups_dir_location().'/'.$file;
3938
+
3939
+ $storage_objects_and_ids = $iwp_backup_core->get_storage_objects_and_ids($services);
3940
+
3941
+ $is_downloaded = false;
3942
+
3943
+ $iwp_backup_core->register_wp_http_option_hooks();
3944
+
3945
+ foreach ($services as $service) {
3946
+
3947
+ if (empty($service) || 'none' == $service) continue;
3948
+
3949
+ if ($restore) {
3950
+ $service_description = empty($iwp_backup_core->backup_methods[$service]) ? $service : $iwp_backup_core->backup_methods[$service];
3951
+ $iwp_backup_core->log(__("File is not locally present - needs retrieving from remote storage",'updraftplus')." ($service_description)", 'notice-restore');
3952
+ }
3953
+
3954
+ $object = $storage_objects_and_ids[$service]['object'];
3955
+
3956
+ if (!$object->supports_feature('multi_options')) {
3957
+ error_log("UpdraftPlus_Admin::get_remote_file(): Multi options not supported by: ".$service);
3958
+ continue;
3959
+ }
3960
+
3961
+ $instance_ids = $storage_objects_and_ids[$service]['instance_settings'];
3962
+ $backups_instance_ids = isset($backup_history[$timestamp]['service_instance_ids'][$service]) ? $backup_history[$timestamp]['service_instance_ids'][$service] : array(false);
3963
+
3964
+ foreach ($backups_instance_ids as $instance_id) {
3965
+
3966
+ if (isset($instance_ids[$instance_id])) {
3967
+ $options = $instance_ids[$instance_id];
3968
+ } else {
3969
+ // 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.
3970
+ $options = $object->get_options();
3971
+ }
3972
+
3973
+ $object->set_options($options, false, $instance_id);
3974
+
3975
+ $download = $this->download_file($file, $object);
3976
+
3977
+ if (is_readable($fullpath) && false !== $download) {
3978
+ if ($restore) {
3979
+ $iwp_backup_core->log(__("OK", 'InfiniteWP'), 'notice-restore');
3980
+ } else {
3981
+ clearstatcache();
3982
+ $iwp_backup_core->log('Remote fetch was successful (file size: '.round(filesize($fullpath)/1024, 1).' KB)');
3983
+ }
3984
+ break 2;
3985
+ } else {
3986
+ if ($restore) {
3987
+ $iwp_backup_core->log(__("Error", 'InfiniteWP'), 'notice-restore');
3988
+ } else {
3989
+ clearstatcache();
3990
+ if (0 === @filesize($fullpath)) @unlink($fullpath);
3991
+ $iwp_backup_core->log('Remote fetch failed');
3992
+ }
3993
+ }
3994
+ }
3995
+ }
3996
+ $iwp_backup_core->register_wp_http_option_hooks(false);
3997
+ }
3998
+
3999
+ public function get_storage_objects_and_ids($services) {
4000
+
4001
+ $storage_objects_and_ids = array();
4002
+
4003
+ foreach ($services as $method) {
4004
+
4005
+ if ('none' === $method || '' == $method) continue;
4006
+
4007
+ $call_method = 'IWP_MMB_UploadModule_'.$method;
4008
+
4009
+ if (!class_exists($call_method)) include_once $GLOBALS['iwp_mmb_plugin_dir'].'/backup/'.$method.'.php';
4010
+
4011
+ if (class_exists($call_method)) {
4012
+
4013
+ $remote_storage = new $call_method;
4014
+
4015
+ if (!empty($method_objects[$method])) $storage_objects_and_ids[$method] = array();
4016
+
4017
+ $storage_objects_and_ids[$method]['object'] = $remote_storage;
4018
+
4019
+ if ($remote_storage->supports_feature('multi_options')) {
4020
+
4021
+ $settings = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_'.$method);
4022
+
4023
+ if (!is_array($settings)) $settings = array();
4024
+
4025
+ if (!isset($settings['version'])) $settings = $this->update_remote_storage_options_format($method);
4026
+
4027
+ if (is_wp_error($settings)) {
4028
+ error_log("InfiniteWP: failed to convert storage options format: $method");
4029
+ $settings = array('settings' => array());
4030
+ }
4031
+
4032
+ if (empty($settings['settings'])) {
4033
+ // See: https://wordpress.org/support/topic/cannot-setup-connectionauthenticate-with-dropbox/
4034
+ error_log("InfiniteWP: Warning: settings for $method are empty. A dummy field is usually needed so that something is saved.");
4035
+
4036
+ // Try to recover by getting a default set of options for display
4037
+ if (is_callable(array($remote_storage, 'get_default_options'))) {
4038
+ $uuid = 's-'.md5(rand().uniqid().microtime(true));
4039
+ $settings['settings'] = array($uuid => $remote_storage->get_default_options());
4040
+ }
4041
+
4042
+ }
4043
+
4044
+ if (!empty($settings['settings'])) {
4045
+
4046
+ if (!isset($storage_objects_and_ids[$method]['instance_settings'])) $storage_objects_and_ids[$method]['instance_settings'] = array();
4047
+
4048
+ foreach ($settings['settings'] as $instance_id => $storage_options) {
4049
+ $storage_objects_and_ids[$method]['instance_settings'][$instance_id] = $storage_options;
4050
+ }
4051
+ }
4052
+ }
4053
+
4054
+ } else {
4055
+ error_log("InfiniteWP: no such storage class: $call_method");
4056
+ }
4057
+ }
4058
+
4059
+ return $storage_objects_and_ids;
4060
+
4061
+ }
4062
+
4063
+ public function download_file($file, $object) {
4064
+
4065
+ global $iwp_backup_core;
4066
+
4067
+ @set_time_limit(IWP_SET_TIME_LIMIT);
4068
+
4069
+ $service = $object->get_id();
4070
+
4071
+ $iwp_backup_core->log("Requested file from remote service: $service: $file");
4072
+
4073
+ if (method_exists($object, 'download')) {
4074
+
4075
+ try {
4076
+ return $object->download($file);
4077
+ } catch (Exception $e) {
4078
+ $log_message = 'Exception ('.get_class($e).') occurred during download: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
4079
+ $iwp_backup_core->log($log_message);
4080
+ error_log($log_message);
4081
+ $iwp_backup_core->log(sprintf(__('A PHP exception (%s) has occurred: %s', 'InfiniteWP'), get_class($e), $e->getMessage()), 'error');
4082
+ return false;
4083
+ // @codingStandardsIgnoreLine
4084
+ } catch (Error $e) {
4085
+ $log_message = 'PHP Fatal error ('.get_class($e).') has occurred. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
4086
+ $iwp_backup_core->log($log_message);
4087
+ error_log($log_message);
4088
+ $iwp_backup_core->log(sprintf(__('A PHP fatal error (%s) has occurred: %s', 'InfiniteWP'), get_class($e), $e->getMessage()), 'error');
4089
+ return false;
4090
+ }
4091
+ } else {
4092
+ $iwp_backup_core->log("Automatic backup restoration is not available with the method: $service.");
4093
+ $iwp_backup_core->log("$file: ".sprintf(__("The backup archive for this file could not be found. The remote storage method in use (%s) does not allow us to retrieve files. To perform any restoration using InfiniteWP, you will need to obtain a copy of this file and place it inside UpdraftPlus's working folder", 'InfiniteWP'), $service)." (".$this->prune_iwp_dir_prefix($iwp_backup_core->backups_dir_location()).")", 'error');
4094
+ return false;
4095
+ }
4096
+
4097
+ }
4098
+ public function prune_iwp_dir_prefix($iwp_backup_dir) {
4099
+ if ('/' == substr($iwp_backup_dir, 0, 1) || "\\" == substr($iwp_backup_dir, 0, 1) || preg_match('/^[a-zA-Z]:/', $iwp_backup_dir)) {
4100
+ $wcd = trailingslashit(WP_CONTENT_DIR);
4101
+ if (strpos($iwp_backup_dir, $wcd) === 0) {
4102
+ $iwp_backup_dir = substr($iwp_backup_dir, strlen($wcd));
4103
+ }
4104
+ # Legacy
4105
+ // if (strpos($iwp_backup_dir, ABSPATH) === 0) {
4106
+ // $iwp_backup_dir = substr($iwp_backup_dir, strlen(ABSPATH));
4107
+ // }
4108
+ }
4109
+ return $iwp_backup_dir;
4110
+ }
4111
+
4112
+ public function do_iwp_download_backup($params = array()) {
4113
+
4114
+ @set_time_limit(IWP_SET_TIME_LIMIT);
4115
+ global $iwp_backup_core;
4116
+ $timestamp = $params['resultID'];
4117
+ $taskName = $params['taskName'];
4118
+ $job_nonce = dechex($timestamp).substr(md5($taskName), 0, 5);
4119
+ // You need a nonce before you can set job data. And we certainly don't yet have one.
4120
+ $nounce = $this->backup_time_nonce($job_nonce);
4121
+
4122
+ $debug_mode = true;
4123
+
4124
+ // Set the job type before logging, as there can be different logging destinations
4125
+ $running_download = $iwp_backup_core->jobdata_get ('download');
4126
+ if (empty($running_download)) {
4127
+ $iwp_backup_core->jobdata_set('download', $params);
4128
+ $iwp_backup_core->jobdata_set('job_time_ms', $iwp_backup_core->job_time_ms);
4129
+ }
4130
+ if ($debug_mode) $iwp_backup_core->logfile_open($iwp_backup_core->nonce);
4131
+
4132
+ $iwp_backup_dir = $iwp_backup_core->backups_dir_location();
4133
+ if (!empty($params['isNewBackup'])) {
4134
+ $types_to_downlaod = $params['types_to_downlaod'];
4135
+ $types_to_downlaod[] = 'backup_file_basename';
4136
+ // Retrieve the information from our backup history
4137
+ $backup_history = $this->get_backup_history();
4138
+ // Base name
4139
+ foreach ($types_to_downlaod as $key => $type ) {
4140
+ $files = $backup_history[$timestamp][$type];
4141
+ if (is_array($files)) {
4142
+ foreach ($files as $index => $file_name) {
4143
+ $itext = empty($index) ? '' : $index;
4144
+ $known_size = isset($backup_history[$timestamp][$type.$itext.'-size']) ? $backup_history[$timestamp][$type.$itext.'-size'] : 0;
4145
+ $file = $files[$index];
4146
+ $fullpath = $iwp_backup_dir.'/'.$file;
4147
+ if (!file_exists($fullpath)) {
4148
+ $findex = $index;
4149
+ break;
4150
+ } elseif ($known_size > 0 && filesize($fullpath) < $known_size) {
4151
+ $findex = $index;
4152
+ break;
4153
+ }else{
4154
+ $file = '';
4155
+ }
4156
+ }
4157
+ }else{
4158
+ $file = $files;
4159
+ $fullpath = $iwp_backup_dir.'/'.$file;
4160
+ $findex = '';
4161
+ $itext = empty($findex) ? '' : $findex;
4162
+ $known_size = isset($backup_history[$timestamp][$type.$itext.'-size']) ? $backup_history[$timestamp][$type.$itext.'-size'] : 0;
4163
+ if (!file_exists($fullpath)) {
4164
+ $findex = $index;
4165
+ break;
4166
+ } elseif ($known_size > 0 && filesize($fullpath) < $known_size) {
4167
+ $findex = $index;
4168
+ break;
4169
+ }else{
4170
+ $file = '';
4171
+ }
4172
+
4173
+ }
4174
+ if (!empty($file)) {
4175
+ break;
4176
+ }
4177
+ }
4178
+ set_error_handler(array($iwp_backup_core, 'php_error'), E_ALL & ~E_STRICT);
4179
+
4180
+ $iwp_backup_core->log("Requested to obtain file: timestamp=$timestamp, type=$type, index=$findex");
4181
+
4182
+ $services = isset($backup_history[$timestamp]['service']) ? $backup_history[$timestamp]['service'] : false;
4183
+ if (!empty($backup_history[$timestamp]['service'][0]) && $backup_history[$timestamp]['service'][0] != 'none' && !empty($backup_history[$timestamp]['service_setting'])) {
4184
+ $service_setting = $backup_history[$timestamp]['service_setting'];
4185
+ $service = 'IWP_'.$backup_history[$timestamp]['service'][0];
4186
+ IWP_MMB_Backup_Options::update_iwp_backup_option($service, $service_setting);
4187
+ }
4188
+ if (is_string($services)) $services = array($services);
4189
+
4190
+ $iwp_backup_core->jobdata_set('service', $services);
4191
+
4192
+ }else{
4193
+ $tasks = $this->get_requested_task($timestamp);
4194
+ $tasks['taskResults'] = unserialize($tasks['taskResults']);
4195
+ $backup = $tasks['taskResults']['task_results'][$timestamp]; //darkCode testing purpose
4196
+ $hashValues = $backup['hashValues'];
4197
+ //$backup = $tasks['taskResults'];
4198
+ $requestParams = unserialize($tasks['requestParams']);
4199
+ $args = $requestParams['account_info'];
4200
+ $this->set_cloud_upload_setting($requestParams);
4201
+ if (isset($backup['ftp'])) {
4202
+ if (!empty($args['iwp_ftp']['use_sftp'])) {
4203
+ $services = array('sftp');
4204
+ $files = $backup['ftp'];
4205
+ }else{
4206
+ $services = array('ftp');
4207
+ $files = $backup['ftp'];
4208
+
4209
+ }
4210
+ $type = 'ftp';
4211
+ }elseif (isset($backup['amazons3'])) {
4212
+ $services = array('s3');
4213
+ $files = $backup['amazons3'];
4214
+ $type = 's3';
4215
+ }elseif (isset($backup['dropbox'])) {
4216
+ $services = array('dropbox');
4217
+ $files = $backup['dropbox'];
4218
+ $type = 'dropbox';
4219
+ }elseif (isset($backup['gDrive'])) {
4220
+ $services = array('googledrive');
4221
+ $files = $backup['gDriveOrgFileName'];
4222
+ $type = 'gDrive';
4223
+
4224
+ }elseif (isset($backup['server'])) {
4225
+ $files = $backup['file_path'];
4226
+ }
4227
+ $cloudInstance = $this->createCloudInstance($type, $args);
4228
+ if (is_array($files)) {
4229
+ foreach ($files as $index => $file_name) {
4230
+ $itext = empty($index) ? '' : $index;
4231
+ $backup_size = $this->getCloudBackupSize($type, $cloudInstance, $files[$index], $args);
4232
+ // $known_size = isset($backup['size']) ? $this->toBytes($backup['size']) : 0;
4233
+ $known_size = isset($backup_size) ? $backup_size : 0;
4234
+ $file = $files[$index];
4235
+ $fullpath = $iwp_backup_dir.'/'.$file;
4236
+ if (!file_exists($fullpath)) {
4237
+ $findex = $index;
4238
+ break;
4239
+ } elseif ($known_size > 0 && filesize($fullpath) < $known_size) {
4240
+ $findex = $index;
4241
+ break;
4242
+ }else{
4243
+ $file = '';
4244
+ }
4245
+ }
4246
+ }else {
4247
+ $file = $files;
4248
+ $fullpath = $iwp_backup_dir.'/'.$file;
4249
+ $findex = '';
4250
+ $itext = empty($findex) ? '' : $findex;
4251
+ $backup_size = $this->getCloudBackupSize($type, $cloudInstance, $file, $args);
4252
+ // $known_size = isset($backup['size']) ? $this->toBytes($backup['size']) : 0;
4253
+ $known_size = isset($backup_size) ? $backup_size : 0;
4254
+ if (!file_exists($fullpath)) {
4255
+ $findex = $index;
4256
+ } elseif ($known_size > 0 && filesize($fullpath) < $known_size) {
4257
+ $findex = $index;
4258
+ }else{
4259
+ $file = '';
4260
+ }
4261
+
4262
+ }
4263
+ set_error_handler(array($iwp_backup_core, 'php_error'), E_ALL & ~E_STRICT);
4264
+ $iwp_backup_core->log("Requested to obtain file: Old History ID=$timestamp, type=full, index=all");
4265
+ $iwp_backup_core->jobdata_set('service', $services);
4266
+
4267
+ }
4268
+ // TODO: FIXME: Failed downloads may leave log files forever (though they are small)
4269
+
4270
+
4271
+ // Fetch it from the cloud, if we have not already got it
4272
+
4273
+ $needs_downloading = false;
4274
+ if (!file_exists($fullpath)) {
4275
+ //if the file doesn't exist and they're using one of the cloud options, fetch it down from the cloud.
4276
+ $needs_downloading = true;
4277
+ $iwp_backup_core->log('File does not yet exist locally - needs downloading');
4278
+ } elseif ($known_size > 0 && filesize($fullpath)+10 < $known_size) {
4279
+ $iwp_backup_core->log("The file was found locally (".filesize($fullpath).") but did not match the size in the backup history ($known_size) - will resume downloading");
4280
+ $needs_downloading = true;
4281
+ } else{
4282
+ return array('success' => 'completed', 'already_closed' => $needs_downloading, 'backup_dir' => $iwp_backup_dir);
4283
+ }
4284
+
4285
+ // The AJAX responder that updates on progress wants to see this
4286
+ $iwp_backup_core->jobdata_set('dlfile_'.$timestamp.'_'.$type.'_'.$findex, "downloading:$known_size:$fullpath");
4287
+
4288
+ if ($needs_downloading) {
4289
+
4290
+ // Update the "last modified" time to dissuade any other instances from thinking that no downloaders are active
4291
+ @touch($fullpath);
4292
+
4293
+ $msg = array(
4294
+ 'result' => 'needs_download',
4295
+ 'request' => array(
4296
+ 'type' => $type,
4297
+ 'timestamp' => $timestamp,
4298
+ 'findex' => $findex
4299
+ )
4300
+ );
4301
+
4302
+ $this->get_remote_file($services, $file, $timestamp);
4303
+ }
4304
+
4305
+ // Now, be ready to spool the thing to the browser
4306
+ if (is_file($fullpath) && is_readable($fullpath)) {
4307
+
4308
+ // That message is then picked up by the AJAX listener
4309
+ $iwp_backup_core->jobdata_set('dlfile_'.$timestamp.'_'.$type.'_'.$findex, 'downloaded:'.filesize($fullpath).":$fullpath");
4310
+
4311
+ $result = 'downloaded';
4312
+
4313
+ } else {
4314
+
4315
+ $iwp_backup_core->jobdata_set('dlfile_'.$timestamp.'_'.$type.'_'.$findex, 'failed');
4316
+ $iwp_backup_core->jobdata_set('dlerrors_'.$timestamp.'_'.$type.'_'.$findex, $iwp_backup_core->errors);
4317
+ $iwp_backup_core->log('Remote fetch failed. File '.$fullpath.' did not exist or was unreadable. If you delete local backups then remote retrieval may have failed.');
4318
+
4319
+ $result = 'download_failed';
4320
+ }
4321
+
4322
+ restore_error_handler();
4323
+
4324
+ @fclose($iwp_backup_core->logfile_handle);
4325
+ if (!$debug_mode) @unlink($iwp_backup_core->logfile_name);
4326
+
4327
+ // The browser connection was possibly already closed, but not necessarily
4328
+ return array('success' => $result, 'already_closed' => $needs_downloading);
4329
+
4330
+ }
4331
+
4332
+ public function createCloudInstance($type, $args){
4333
+ if ($type == 'dropbox') {
4334
+ extract($args['iwp_dropbox']);
4335
+ require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/API.php';
4336
+ require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/Exception.php';
4337
+ require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/OAuth/Consumer/ConsumerAbstract.php';
4338
+ require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/OAuth/Consumer/Curl.php';
4339
+
4340
+ $oauth = new IWP_Dropbox_OAuth_Consumer_Curl($dropbox_app_key, $dropbox_app_secure_key);
4341
+ $oauth->setToken($dropbox_access_token);
4342
+ $dropbox = new IWP_Dropbox_API($oauth);
4343
+ return $dropbox;
4344
+ }elseif ($type == 'ftp') {
4345
+ extract($args['iwp_ftp']);
4346
+ if(isset($use_sftp) && $use_sftp==1) {
4347
+ $port = $ftp_port ? $ftp_port : 22; //default port is 22
4348
+ /*
4349
+ * SFTP section start here phpseclib library is used for this functionality
4350
+ */
4351
+ $path = $GLOBALS['iwp_mmb_plugin_dir'].'/lib/phpseclib/phpseclib/phpseclib';
4352
+ set_include_path(get_include_path() . PATH_SEPARATOR . $path);
4353
+ include_once('Net/SFTP.php');
4354
+
4355
+
4356
+ $sftp = new Net_SFTP($ftp_hostname, $port);
4357
+ if(!$sftp) {
4358
+ return false;
4359
+ }
4360
+ if (!$sftp->login($ftp_username, $ftp_password)) {
4361
+ return false;
4362
+ } else {
4363
+ return $sftp;
4364
+ }
4365
+
4366
+ }
4367
+ $port = $ftp_port ? $ftp_port : 21; //default port is 21
4368
+ if (!empty($ftp_ssl)) {
4369
+ if (function_exists('ftp_ssl_connect')) {
4370
+ $conn_id = ftp_ssl_connect($ftp_hostname,$port);
4371
+ if ($conn_id === false) {
4372
+ return false;
4373
+ }
4374
+ } else {
4375
+ return false;
4376
+ }
4377
+ }
4378
+ else {
4379
+ if (function_exists('ftp_connect')) {
4380
+ $conn_id = ftp_connect($ftp_hostname,$port);
4381
+ if ($conn_id === false) {
4382
+ return false;
4383
+ }
4384
+ } else {
4385
+ return false;
4386
+ }
4387
+ }
4388
+
4389
+ $login = @ftp_login($conn_id, $ftp_username, $ftp_password);
4390
+ if ($login === false) {
4391
+ return false;
4392
+ }
4393
+
4394
+ if(!empty($ftp_passive)){
4395
+ @ftp_pasv($conn_id,true);
4396
+ }
4397
+ return $conn_id;
4398
+ }elseif ($type == 'gDrive') {
4399
+ require_once $GLOBALS['iwp_mmb_plugin_dir'].'/backup/googledrive.php';
4400
+ $obj = new IWP_MMB_UploadModule_googledrive();
4401
+ return $obj;
4402
+ }
4403
+ }
4404
+
4405
+ public function getCloudBackupSize($type, &$obj, $backup_file, $args){
4406
+ require_once($GLOBALS['iwp_mmb_plugin_dir']."/backup.class.multicall.php");
4407
+ $backup_instance = new IWP_MMB_Backup_Multicall();
4408
+ if ($type == 'dropbox') {
4409
+ extract($args['iwp_dropbox']);
4410
+ $oldRoot = 'Apps/InfiniteWP/';
4411
+ $dropbox_destination = $oldRoot.ltrim(trim($dropbox_destination), '/');
4412
+ $dropbox_destination = rtrim($dropbox_destination, '/');
4413
+ if (isset($dropbox_site_folder) && $dropbox_site_folder == true){
4414
+ $dropbox_destination .= '/'.$backup_instance->site_name;
4415
+ }
4416
+ $folders = explode('/',$dropbox_destination);
4417
+ foreach ($folders as $key => $name) {
4418
+ $path.=trim($name).'/';
4419
+ }
4420
+ $destFile = trim($path, '/').'/';
4421
+ $filename = basename($backup_file);
4422
+ $destFile .= $filename;
4423
+ $dBoxMetaData = $obj -> metaData($destFile);
4424
+ if (empty($dBoxMetaData['body']->size)) {
4425
+ return false;
4426
+ }else{
4427
+ return $dBoxMetaData['body']->size;
4428
+ }
4429
+
4430
+ }elseif ($type == 's3') {
4431
+ extract($args['iwp_amazon_s3']);
4432
+ if (isset($as3_site_folder) && $as3_site_folder == true){
4433
+ $destination .= '/'.$backup_instance->site_name;
4434
+ }
4435
+ $folders = explode('/',$destination);
4436
+ foreach ($folders as $key => $name) {
4437
+ $path.=trim($name).'/';
4438
+ }
4439
+ $destFile = trim($path, '/').'/';
4440
+ $filename = basename($backup_file);
4441
+ $destFile .= $filename;
4442
+ if(1 || is_new_s3_compatible()){
4443
+ require_once $GLOBALS['iwp_mmb_plugin_dir'].'/lib/amazon/s3IWPBackup.php';
4444
+ if(!class_exists('S3Client')){
4445
+ require_once($GLOBALS['iwp_mmb_plugin_dir'].'/lib/amazon/autoload.php');
4446
+ }
4447
+ $new_s3_obj = new IWP_MMB_S3_MULTICALL();
4448
+ return $new_s3_obj->postUploadS3Verification($backup_file, $destFile, $type, $as3_bucket, $as3_access_key, $as3_secure_key, $as3_bucket_region, $size1, $size2, $return_size = true);
4449
+ }
4450
+ else{
4451
+ return $backup_instance->postUploadS3VerificationBwdComp($backup_file, $destFile, $type, $as3_bucket, $as3_access_key, $as3_secure_key, $as3_bucket_region, $obj, $actual_file_size, $size1, $size2, $return_size = true);
4452
+ }
4453
+ }elseif ($type == 'ftp') {
4454
+ extract($args['iwp_ftp']);
4455
+
4456
+ $destination = trim($ftp_remote_folder, '/');
4457
+ if (isset($ftp_site_folder) && $ftp_site_folder == true){
4458
+ $destination .= '/'.$backup_instance->site_name;
4459
+ }
4460
+ $folders = explode('/',$destination);
4461
+ foreach ($folders as $key => $name) {
4462
+ $path.=trim($name).'/';
4463
+ }
4464
+ $destFile = trim($path, '/').'/';
4465
+ $filename = basename($backup_file);
4466
+ $destFile .= $filename;
4467
+ if(isset($use_sftp) && $use_sftp==1) {
4468
+ $destFile = '/'.$destFile;
4469
+ $ftp_file_size = $obj->size($destFile);
4470
+ }else{
4471
+ ftp_chdir ($obj , dirname($destFile));
4472
+ $ftp_file_size = ftp_size($obj, basename($destFile));
4473
+ }
4474
+ if($ftp_file_size > 0)
4475
+ {
4476
+ return $ftp_file_size;
4477
+ }
4478
+ else
4479
+ {
4480
+ return false;
4481
+ }
4482
+ }elseif ($type == 'gDrive') {
4483
+ $return = $obj->get_backup_file_size($backup_file);
4484
+ if ($return >0 ) {
4485
+ return $return;
4486
+ }
4487
+ }
4488
+ }
4489
+
4490
+ public function get_requested_task($ID){
4491
+ global $wpdb;
4492
+ $table_name = $wpdb->base_prefix . "iwp_backup_status";
4493
+
4494
+ $rows = $wpdb->get_row($wpdb->prepare("SELECT * FROM ".$table_name." WHERE historyID = %d ORDER BY ID DESC LIMIT 1", $ID), ARRAY_A);
4495
+
4496
+ return $rows;
4497
+
4498
+ }
4499
+
4500
+ public function toBytes($str){
4501
+ $val = str_replace(array(' MB', ' KB'),'', $str);
4502
+ $last = strtolower($str[strlen($str)-2]);
4503
+ switch($last) {
4504
+ case 'g': $val *= 1024;
4505
+ case 'm': $val *= 1024;
4506
+ case 'k': $val *= 1024;
4507
+ }
4508
+ return $val;
4509
+ }
4510
+
4511
+ public function createBackupMetaFile($our_files){
4512
+ $site_name = str_replace(array(
4513
+ "_",
4514
+ "/",
4515
+ "~"
4516
+ ), array(
4517
+ "",
4518
+ "-",
4519
+ "-"
4520
+ ), rtrim(remove_http(get_bloginfo('url')), "/"));
4521
+ $backup_file_basename = $site_name.'_backup_'.get_date_from_gmt(gmdate('Y-m-d H:i:s', $this->backup_time), 'Y-m-d-Hi').'_'.$this->blog_name.'_'.$this->nonce.'_backup_meta_'.$this->get_wordpress_version().'.tmp';
4522
+ $our_files['wp_content_url'] = content_url();
4523
+ $our_files['wp_content_path'] = WP_CONTENT_DIR;
4524
+ $our_files['backup_meta_file'] = $backup_file_basename;
4525
+ $our_files['old_file_path'] = ABSPATH;
4526
+ $our_files['old_url'] = get_option('siteurl');
4527
+ $backup_dir = $this->backups_dir_location();
4528
+ $backup_meta_file = $backup_dir.'/'.$backup_file_basename;
4529
+ $meta_file_handle = fopen($backup_meta_file, 'w');
4530
+ if ($meta_file_handle == false) {
4531
+ return false;
4532
+ }
4533
+ @fwrite($meta_file_handle, "<?php"."\n".'$backup_meta_files ='."'".serialize($our_files)."';\n");
4534
+ fclose($meta_file_handle);
4535
+ return $backup_file_basename;
4536
+ }
4537
+
4538
+ public function delete_backup($opts) {
4539
+
4540
+ $backups = $this->get_backup_history();
4541
+ $timestamps = (string)$opts['result_id'];
4542
+
4543
+ $remote_delete_limit = (isset($opts['remote_delete_limit']) && $opts['remote_delete_limit'] > 0) ? (int)$opts['remote_delete_limit'] : PHP_INT_MAX;
4544
+
4545
+ $timestamps = explode(',', $timestamps);
4546
+ $delete_remote = empty($opts['delete_remote']) ? true : true;
4547
+
4548
+ // You need a nonce before you can set job data. And we certainly don't yet have one.
4549
+ // $this->backup_time_nonce();
4550
+ // // Set the job type before logging, as there can be different logging destinations
4551
+ // $this->jobdata_set('job_type', 'delete');
4552
+ // $this->jobdata_set('job_time_ms', $this->job_time_ms);
4553
+
4554
+ if (IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_debug_mode')) {
4555
+ $this->logfile_open($this->nonce);
4556
+ set_error_handler(array($this, 'php_error'), E_ALL & ~E_STRICT);
4557
+ }
4558
+
4559
+ $iwp_backup_dir = $this->backups_dir_location();
4560
+ $backupable_entities = $this->get_backupable_file_entities(true, true);
4561
+
4562
+ $local_deleted = 0;
4563
+ $remote_deleted = 0;
4564
+ $sets_removed = 0;
4565
+ foreach ($timestamps as $i => $timestamp) {
4566
+
4567
+ if (!isset($backups[$timestamp])) {
4568
+ return array('result' => 'error', 'message' => __('Backup set not found', 'InfiniteWP'));
4569
+ }
4570
+
4571
+ $nonce = isset($backups[$timestamp]['nonce']) ? $backups[$timestamp]['nonce'] : '';
4572
+
4573
+ $delete_from_service = array();
4574
+
4575
+ if ($delete_remote) {
4576
+ // Locate backup set
4577
+ if (isset($backups[$timestamp]['service'])) {
4578
+ // Convert to an array so that there is no uncertainty about how to process it
4579
+ $services = is_string($backups[$timestamp]['service']) ? array($backups[$timestamp]['service']) : $backups[$timestamp]['service'];
4580
+ if (is_array($services)) {
4581
+ foreach ($services as $service) {
4582
+ if ($service && $service != 'none' && $service != 'email') $delete_from_service[] = $service;
4583
+ }
4584
+ }
4585
+ }
4586
+ }
4587
+
4588
+ $files_to_delete = array();
4589
+ foreach ($backupable_entities as $key => $ent) {
4590
+ if (isset($backups[$timestamp][$key])) {
4591
+ $files_to_delete[$key] = $backups[$timestamp][$key];
4592
+ }
4593
+ }
4594
+ // Delete DB
4595
+ foreach ($backups[$timestamp] as $key => $value){
4596
+ if ('db' == strtolower(substr($key, 0, 2)) && '-size' != substr($key, -5, 5)) {
4597
+ $files_to_delete[$key] = $backups[$timestamp][$key];
4598
+ }
4599
+ }
4600
+
4601
+ // Also delete the log
4602
+ if ($nonce && !IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_debug_mode')) {
4603
+ $files_to_delete['log'] = "log.$nonce.txt";
4604
+ }
4605
+ if (!empty($backups[$timestamp]['backup_file_basename'])) {
4606
+ $files_to_delete['backup_file_basename'] = $backups[$timestamp]['backup_file_basename'];
4607
+ }
4608
+ $this->register_wp_http_option_hooks();
4609
+
4610
+ foreach ($files_to_delete as $key => $files) {
4611
+
4612
+ if (is_string($files)) {
4613
+ $was_string = true;
4614
+ $files = array($files);
4615
+ } else {
4616
+ $was_string = false;
4617
+ }
4618
+
4619
+ foreach ($files as $file) {
4620
+ if (is_file($iwp_backup_dir.'/'.$file) && @unlink($iwp_backup_dir.'/'.$file)) $local_deleted++;
4621
+ }
4622
+
4623
+ if ('log' != $key && count($delete_from_service) > 0) {
4624
+
4625
+ $storage_objects_and_ids = $this->get_storage_objects_and_ids($delete_from_service);
4626
+
4627
+ foreach ($delete_from_service as $service) {
4628
+
4629
+ if ('email' == $service || 'none' == $service || !$service) continue;
4630
+
4631
+ $deleted = -1;
4632
+
4633
+ $remote_obj = $storage_objects_and_ids[$service]['object'];
4634
+
4635
+ $instance_settings = $storage_objects_and_ids[$service]['instance_settings'];
4636
+ $this->backups_instance_ids = empty($backups[$timestamp]['service_instance_ids'][$service]) ? array() : $backups[$timestamp]['service_instance_ids'][$service];
4637
+
4638
+ uksort($instance_settings, array($this, 'instance_ids_sort'));
4639
+
4640
+ foreach ($instance_settings as $instance_id => $options) {
4641
+
4642
+ $remote_obj->set_options($options, false, $instance_id);
4643
+
4644
+ foreach ($files as $index => $file) {
4645
+ if ($remote_deleted == $remote_delete_limit) {
4646
+ return $this->remove_backup_set_cleanup(false, $backups, $local_deleted, $remote_deleted, $sets_removed);
4647
+ }
4648
+
4649
+ $deleted = $remote_obj->delete($file);
4650
+
4651
+ if (-1 === $deleted) {
4652
+ //echo __('Did not know how to delete from this cloud service.', 'updraftplus');
4653
+ } elseif (false !== $deleted) {
4654
+ $remote_deleted++;
4655
+ }
4656
+
4657
+ $itext = $index ? (string)$index : '';
4658
+ if ($was_string) {
4659
+ unset($backups[$timestamp][$key]);
4660
+ if ('db' == strtolower(substr($key, 0, 2))) unset($backups[$timestamp][$key][$index.'-size']);
4661
+ } else {
4662
+ unset($backups[$timestamp][$key][$index]);
4663
+ unset($backups[$timestamp][$key.$itext.'-size']);
4664
+ if (empty($backups[$timestamp][$key])) unset($backups[$timestamp][$key]);
4665
+ }
4666
+ if (isset($backups[$timestamp]['checksums']) && is_array($backups[$timestamp]['checksums'])) {
4667
+ foreach (array_keys($backups[$timestamp]['checksums']) as $algo) {
4668
+ unset($backups[$timestamp]['checksums'][$algo][$key.$index]);
4669
+ }
4670
+ }
4671
+
4672
+ // If we don't save the array back, then the above section will fire again for the same files - and the remote storage will be requested to delete already-deleted files, which then means no time is actually saved by the browser-backend loop method.
4673
+ $this->save_history($backups);
4674
+ }
4675
+ }
4676
+ }
4677
+ }
4678
+ }
4679
+
4680
+ unset($backups[$timestamp]);
4681
+ $this->save_history($backups);
4682
+ $sets_removed++;
4683
+ }
4684
+
4685
+ return $this->remove_backup_set_cleanup(true, $backups, $local_deleted, $remote_deleted, $sets_removed);
4686
+
4687
+ }
4688
+
4689
+ public function remove_backup_set_cleanup($delete_complete, $backups, $local_deleted, $remote_deleted, $sets_removed) {
4690
+
4691
+ $this->register_wp_http_option_hooks(false);
4692
+
4693
+ $this->save_history($backups);
4694
+
4695
+ $this->log("Local files deleted: $local_deleted. Remote files deleted: $remote_deleted");
4696
+
4697
+ if ($delete_complete) {
4698
+ $set_message = __('Backup sets removed:', 'InfiniteWP');
4699
+ $local_message = __('Local files deleted:', 'InfiniteWP');
4700
+ $remote_message = __('Remote files deleted:', 'InfiniteWP');
4701
+
4702
+ if (IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_debug_mode')) {
4703
+ restore_error_handler();
4704
+ }
4705
+
4706
+ return array('result' => 'success', 'set_message' => $set_message, 'local_message' => $local_message, 'remote_message' => $remote_message, 'backup_sets' => $sets_removed, 'backup_local' => $local_deleted, 'backup_remote' => $remote_deleted);
4707
+ } else {
4708
+
4709
+ return array('result' => 'continue', 'backup_local' => $local_deleted, 'backup_remote' => $remote_deleted, 'backup_sets' => $sets_removed);
4710
+ }
4711
+ }
4712
+
4713
+ public function save_history($backup_history, $use_cache = true) {
4714
+ IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_backup_history', $backup_history, $use_cache);
4715
+ }
4716
+
4717
+ public function instance_ids_sort($a, $b) {
4718
+ if (in_array($a, $this->backups_instance_ids)) {
4719
+ if (in_array($b, $this->backups_instance_ids)) return 0;
4720
+ return -1;
4721
+ }
4722
+ return in_array($b, $this->backups_instance_ids) ? 1 : 0;
4723
+ }
4724
+
4725
+ public function activejobs_delete($job_id) {
4726
+
4727
+ if (preg_match("/^[0-9a-f]{12}$/", $job_id)) {
4728
+
4729
+ $cron = get_option('cron');
4730
+ $found_it = false;
4731
+
4732
+ foreach ($cron as $time => $job) {
4733
+ if (isset($job['IWP_backup_resume'])) {
4734
+ foreach ($job['IWP_backup_resume'] as $hook => $info) {
4735
+ if (isset($info['args'][1]) && $info['args'][1] == $job_id) {
4736
+ $args = $cron[$time]['IWP_backup_resume'][$hook]['args'];
4737
+ wp_unschedule_event($time, 'IWP_backup_resume', $args);
4738
+ if (!$found_it) return array('ok' => 'Y', 'c' => 'deleted', 'm' => __('Job deleted', 'InfiniteWP'));
4739
+ $found_it = true;
4740
+ }
4741
+ }
4742
+ }
4743
+ }
4744
+ }
4745
+
4746
+ if (!$found_it) return true;
4747
+
4748
+ }
4749
+
4750
+ public function kill_new_backup($params){
4751
+ $this->activejobs_delete($params['result_id']);
4752
+ $backups = $this->get_backup_history();
4753
+ delete_option('IWP_jobdata_'.$params['result_id']);
4754
+ delete_option('IWP_backup_status', '0');
4755
+ delete_option('IWP_semaphore_fd');
4756
+ delete_option('IWP_locked_fd');
4757
+ delete_option('IWP_unlocked_fd');
4758
+ delete_option('IWP_semaphore_d');
4759
+ delete_option('IWP_unlocked_d');
4760
+ delete_option('IWP_locked_d');
4761
+ wp_clear_scheduled_hook('IWP_backup_resume');
4762
+ if (!empty($backups)) {
4763
+ foreach ($backups as $key => $value) {
4764
+ if ($value['nonce'] == $params['result_id']) {
4765
+ $params['result_id'] = $key;
4766
+ }
4767
+ }
4768
+ return $this->delete_backup($params);
4769
+ }
4770
+
4771
+ return true;
4772
+ }
4773
+ public function dropbox_modpath($file, $obj){
4774
+ $opts = $obj->get_options();
4775
+ $dropbox_site_folder = $opts['dropbox_site_folder'];
4776
+ $dropbox_destination = $opts['folder'];
4777
+ $path= '';
4778
+ if (isset($dropbox_site_folder) && $dropbox_site_folder == true){
4779
+ $site_name = iwp_getSiteName();
4780
+ $dropbox_destination .= '/' . $site_name . '/';
4781
+ }
4782
+ else{
4783
+ $dropbox_destination .= '/';
4784
+ }
4785
+ $oldRoot = 'Apps/InfiniteWP/';
4786
+ $dropbox_destination = $oldRoot.ltrim(trim($dropbox_destination), '/');
4787
+ $dropbox_destination = rtrim($dropbox_destination, '/');
4788
+ $folders = explode('/',$dropbox_destination);
4789
+ foreach ($folders as $key => $name) {
4790
+ $path.=trim($name).'/';
4791
+ }
4792
+ $dropbox_destination = $path;
4793
+ $dropbox_folder = untrailingslashit($dropbox_destination);
4794
+ if (strpos($file, $dropbox_folder) === false) {
4795
+ $dropbox_folder.= '/'.$file;
4796
+ }else{
4797
+ $dropbox_folder = $file;
4798
+ }
4799
+ return $dropbox_folder;
4800
+ }
4801
+
4802
+ public function get_timestamp_by_label($label){
4803
+ $new_backup_keys = array();
4804
+ $new_backups = $this->get_backup_history();
4805
+ if (!empty($new_backups)) {
4806
+ foreach ($new_backups as $timestamp => $value) {
4807
+ if ($label == $value['label']) {
4808
+ $new_backup_keys[$timestamp] = $value;
4809
+ }
4810
+ }
4811
+ ksort($new_backup_keys);
4812
+ }
4813
+
4814
+ return $new_backup_keys;
4815
+ }
4816
+
4817
+ public function set_cloud_upload_setting($params){
4818
+ if (!empty($params['account_info'])) {
4819
+ if (!empty($params['account_info']['iwp_ftp'])) {
4820
+ $ftp_details = $params['account_info']['iwp_ftp'];
4821
+ $opts = array(
4822
+ 'user' => $ftp_details['ftp_username'],
4823
+ 'pass' => $ftp_details['ftp_password'],
4824
+ 'host' => $ftp_details['ftp_hostname'],
4825
+ 'path' => $ftp_details['ftp_remote_folder'],
4826
+ 'ftp_site_folder' => $ftp_details['ftp_site_folder'],
4827
+ 'passive' => $ftp_details['ftp_passive']?true:false
4828
+ );
4829
+ if ($ftp_details['use_sftp']) {
4830
+ IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_sftp', $opts);
4831
+ }else{
4832
+ IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_ftp', $opts);
4833
+ }
4834
+ }elseif (!empty($params['account_info']['iwp_amazon_s3'])) {
4835
+ $s3_details = $params['account_info']['iwp_amazon_s3'];
4836
+ if (!empty($s3_details['as3_directory'])) {
4837
+ $path = trim($s3_details['as3_bucket'],'/').'/'.trim($s3_details['as3_directory'],'/');
4838
+ }else{
4839
+ $path = $s3_details['as3_bucket'];
4840
+ }
4841
+ $opts = array(
4842
+ 'endpoint' => '',
4843
+ 'accesskey' => $s3_details['as3_access_key'],
4844
+ 'secretkey' => $s3_details['as3_secure_key'],
4845
+ 'path' => $path,
4846
+ 'as3_site_folder' => $s3_details['as3_site_folder'],
4847
+ 'server_side_encryption' => $s3_details['server_side_encryption']?true:false
4848
+ );
4849
+ IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_s3', $opts);
4850
+
4851
+ }elseif (!empty($params['account_info']['iwp_dropbox'])) {
4852
+ $dropbox_details = $params['account_info']['iwp_dropbox'];
4853
+
4854
+ $opts = array(
4855
+ 'appkey' => $dropbox_details['dropbox_app_key'],
4856
+ 'secret' => $dropbox_details['dropbox_app_secure_key'],
4857
+ 'tk_access_token' => $dropbox_details['dropbox_access_token'],
4858
+ 'folder' => $dropbox_details['dropbox_destination'],
4859
+ 'ownername' => '',
4860
+ 'CSRF' => '',
4861
+ 'dropbox_site_folder' => $dropbox_details['dropbox_site_folder']
4862
+ );
4863
+ IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_dropbox', $opts);
4864
+ }elseif (!empty($params['account_info']['iwp_gdrive'])) {
4865
+ $google_details = $params['account_info']['iwp_gdrive'];
4866
+ $opts = array(
4867
+ 'clientid' => $google_details['clientID'],
4868
+ 'secret' => $google_details['clientSecretKey'],
4869
+ 'token' => $google_details['token']['refresh_token'],
4870
+ 'tmp_access_token' => $google_details['token']['access_token'],
4871
+ 'gdrive_site_folder' => $google_details['gdrive_site_folder'],
4872
+ 'ownername' => ''
4873
+ );
4874
+ IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_googledrive', $opts);
4875
+ }
4876
+ }
4877
+ }
4878
+ }
backup/backup.options.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // Options handling
4
+ if ( ! defined('ABSPATH') )
5
+ die();
6
+
7
+ class IWP_MMB_Backup_Options {
8
+
9
+ public static function get_iwp_backup_option($option, $default = null) {
10
+ $ret = get_option($option, $default);
11
+ return apply_filters('IWP_get_option', $ret, $option, $default);
12
+ }
13
+
14
+ // The apparently unused parameter is used in the alternative class in the Multisite add-on
15
+ public static function update_iwp_backup_option($option, $value, $use_cache = true) {
16
+ return update_option($option, apply_filters('IWP_update_option', $value, $option, $use_cache));
17
+ }
18
+
19
+ public static function delete_iwp_backup_option($option) {
20
+ delete_option($option);
21
+ }
22
+
23
+ public static function admin_page_url() {
24
+ return admin_url('options-general.php');
25
+ }
26
+ }
backup/backup.php ADDED
@@ -0,0 +1,2935 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if ( ! defined('ABSPATH') )
4
+ die();
5
+
6
+ if (!class_exists('IWP_MMB_PclZip')) {
7
+ require_once($GLOBALS['iwp_mmb_plugin_dir']."/backup/backup.zip.class.php");
8
+ require_once($GLOBALS['iwp_mmb_plugin_dir']."/backup/functions.php");
9
+
10
+ }
11
+
12
+ // This file contains functions that are only needed/loaded when a backup is running (reduces memory usage on other pages)
13
+
14
+ class IWP_MMB_Backup {
15
+
16
+ public $index = 0;
17
+
18
+ private $zipfiles_added;
19
+ private $zipfiles_added_thisrun = 0;
20
+ public $zipfiles_dirbatched;
21
+ public $zipfiles_batched;
22
+ public $zipfiles_skipped_notaltered;
23
+ private $zip_split_every = 419430400; # 400MB
24
+ private $zip_last_ratio = 1;
25
+ private $whichone;
26
+ private $zip_basename = '';
27
+ private $backup_basename = '';
28
+ private $zipfiles_lastwritetime;
29
+ // 0 = unknown; false = failed
30
+ public $binzip = 0;
31
+
32
+ private $dbhandle;
33
+ private $dbhandle_isgz;
34
+
35
+ # Array of entities => times
36
+ private $altered_since = -1;
37
+ # Time for the current entity
38
+ private $makezip_if_altered_since = -1;
39
+
40
+ private $excluded_extensions = false;
41
+
42
+ private $use_zip_object = 'IWP_MMB_ZipArchive';
43
+ public $debug = false;
44
+
45
+ public $iwp_backup_dir;
46
+ private $blog_name;
47
+ private $wpdb_obj;
48
+ private $job_file_entities = array();
49
+
50
+ private $first_run = 0;
51
+
52
+ // Record of zip files created
53
+ private $backup_files_array = array();
54
+
55
+ // Used for reporting
56
+ private $remotestorage_extrainfo = array();
57
+
58
+ // Used when deciding to use the 'store' or 'deflate' zip storage method
59
+ private $extensions_to_not_compress = array();
60
+
61
+ // Append to this any skipped tables
62
+ private $skipped_tables;
63
+
64
+ public function __construct($backup_files, $altered_since = -1) {
65
+
66
+ global $iwp_backup_core;
67
+
68
+ // Get the blog name and rip out known-problematic characters. Remember that we may need to be able to upload this to any FTP server or cloud storage, where filename support may be unknown
69
+ $blog_name = str_replace('__', '_', preg_replace('/[^A-Za-z0-9_]/','', str_replace(' ','_', substr(get_bloginfo(), 0, 32))));
70
+ if (!$blog_name || preg_match('#^_+$#', $blog_name)) {
71
+ // Try again...
72
+ $parsed_url = parse_url(home_url(), PHP_URL_HOST);
73
+ $parsed_subdir = untrailingslashit(parse_url(home_url(), PHP_URL_PATH));
74
+ if ($parsed_subdir && '/' != $parsed_subdir) $parsed_url .= str_replace(array('/', '\\'), '_', $parsed_subdir);
75
+ $blog_name = str_replace('__', '_', preg_replace('/[^A-Za-z0-9_]/','', str_replace(' ','_', substr($parsed_url, 0, 32))));
76
+ if (!$blog_name || preg_match('#^_+$#', $blog_name)) $blog_name = 'WordPress_Backup';
77
+ }
78
+
79
+ $this->debug = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_debug_mode');
80
+ $this->iwp_backup_dir = $iwp_backup_core->backups_dir_location();
81
+ $this->iwp_backup_dir_realpath = realpath($this->iwp_backup_dir);
82
+ $iwp_backup_core->blog_name = $blog_name;
83
+ if ('no' === $backup_files) {
84
+ $this->use_zip_object = 'IWP_MMB_PclZip';
85
+ return;
86
+ }
87
+
88
+ $this->extensions_to_not_compress = array_unique(array_map('strtolower', array_map('trim', explode(',', IWP_ZIP_NOCOMPRESS))));
89
+
90
+ $this->altered_since = $altered_since;
91
+
92
+ // false means 'tried + failed'; whereas 0 means 'not yet tried'
93
+ // Disallow binzip on OpenVZ when we're not sure there's plenty of memory
94
+ if ($this->binzip === 0 && (!defined('IWP_PREFERPCLZIP') || IWP_PREFERPCLZIP != true) && (!defined('IWP_NO_BINZIP') || !IWP_NO_BINZIP) && $iwp_backup_core->current_resumption <9) {
95
+
96
+ if (@file_exists('/proc/user_beancounters') && @file_exists('/proc/meminfo') && @is_readable('/proc/meminfo')) {
97
+ $meminfo = @file_get_contents('/proc/meminfo', false, null, -1, 200);
98
+ if (is_string($meminfo) && preg_match('/MemTotal:\s+(\d+) kB/', $meminfo, $matches)) {
99
+ $memory_mb = $matches[1]/1024;
100
+ # If the report is of a large amount, then we're probably getting the total memory on the hypervisor (this has been observed), and don't really know the VPS's memory
101
+ $vz_log = "OpenVZ; reported memory: ".round($memory_mb, 1)." MB";
102
+ if ($memory_mb < 1024 || $memory_mb > 8192) {
103
+ $openvz_lowmem = true;
104
+ $vz_log .= " (will not use BinZip)";
105
+ }
106
+ $iwp_backup_core->log($vz_log);
107
+ }
108
+ }
109
+ if (empty($openvz_lowmem)) {
110
+ $iwp_backup_core->log('Checking if we have a zip executable available');
111
+ $binzip = $iwp_backup_core->find_working_bin_zip();
112
+ if (is_string($binzip)) {
113
+ $iwp_backup_core->log("Zip engine: found/will use a binary zip: $binzip");
114
+ $this->binzip = $binzip;
115
+ $this->use_zip_object = 'IWP_MMB_BinZip';
116
+ }
117
+ }
118
+ }
119
+
120
+ # In tests, PclZip was found to be 25% slower than ZipArchive
121
+ if ($this->use_zip_object != 'IWP_MMB_PclZip' && empty($this->binzip) && ((defined('IWP_PREFERPCLZIP') && IWP_PREFERPCLZIP == true) || !class_exists('ZipArchive') || !class_exists('IWP_MMB_ZipArchive') || (!extension_loaded('zip') && !method_exists('ZipArchive', 'AddFile')))) {
122
+ global $iwp_backup_core;
123
+ $iwp_backup_core->log("Zip engine: ZipArchive is not available or is disabled (will use PclZip if needed)");
124
+ $this->use_zip_object = 'IWP_MMB_PclZip';
125
+ }
126
+
127
+ }
128
+
129
+ public function report_remotestorage_extrainfo($service, $info_html, $info_plain) {
130
+ $this->remotestorage_extrainfo[$service] = array('pretty' => $info_html, 'plain' => $info_plain);
131
+ }
132
+
133
+ // Public, because called from the 'More Files' add-on
134
+ public function create_zip($create_from_dir, $whichone, $backup_file_basename, $index, $first_linked_index = false) {
135
+ // Note: $create_from_dir can be an array or a string
136
+ @set_time_limit(IWP_SET_TIME_LIMIT);
137
+ $original_index = $index;
138
+ $this->index = $index;
139
+ $this->first_linked_index = (false === $first_linked_index) ? 0 : $first_linked_index;
140
+
141
+ $this->whichone = $whichone;
142
+
143
+ global $iwp_backup_core;
144
+
145
+ $this->zip_split_every = max((int)$iwp_backup_core->jobdata_get('split_every'), IWP_SPLIT_MIN)*1048576;
146
+
147
+ if ('others' != $whichone) $iwp_backup_core->log("Beginning creation of dump of $whichone (split every: ".round($this->zip_split_every/1048576,1)." MB)");
148
+
149
+ if (is_string($create_from_dir) && !file_exists($create_from_dir)) {
150
+ $flag_error = true;
151
+ $iwp_backup_core->log("Does not exist: $create_from_dir");
152
+ if ('mu-plugins' == $whichone) {
153
+ if (!function_exists('get_mu_plugins')) require_once(ABSPATH.'wp-admin/includes/plugin.php');
154
+ $mu_plugins = get_mu_plugins();
155
+ if (count($mu_plugins) == 0) {
156
+ $iwp_backup_core->log("There appear to be no mu-plugins to back up. Will not raise an error.");
157
+ $flag_error = false;
158
+ }
159
+ }
160
+ if ($flag_error) $iwp_backup_core->log(sprintf(__("%s - could not back this entity up; the corresponding directory does not exist (%s)", 'iwp_backup_core'), $whichone, $create_from_dir), 'error');
161
+ return false;
162
+ }
163
+
164
+ $itext = (empty($index)) ? '' : ($index+1);
165
+ $base_path = $backup_file_basename.'-'.$whichone.$itext.'.zip';
166
+ $full_path = $this->iwp_backup_dir.'/'.$base_path;
167
+ $time_now = time();
168
+
169
+ # This is compatible with filenames which indicate increments, as it is looking only for the current increment
170
+ if (file_exists($full_path)) {
171
+ # Gather any further files that may also exist
172
+ $files_existing = array();
173
+ while (file_exists($full_path)) {
174
+ $files_existing[] = $base_path;
175
+ $time_mod = (int)@filemtime($full_path);
176
+ $iwp_backup_core->log($base_path.": this file has already been created (age: ".round($time_now-$time_mod,1)." s)");
177
+ if ($time_mod>100 && ($time_now-$time_mod)<30) {
178
+ $iwp_backup_core->terminate_due_to_activity($base_path, $time_now, $time_mod);
179
+ }
180
+ $index++;
181
+ # This is compatible with filenames which indicate increments, as it is looking only for the current increment
182
+ $base_path = $backup_file_basename.'-'.$whichone.($index+1).'.zip';
183
+ $full_path = $this->iwp_backup_dir.'/'.$base_path;
184
+ }
185
+ }
186
+
187
+ // Temporary file, to be able to detect actual completion (upon which, it is renamed)
188
+
189
+ // New (Jun-13) - be more aggressive in removing temporary files from earlier attempts - anything >=600 seconds old of this kind
190
+ $iwp_backup_core->clean_temporary_files('_'.$iwp_backup_core->nonce."-$whichone", 600);
191
+
192
+ // Firstly, make sure that the temporary file is not already being written to - which can happen if a resumption takes place whilst an old run is still active
193
+ $zip_name = $full_path.'.tmp';
194
+ $time_mod = (int)@filemtime($zip_name);
195
+ if (file_exists($zip_name) && $time_mod>100 && ($time_now-$time_mod)<30) {
196
+ $iwp_backup_core->terminate_due_to_activity($zip_name, $time_now, $time_mod);
197
+ }
198
+ if (file_exists($zip_name)) {
199
+ $iwp_backup_core->log("File exists ($zip_name), but was apparently not modified within the last 30 seconds, so we assume that any previous run has now terminated (time_mod=$time_mod, time_now=$time_now, diff=".($time_now-$time_mod).")");
200
+ }
201
+
202
+ // Now, check for other forms of temporary file, which would indicate that some activity is going on (even if it hasn't made it into the main zip file yet)
203
+ // Note: this doesn't catch PclZip temporary files
204
+ $d = dir($this->iwp_backup_dir);
205
+ $match = '_'.$iwp_backup_core->nonce."-".$whichone;
206
+ while (false !== ($e = $d->read())) {
207
+ if ('.' == $e || '..' == $e || !is_file($this->iwp_backup_dir.'/'.$e)) continue;
208
+ $ziparchive_match = preg_match("/$match([0-9]+)?\.zip\.tmp\.([A-Za-z0-9]){6}?$/i", $e);
209
+ $binzip_match = preg_match("/^zi([A-Za-z0-9]){6}$/", $e);
210
+ $pclzip_match = preg_match("/^pclzip-[a-z0-9]+.tmp$/", $e);
211
+ if ($time_now-filemtime($this->iwp_backup_dir.'/'.$e) < 30 && ($ziparchive_match || (0 != $iwp_backup_core->current_resumption && ($binzip_match || $pclzip_match)))) {
212
+ $iwp_backup_core->terminate_due_to_activity($this->iwp_backup_dir.'/'.$e, $time_now, filemtime($this->iwp_backup_dir.'/'.$e));
213
+ }
214
+ }
215
+ @$d->close();
216
+ clearstatcache();
217
+
218
+ if (isset($files_existing)) {
219
+ # Because of zip-splitting, the mere fact that files exist is not enough to indicate that the entity is finished. For that, we need to also see that no subsequent file has been started.
220
+ # Q. What if the previous runner died in between zips, and it is our job to start the next one? A. The next temporary file is created before finishing the former zip, so we are safe (and we are also safe-guarded by the updated value of the index being stored in the database).
221
+ return $files_existing;
222
+ }
223
+
224
+ $this->log_account_space();
225
+
226
+ $this->zip_microtime_start = microtime(true);
227
+
228
+ # The paths in the zip should then begin with '$whichone', having removed WP_CONTENT_DIR from the front
229
+ $zipcode = $this->make_zipfile($create_from_dir, $backup_file_basename, $whichone);
230
+ if ($zipcode !== true) {
231
+ $iwp_backup_core->log("ERROR: Zip failure: Could not create $whichone zip (".$this->index." / $index)");
232
+ $iwp_backup_core->log(sprintf(__("Could not create %s zip. Consult the log file for more information.",'iwp_backup_core'),$whichone), 'error');
233
+ # The caller is required to update $index from $this->index
234
+ return false;
235
+ } else {
236
+ $itext = (empty($this->index)) ? '' : ($this->index+1);
237
+ $full_path = $this->iwp_backup_dir.'/'.$backup_file_basename.'-'.$whichone.$itext.'.zip';
238
+ if (file_exists($full_path.'.tmp')) {
239
+ if (@filesize($full_path.'.tmp') === 0) {
240
+ $iwp_backup_core->log("Did not create $whichone zip (".$this->index.") - not needed");
241
+ @unlink($full_path.'.tmp');
242
+ } else {
243
+
244
+ $checksum_description = '';
245
+
246
+ $checksums = $iwp_backup_core->which_checksums();
247
+
248
+ foreach ($checksums as $checksum) {
249
+
250
+ $cksum = hash_file($checksum, $full_path.'.tmp');
251
+ $iwp_backup_core->jobdata_set($checksum.'-'.$whichone.$this->index, $cksum);
252
+ if ($checksum_description) $checksum_description .= ', ';
253
+ $checksum_description .= "$checksum: $cksum";
254
+
255
+ }
256
+
257
+ @rename($full_path.'.tmp', $full_path);
258
+ $timetaken = max(microtime(true)-$this->zip_microtime_start, 0.000001);
259
+ $kbsize = filesize($full_path)/1024;
260
+ $rate = round($kbsize/$timetaken, 1);
261
+ $iwp_backup_core->log("Created $whichone zip (".$this->index.") - ".round($kbsize,1)." KB in ".round($timetaken,1)." s ($rate KB/s) ($checksum_description)");
262
+ // We can now remove any left-over temporary files from this job
263
+ }
264
+ } elseif ($this->index > $original_index) {
265
+ $iwp_backup_core->log("Did not create $whichone zip (".$this->index.") - not needed (2)");
266
+ # Added 12-Feb-2014 (to help multiple morefiles)
267
+ $this->index--;
268
+ } else {
269
+ $iwp_backup_core->log("Looked-for $whichone zip (".$this->index.") was not found (".basename($full_path).".tmp)", 'warning');
270
+ }
271
+ $iwp_backup_core->clean_temporary_files('_'.$iwp_backup_core->nonce."-$whichone", 0);
272
+ }
273
+
274
+ // Remove cache list files as well, if there are any
275
+ $iwp_backup_core->clean_temporary_files('_'.$iwp_backup_core->nonce."-$whichone", 0, true);
276
+
277
+ # Create the results array to send back (just the new ones, not any prior ones)
278
+ $files_existing = array();
279
+ $res_index = 0;
280
+ for ($i = $original_index; $i<= $this->index; $i++) {
281
+ $itext = (empty($i)) ? '' : ($i+1);
282
+ $full_path = $this->iwp_backup_dir.'/'.$backup_file_basename.'-'.$whichone.$itext.'.zip';
283
+ if (file_exists($full_path)) {
284
+ $files_existing[$res_index] = $backup_file_basename.'-'.$whichone.$itext.'.zip';
285
+ }
286
+ $res_index++;
287
+ }
288
+ return $files_existing;
289
+ }
290
+
291
+ // This method is for calling outside of a cloud_backup() context. It constructs a list of services for which prune operations should be attempted, and then calls prune_retained_backups() if necessary upon them.
292
+ public function do_prune_standalone() {
293
+ global $iwp_backup_core;
294
+
295
+ $services = $iwp_backup_core->just_one($iwp_backup_core->jobdata_get('service'));
296
+ if (!is_array($services)) $services = array($services);
297
+
298
+ $prune_services = array();
299
+
300
+ foreach ($services as $ind => $service) {
301
+ if ($service == "none" || '' == $service) continue;
302
+
303
+ $objname = "IWP_MMB_UploadModule_${service}";
304
+ if (!class_exists($objname) && file_exists($GLOBALS['iwp_mmb_plugin_dir'].'/backup/'.$service.'.php')) {
305
+ require_once($GLOBALS['iwp_mmb_plugin_dir'].'/backup/'.$service.'.php');
306
+ }
307
+ if (class_exists($objname)) {
308
+ $remote_obj = new $objname;
309
+ $pass_to_prune = null;
310
+ $prune_services[$service] = array($remote_obj, null);
311
+ } else {
312
+ $iwp_backup_core->log("Could not prune from service $service: remote method not found");
313
+ }
314
+
315
+ }
316
+
317
+ if (!empty($prune_services)) $this->prune_retained_backups($prune_services);
318
+ }
319
+
320
+ // Dispatch to the relevant function
321
+ public function cloud_backup($backup_array) {
322
+
323
+ global $iwp_backup_core;
324
+
325
+ $services = $iwp_backup_core->just_one($iwp_backup_core->jobdata_get('service'));
326
+ if (!is_array($services)) $services = array($services);
327
+
328
+ // We need to make sure that the loop below actually runs
329
+ if (empty($services)) $services = array('none');
330
+
331
+ $iwp_backup_core->jobdata_set('jobstatus', 'clouduploading');
332
+
333
+ add_action('http_request_args', array($iwp_backup_core, 'modify_http_options'));
334
+
335
+ $upload_status = $iwp_backup_core->jobdata_get('uploading_substatus');
336
+ if (!is_array($upload_status) || !isset($upload_status['t'])) {
337
+ $upload_status = array('i' => 0, 'p' => 0, 't' => max(1, count($services))*count($backup_array));
338
+ $iwp_backup_core->jobdata_set('uploading_substatus', $upload_status);
339
+ }
340
+
341
+ $do_prune = array();
342
+
343
+ # If there was no check-in last time, then attempt a different service first - in case a time-out on the attempted service leads to no activity and everything stopping
344
+ if (count($services) >1 && !empty($iwp_backup_core->no_checkin_last_time)) {
345
+ $iwp_backup_core->log('No check-in last time: will try a different remote service first');
346
+ array_push($services, array_shift($services));
347
+ // Make sure that the 'no worthwhile activity' detector isn't flumoxed by the starting of a new upload at 0%
348
+ if ($iwp_backup_core->current_resumption > 9) $iwp_backup_core->jobdata_set('uploaded_lastreset', $iwp_backup_core->current_resumption);
349
+ if (1 == ($iwp_backup_core->current_resumption % 2) && count($services)>2) array_push($services, array_shift($services));
350
+ }
351
+
352
+ $errors_before_uploads = $iwp_backup_core->error_count();
353
+
354
+ foreach ($services as $ind => $service) {
355
+ # Used for logging by record_upload_chunk()
356
+ $this->current_service = $service;
357
+ # Used when deciding whether to delete the local file
358
+ $this->last_service = ($ind+1 >= count($services) && $errors_before_uploads == $iwp_backup_core->error_count()) ? true : false;
359
+
360
+ $log_extra = ($this->last_service) ? ' (last)' : '';
361
+ $iwp_backup_core->log("Cloud backup selection (".($ind+1)."/".count($services)."): ".$service.$log_extra);
362
+ @set_time_limit(IWP_SET_TIME_LIMIT);
363
+
364
+ $method_include = $GLOBALS['iwp_mmb_plugin_dir'].'/backup/'.$service.'.php';
365
+ if (file_exists($method_include)) require_once($method_include);
366
+
367
+ if ($service == "none" || '' == $service) {
368
+ $iwp_backup_core->log("No remote despatch: user chose no remote backup service");
369
+ # Still want to mark as "uploaded", to signal that nothing more needs doing. (Important on incremental runs with no cloud storage).
370
+ foreach ($backup_array as $bind => $file) {
371
+ if ($iwp_backup_core->is_uploaded($file)) {
372
+ $iwp_backup_core->log("Already uploaded: $file");
373
+ } else {
374
+ $iwp_backup_core->uploaded_file($file, true);
375
+ }
376
+ }
377
+ $this->prune_retained_backups(array("none" => array(null, null)));
378
+ } else {
379
+ $iwp_backup_core->log("Beginning dispatch of backup to remote ($service)");
380
+ $sarray = array();
381
+ foreach ($backup_array as $bind => $file) {
382
+ if ($iwp_backup_core->is_uploaded($file, $service)) {
383
+ $iwp_backup_core->log("Already uploaded to $service: $file");
384
+ } else {
385
+ $sarray[$bind] = $file;
386
+ }
387
+ }
388
+ $objname = "IWP_MMB_UploadModule_$service";
389
+ if (class_exists($objname)) {
390
+ $remote_obj = new $objname;
391
+ if (count($sarray)>0) {
392
+ $pass_to_prune = $remote_obj->backup($sarray);
393
+ $do_prune[$service] = array($remote_obj, $pass_to_prune);
394
+ } else {
395
+ // We still need to make sure that prune is run on this remote storage method, even if all entities were previously uploaded
396
+ $do_prune[$service] = array($remote_obj, null);
397
+ }
398
+ } else {
399
+ $iwp_backup_core->log("Unexpected error: no class '$objname' was found ($method_include)");
400
+ $iwp_backup_core->log(sprintf(__("Unexpected error: no class '%s' was found (your iwp_backup_core installation seems broken - try re-installing)", 'iwp_backup_core'), $objname), 'error');
401
+ }
402
+ }
403
+ }
404
+
405
+ if (!empty($do_prune)) $this->prune_retained_backups($do_prune);
406
+
407
+ remove_action('http_request_args', array($iwp_backup_core, 'modify_http_options'));
408
+
409
+ }
410
+
411
+ private function group_backups($backup_history) {
412
+ return array(array('sets' => $backup_history, 'process_order' => 'keep_newest'));
413
+ }
414
+
415
+ // $services *must* be an array
416
+ public function prune_retained_backups($services) {
417
+
418
+ global $iwp_backup_core, $wpdb;
419
+
420
+ if ($iwp_backup_core->jobdata_get('remotesend_info') != '') {
421
+ $iwp_backup_core->log("Prune old backups from local store: skipping, as this was a remote send operation");
422
+ return;
423
+ }
424
+
425
+ if (method_exists($wpdb, 'check_connection') && (!defined('IWP_SUPPRESS_CONNECTION_CHECKS') || !IWP_SUPPRESS_CONNECTION_CHECKS)) {
426
+ if (!$wpdb->check_connection(false)) {
427
+ $iwp_backup_core->reschedule(60);
428
+ $iwp_backup_core->log("It seems the database went away; scheduling a resumption and terminating for now");
429
+ $iwp_backup_core->record_still_alive();
430
+ die;
431
+ }
432
+ }
433
+
434
+ // If they turned off deletion on local backups, then there is nothing to do
435
+ if (0 == IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_delete_local') && 1 == count($services) && in_array('none', $services)) {
436
+ $iwp_backup_core->log("Prune old backups from local store: nothing to do, since the user disabled local deletion and we are using local backups");
437
+ return;
438
+ }
439
+
440
+ // $iwp_backup_core->jobdata_set('jobstatus', 'pruning');
441
+ // $iwp_backup_core->jobdata_set('prune', 'begun');
442
+ call_user_func_array(array($iwp_backup_core, 'jobdata_set_multi'), array('jobstatus', 'pruning', 'prune', 'begun'));
443
+
444
+ // Number of backups to retain - files
445
+ $label = $iwp_backup_core->jobdata_get('label');
446
+ $IWP_retain = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_retain', 2);
447
+ $IWP_retain = is_numeric($IWP_retain) ? $IWP_retain : 1;
448
+
449
+ // Number of backups to retain - db
450
+ $IWP_retain_db = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_retain_db', $IWP_retain);
451
+ $IWP_retain_db = is_numeric($IWP_retain_db) ? $IWP_retain_db : 1;
452
+ $other_method_backups = iwp_mmb_get_backup_ID_by_taskname('multicall', $label);
453
+ $current_backups = $iwp_backup_core->get_timestamp_by_label($label);
454
+ $all_backups = array();
455
+ $delete_backup = array();
456
+ if (!empty($other_method_backups)) {
457
+ $all_backups = array_merge($all_backups, $other_method_backups);
458
+ if (!empty($current_backups)) {
459
+ $all_backups = array_merge($all_backups, $current_backups);
460
+ ksort($all_backups);
461
+ ksort($current_backups);
462
+ foreach ($other_method_backups as $lastUpdateTime => $historyID) {
463
+ foreach ($current_backups as $time => $value) {
464
+ if ($time > $lastUpdateTime) {
465
+ $delete_backup[$lastUpdateTime] = $historyID;
466
+ }
467
+ break;
468
+ }
469
+ }
470
+ }
471
+ }
472
+ if (!empty($delete_backup)) {
473
+ $total_backups = count($all_backups);
474
+ if ($total_backups > $IWP_retain) {
475
+ require_once($GLOBALS['iwp_mmb_plugin_dir']."/backup.class.singlecall.php");
476
+ $backup_instance = new IWP_MMB_Backup_Singlecall();
477
+ foreach ($delete_backup as $lastUpdateTime => $historyID) {
478
+ $total_backups--;
479
+ $backup_instance->delete_backup(array('result_id' => $historyID));
480
+ if ($total_backups<= $IWP_retain) {
481
+ return;
482
+ }
483
+ }
484
+ }
485
+ }
486
+ // $IWP_retain_db-=1;
487
+ $iwp_backup_core->log("Retain: beginning examination of existing backup sets; user setting: retain_files=$IWP_retain, retain_db=$IWP_retain_db");
488
+
489
+ // Returns an array, most recent first, of backup sets
490
+
491
+
492
+ $ignored_because_imported = array();
493
+ ksort($current_backups);
494
+ $delete_backup_count = count($current_backups);
495
+ if (!empty($current_backups) && $delete_backup_count>$IWP_retain) {
496
+ foreach ($current_backups as $timestamp => $value) {
497
+ $delete_backup_count--;
498
+ $iwp_backup_core->delete_backup(array('result_id'=>$timestamp));
499
+ if ($delete_backup_count==$IWP_retain) {
500
+ break;
501
+ }
502
+ }
503
+ }
504
+
505
+
506
+ // // Remove non-native (imported) backups, which are neither counted nor pruned. It's neater to do these in advance, and log only one line.
507
+ // $functional_backup_history = $backup_history;
508
+ // foreach ($functional_backup_history as $backup_time => $backup_to_examine) {
509
+ // if (isset($backup_to_examine['native']) && false == $backup_to_examine['native']) {
510
+ // $ignored_because_imported[] = $backup_time;
511
+ // unset($functional_backup_history[$backup_time]);
512
+ // }
513
+ // }
514
+ // if (!empty($ignored_because_imported)) {
515
+ // $iwp_backup_core->log("These backup set(s) were imported from a remote location, so will not be counted or pruned. Skipping: ".implode(', ', $ignored_because_imported));
516
+ // }
517
+
518
+ // $backupable_entities = $iwp_backup_core->get_backupable_file_entities(true);
519
+ // $backupable_entities = array_merge($backupable_entities, array('backup_file_basename'=>0, 'db' =>0));
520
+ // $database_backups_found = array();
521
+
522
+ // $file_entities_backups_found = array();
523
+ // foreach ($backupable_entities as $entity => $info) {
524
+ // $file_entities_backups_found[$entity] = 0;
525
+ // }
526
+ // $file_entities_backups_found['db'] = 0;
527
+ // if (false === ($backup_db_groups = apply_filters('IWP_group_backups_for_pruning', false, $functional_backup_history, 'db'))) {
528
+ // $backup_db_groups = $this->group_backups($functional_backup_history);
529
+ // }
530
+ // $iwp_backup_core->log("Number of backup sets in history: ".count($backup_history)."; groups (db): ".count($backup_db_groups));
531
+ // foreach ($backup_db_groups as $group_id => $group) {
532
+
533
+ // // The array returned by iwp_backup_core::get_backup_history() is already sorted, with most-recent first
534
+ // // foreach ($backup_history as $backup_datestamp => $backup_to_examine) {
535
+
536
+ // if (empty($group['sets']) || !is_array($group['sets'])) continue;
537
+ // $sets = $group['sets'];
538
+
539
+ // // Sort the groups into the desired "keep this first" order
540
+ // $process_order = (!empty($group['process_order']) && 'keep_oldest' == $group['process_order']) ? 'keep_oldest' : 'keep_newest';
541
+ // if ('keep_oldest' == $process_order) ksort($sets);
542
+
543
+ // $rule = !empty($group['rule']) ? $group['rule'] : array('after-howmany' => 0, 'after-period' => 0, 'every-period' => 1, 'every-howmany' => 1);
544
+
545
+ // foreach ($sets as $backup_datestamp => $backup_to_examine) {
546
+ // if ($backup_to_examine['label'] != $label) {
547
+ // continue;
548
+ // }
549
+
550
+ // $files_to_prune = array();
551
+ // $nonce = empty($backup_to_examine['nonce']) ? '???' : $backup_to_examine['nonce'];
552
+
553
+ // // $backup_to_examine is an array of file names, keyed on db/plugins/themes/uploads
554
+ // // The new backup_history array is saved afterwards, so remember to unset the ones that are to be deleted
555
+ // $iwp_backup_core->log(sprintf("Examining (for databases) backup set with group_id=$group_id, nonce=%s, datestamp=%s (%s)", $nonce, $backup_datestamp, gmdate('M d Y H:i:s', $backup_datestamp)));
556
+
557
+ // // Auto-backups are only counted or deleted once we have reached the retain limit - before that, they are skipped
558
+ // $is_autobackup = !empty($backup_to_examine['autobackup']);
559
+
560
+ // $remote_sent = (!empty($backup_to_examine['service']) && ((is_array($backup_to_examine['service']) && in_array('remotesend', $backup_to_examine['service'])) || 'remotesend' === $backup_to_examine['service'])) ? true : false;
561
+
562
+ // $any_deleted_via_filter_yet = false;
563
+
564
+ // // Databases
565
+ // foreach ($backup_to_examine as $key => $data) {
566
+ // if ('db' != strtolower(substr($key, 0, 2)) || '-size' == substr($key, -5, 5)) continue;
567
+
568
+ // if (empty($database_backups_found[$key])) $database_backups_found[$key] = 0;
569
+
570
+ // if ($nonce == $iwp_backup_core->nonce) {
571
+ // $iwp_backup_core->log("This backup set is the backup set just made, so will not be deleted.");
572
+ // $database_backups_found[$key]++;
573
+ // continue;
574
+ // }
575
+
576
+ // if ($is_autobackup) {
577
+ // if ($any_deleted_via_filter_yet) {
578
+ // $iwp_backup_core->log("This backup set ($backup_datestamp) was an automatic backup, but we have previously deleted a backup due to a limit, so it will be pruned (but not counted towards numerical limits).");
579
+ // $prune_it = true;
580
+ // } elseif ($database_backups_found[$key] < $IWP_retain_db) {
581
+ // $iwp_backup_core->log("This backup set ($backup_datestamp) was an automatic backup, and we have not yet reached any retain limits, so it will not be counted or pruned. Skipping.");
582
+ // continue;
583
+ // } else {
584
+ // $iwp_backup_core->log("This backup set ($backup_datestamp) was an automatic backup, and we have already reached retain limits, so it will be pruned.");
585
+ // $prune_it = true;
586
+ // }
587
+ // } else {
588
+ // $prune_it = false;
589
+ // }
590
+
591
+ // if ($remote_sent) {
592
+ // $prune_it = true;
593
+ // $iwp_backup_core->log("$backup_datestamp: $key: was sent to remote site; will remove from local record (only)");
594
+ // }
595
+
596
+ // // All non-auto backups must be run through this filter (in date order) regardless of the current state of $prune_it - so that filters are able to track state.
597
+ // $prune_it_before_filter = $prune_it;
598
+
599
+ // if (!$is_autobackup) $prune_it = apply_filters('IWP_prune_or_not', $prune_it, 'db', $backup_datestamp, $key, $database_backups_found[$key], $rule, $group_id);
600
+
601
+ // // Apply the final retention limit list (do not increase the 'retained' counter before seeing if the backup is being pruned for some other reason)
602
+ // if (!$prune_it && !$is_autobackup) {
603
+
604
+ // if ($database_backups_found[$key] + 1 > $IWP_retain_db) {
605
+ // $prune_it = true;
606
+
607
+ // $fname = (is_string($data)) ? $data : $data[0];
608
+ // $iwp_backup_core->log("$backup_datestamp: $key: this set includes a database (".$fname."); db count is now ".$database_backups_found[$key]);
609
+
610
+ // $iwp_backup_core->log("$backup_datestamp: $key: over retain limit ($IWP_retain_db); will delete this database");
611
+ // }
612
+
613
+ // }
614
+
615
+ // if ($prune_it) {
616
+ // if (!$prune_it_before_filter) $any_deleted_via_filter_yet = true;
617
+
618
+ // if (!empty($data)) {
619
+ // $size_key = $key.'-size';
620
+ // $size = isset($backup_to_examine[$size_key]) ? $backup_to_examine[$size_key] : null;
621
+ // foreach ($services as $service => $sd) {
622
+ // $this->prune_file($service, $data, $sd[0], $sd[1], array($size));
623
+ // }
624
+ // }
625
+ // unset($backup_to_examine[$key]);
626
+ // $iwp_backup_core->record_still_alive();
627
+ // } elseif (!$is_autobackup) {
628
+ // $database_backups_found[$key]++;
629
+ // }else{
630
+ // continue;
631
+ // }
632
+
633
+ // $backup_to_examine = $this->remove_backup_set_if_empty($backup_to_examine, $backup_datestamp, $backupable_entities, $backup_history);
634
+ // if (empty($backup_to_examine)) {
635
+ // unset($functional_backup_history[$backup_datestamp]);
636
+ // unset($backup_history[$backup_datestamp]);
637
+ // $this->maybe_save_backup_history_and_reschedule($backup_history);
638
+ // } else {
639
+ // $functional_backup_history[$backup_datestamp] = $backup_to_examine;
640
+ // $backup_history[$backup_datestamp] = $backup_to_examine;
641
+ // }
642
+ // }
643
+ // }
644
+ // }
645
+
646
+ // if (false === ($backup_files_groups = apply_filters('IWP_group_backups_for_pruning', false, $functional_backup_history, 'files'))) {
647
+ // $backup_files_groups = $this->group_backups($functional_backup_history);
648
+ // }
649
+
650
+ // $iwp_backup_core->log("Number of backup sets in history: ".count($backup_history)."; groups (files): ".count($backup_files_groups));
651
+
652
+ // // Now again - this time for the files
653
+ // foreach ($backup_files_groups as $group_id => $group) {
654
+
655
+ // // The array returned by iwp_backup_core::get_backup_history() is already sorted, with most-recent first
656
+ // // foreach ($backup_history as $backup_datestamp => $backup_to_examine) {
657
+
658
+ // if (empty($group['sets']) || !is_array($group['sets'])) continue;
659
+ // $sets = $group['sets'];
660
+
661
+ // // Sort the groups into the desired "keep this first" order
662
+ // $process_order = (!empty($group['process_order']) && 'keep_oldest' == $group['process_order']) ? 'keep_oldest' : 'keep_newest';
663
+ // // Youngest - i.e. smallest epoch - first
664
+ // if ('keep_oldest' == $process_order) ksort($sets);
665
+
666
+ // $rule = !empty($group['rule']) ? $group['rule'] : array('after-howmany' => 0, 'after-period' => 0, 'every-period' => 1, 'every-howmany' => 1);
667
+
668
+ // foreach ($sets as $backup_datestamp => $backup_to_examine) {
669
+ // if ($backup_to_examine['label'] != $label) {
670
+ // continue;
671
+ // }
672
+ // $files_to_prune = array();
673
+ // $nonce = empty($backup_to_examine['nonce']) ? '???' : $backup_to_examine['nonce'];
674
+
675
+ // // $backup_to_examine is an array of file names, keyed on db/plugins/themes/uploads
676
+ // // The new backup_history array is saved afterwards, so remember to unset the ones that are to be deleted
677
+ // $iwp_backup_core->log(sprintf("Examining (for files) backup set with nonce=%s, datestamp=%s (%s)", $nonce, $backup_datestamp, gmdate('M d Y H:i:s', $backup_datestamp)));
678
+
679
+ // // This was already done earlier
680
+ // // if (isset($backup_to_examine['native']) && false == $backup_to_examine['native']) {
681
+ // // $iwp_backup_core->log("This backup set was imported from a remote location, so will not be counted or pruned. Skipping.");
682
+ // // continue;
683
+ // // }
684
+
685
+ // // Auto-backups are only counted or deleted once we have reached the retain limit - before that, they are skipped
686
+ // $is_autobackup = !empty($backup_to_examine['autobackup']);
687
+
688
+ // $remote_sent = (!empty($backup_to_examine['service']) && ((is_array($backup_to_examine['service']) && in_array('remotesend', $backup_to_examine['service'])) || 'remotesend' === $backup_to_examine['service'])) ? true : false;
689
+
690
+ // $any_deleted_via_filter_yet = false;
691
+
692
+ // $file_sizes = array();
693
+
694
+ // // Files
695
+ // foreach ($backupable_entities as $entity => $info) {
696
+ // if (!empty($backup_to_examine[$entity])) {
697
+
698
+ // // This should only be able to happen if you import backups with a future timestamp
699
+ // if ($nonce == $iwp_backup_core->nonce) {
700
+ // $iwp_backup_core->log("This backup set is the backup set just made, so will not be deleted.");
701
+ // $file_entities_backups_found[$entity]++;
702
+ // continue;
703
+ // }
704
+
705
+ // if ($is_autobackup) {
706
+ // if ($any_deleted_via_filter_yet) {
707
+ // $iwp_backup_core->log("This backup set was an automatic backup, but we have previously deleted a backup due to a limit, so it will be pruned (but not counted towards numerical limits).");
708
+ // $prune_it = true;
709
+ // } elseif ($file_entities_backups_found[$entity] < $IWP_retain) {
710
+ // $iwp_backup_core->log("This backup set ($backup_datestamp) was an automatic backup, and we have not yet reached any retain limits, so it will not be counted or pruned. Skipping.");
711
+ // continue;
712
+ // } else {
713
+ // $iwp_backup_core->log("This backup set ($backup_datestamp) was an automatic backup, and we have already reached retain limits, so it will be pruned.");
714
+ // $prune_it = true;
715
+ // }
716
+ // } else {
717
+ // $prune_it = false;
718
+ // }
719
+
720
+ // if ($remote_sent) {
721
+ // $prune_it = true;
722
+ // }
723
+
724
+ // // All non-auto backups must be run through this filter (in date order) regardless of the current state of $prune_it - so that filters are able to track state.
725
+ // $prune_it_before_filter = $prune_it;
726
+ // if (!$is_autobackup) $prune_it = apply_filters('IWP_backup_core_prune_or_not', $prune_it, 'files', $backup_datestamp, $entity, $file_entities_backups_found[$entity], $rule, $group_id);
727
+
728
+ // // The "more than maximum to keep?" counter should not be increased until we actually know that the set is being kept. Before verison 1.11.22, we checked this before running the filter, which resulted in the counter being increased for sets that got pruned via the filter (i.e. not kept) - and too many backups were thus deleted
729
+ // if (!$prune_it && !$is_autobackup) {
730
+ // if ($file_entities_backups_found[$entity] >= $IWP_retain) {
731
+ // $iwp_backup_core->log("$entity: over retain limit ($IWP_retain); will delete this file entity");
732
+ // $prune_it = true;
733
+ // }
734
+ // }else{
735
+ // continue;
736
+ // }
737
+
738
+ // if ($prune_it) {
739
+ // if (!$prune_it_before_filter) $any_deleted_via_filter_yet = true;
740
+ // $prune_this = $backup_to_examine[$entity];
741
+ // if (is_string($prune_this)) $prune_this = array($prune_this);
742
+ // foreach ($prune_this as $k => $prune_file) {
743
+ // if ($remote_sent) {
744
+ // $iwp_backup_core->log("$entity: $backup_datestamp: was sent to remote site; will remove from local record (only)");
745
+ // }
746
+ // $size_key = (0 == $k) ? $entity.'-size' : $entity.$k.'-size';
747
+ // $size = (isset($backup_to_examine[$size_key])) ? $backup_to_examine[$size_key] : null;
748
+ // $files_to_prune[] = $prune_file;
749
+ // $file_sizes[] = $size;
750
+ // }
751
+ // unset($backup_to_examine[$entity]);
752
+
753
+ // } elseif (!$is_autobackup) {
754
+ // $file_entities_backups_found[$entity]++;
755
+ // }
756
+ // }
757
+ // }
758
+
759
+ // // Sending an empty array is not itself a problem - except that the remote storage method may not check that before setting up a connection, which can waste time: especially if this is done every time around the loop.
760
+ // if (!empty($files_to_prune)) {
761
+ // // Actually delete the files
762
+ // foreach ($services as $service => $sd) {
763
+ // $this->prune_file($service, $files_to_prune, $sd[0], $sd[1], $file_sizes);
764
+ // $iwp_backup_core->record_still_alive();
765
+ // }
766
+ // }
767
+
768
+ // $backup_to_examine = $this->remove_backup_set_if_empty($backup_to_examine, $backup_datestamp, $backupable_entities, $backup_history);
769
+ // if (empty($backup_to_examine)) {
770
+ // // unset($functional_backup_history[$backup_datestamp]);
771
+ // unset($backup_history[$backup_datestamp]);
772
+ // $this->maybe_save_backup_history_and_reschedule($backup_history);
773
+ // } else {
774
+ // // $functional_backup_history[$backup_datestamp] = $backup_to_examine;
775
+ // $backup_history[$backup_datestamp] = $backup_to_examine;
776
+ // }
777
+
778
+ // // Loop over backup sets
779
+ // }
780
+
781
+ // // Look over backup groups
782
+ // }
783
+
784
+ // $iwp_backup_core->log("Retain: saving new backup history (sets now: ".count($backup_history).") and finishing retain operation");
785
+
786
+ // IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_backup_history', $backup_history, false);
787
+
788
+ do_action('IWP_prune_retained_backups_finished');
789
+
790
+ $iwp_backup_core->jobdata_set('prune', 'finished');
791
+
792
+ }
793
+
794
+ // The purpose of this is to save the backup history periodically - for the benefit of setups where the pruning takes longer than the total allow run time (e.g. if the network communications to the remote storage have delays in, and there are a lot of sets to scan)
795
+ private function maybe_save_backup_history_and_reschedule($backup_history) {
796
+ static $last_saved_at = 0;
797
+ if (!$last_saved_at) $last_saved_at = time();
798
+ if (time() - $last_saved_at >= 10) {
799
+ global $iwp_backup_core;
800
+ $iwp_backup_core->log("Retain: saving new backup history, because at least 10 seconds have passed since the last save (sets now: ".count($backup_history).")");
801
+ IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_backup_history', $backup_history, false);
802
+ $iwp_backup_core->something_useful_happened();
803
+ $last_saved_at = time();
804
+ }
805
+ }
806
+
807
+ private function remove_backup_set_if_empty($backup_to_examine, $backup_datestamp, $backupable_entities, $backup_history) {
808
+
809
+ global $iwp_backup_core;
810
+
811
+ // Get new result, post-deletion; anything left in this set?
812
+ $contains_files = 0;
813
+ foreach ($backupable_entities as $entity => $info) {
814
+ if (isset($backup_to_examine[$entity])) {
815
+ $contains_files = 1;
816
+ break;
817
+ }
818
+ }
819
+
820
+ $contains_db = 0;
821
+ foreach ($backup_to_examine as $key => $data) {
822
+ if ('db' == strtolower(substr($key, 0, 2)) && '-size' != substr($key, -5, 5)) {
823
+ $contains_db = 1;
824
+ break;
825
+ }
826
+ }
827
+
828
+ // Delete backup set completely if empty, o/w just remove DB
829
+ // We search on the four keys which represent data, allowing other keys to be used to track other things
830
+ if (!$contains_files && !$contains_db) {
831
+ $iwp_backup_core->log("This backup set is now empty; will remove from history");
832
+ if (isset($backup_to_examine['nonce'])) {
833
+ $fullpath = $this->iwp_backup_dir."/log.".$backup_to_examine['nonce'].".txt";
834
+ if (is_file($fullpath)) {
835
+ $iwp_backup_core->log("Deleting log file (log.".$backup_to_examine['nonce'].".txt)");
836
+ @unlink($fullpath);
837
+ } else {
838
+ $iwp_backup_core->log("Corresponding log file (log.".$backup_to_examine['nonce'].".txt) not found - must have already been deleted");
839
+ }
840
+ } else {
841
+ $iwp_backup_core->log("No nonce record found in the backup set, so cannot delete any remaining log file");
842
+ }
843
+ // unset($backup_history[$backup_datestamp]);
844
+ return false;
845
+ } else {
846
+ $iwp_backup_core->log("This backup set remains non-empty (f=$contains_files/d=$contains_db); will retain in history");
847
+ return $backup_to_examine;
848
+ }
849
+
850
+ }
851
+
852
+ # $dofiles: An array of files (or a single string for one file)
853
+ private function prune_file($service, $dofiles, $method_object = null, $object_passback = null, $file_sizes = array()) {
854
+ global $iwp_backup_core;
855
+ if (!is_array($dofiles)) $dofiles=array($dofiles);
856
+
857
+ if (!apply_filters('IWP_prune_file', true, $dofiles, $service, $method_object, $object_passback, $file_sizes)) {
858
+ $iwp_backup_core->log("Prune: service=$service: skipped via filter");
859
+ }
860
+ foreach ($dofiles as $i => $dofile) {
861
+ if (empty($dofile)) continue;
862
+ $iwp_backup_core->log("Delete file: $dofile, service=$service");
863
+ $fullpath = $this->iwp_backup_dir.'/'.$dofile;
864
+ // delete it if it's locally available
865
+ if (file_exists($fullpath)) {
866
+ $iwp_backup_core->log("Deleting local copy ($dofile)");
867
+ @unlink($fullpath);
868
+ }
869
+ }
870
+ // Despatch to the particular method's deletion routine
871
+ if (!is_null($method_object)) $method_object->delete($dofiles, $object_passback, $file_sizes);
872
+ }
873
+
874
+ // 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
875
+ private function backup_db_sorttables($a_arr, $b_arr) {
876
+
877
+ $a = $a_arr['name'];
878
+ $a_table_type = $a_arr['type'];
879
+ $b = $b_arr['name'];
880
+ $b_table_type = $b_arr['type'];
881
+
882
+ // Views must always go after tables (since they can depend upon them)
883
+ if ('VIEW' == $a_table_type && 'VIEW' != $b_table_type) return 1;
884
+ if ('VIEW' == $b_table_type && 'VIEW' != $a_table_type) return -1;
885
+
886
+ if ('wp' != $this->whichdb) return strcmp($a, $b);
887
+
888
+ global $iwp_backup_core;
889
+ if ($a == $b) return 0;
890
+ $our_table_prefix = $this->table_prefix_raw;
891
+ if ($a == $our_table_prefix.'options') return -1;
892
+ if ($b == $our_table_prefix.'options') return 1;
893
+ if ($a == $our_table_prefix.'site') return -1;
894
+ if ($b == $our_table_prefix.'site') return 1;
895
+ if ($a == $our_table_prefix.'blogs') return -1;
896
+ if ($b == $our_table_prefix.'blogs') return 1;
897
+ if ($a == $our_table_prefix.'users') return -1;
898
+ if ($b == $our_table_prefix.'users') return 1;
899
+ if ($a == $our_table_prefix.'usermeta') return -1;
900
+ if ($b == $our_table_prefix.'usermeta') return 1;
901
+
902
+ if (empty($our_table_prefix)) return strcmp($a, $b);
903
+
904
+ try {
905
+ $core_tables = array_merge($this->wpdb_obj->tables, $this->wpdb_obj->global_tables, $this->wpdb_obj->ms_global_tables);
906
+ } catch (Exception $e) {
907
+ $iwp_backup_core->log($e->getMessage());
908
+ }
909
+
910
+ if (empty($core_tables)) $core_tables = array('terms', 'term_taxonomy', 'termmeta', 'term_relationships', 'commentmeta', 'comments', 'links', 'postmeta', 'posts', 'site', 'sitemeta', 'blogs', 'blogversions');
911
+
912
+ global $iwp_backup_core;
913
+ $na = $iwp_backup_core->str_replace_once($our_table_prefix, '', $a);
914
+ $nb = $iwp_backup_core->str_replace_once($our_table_prefix, '', $b);
915
+ if (in_array($na, $core_tables) && !in_array($nb, $core_tables)) return -1;
916
+ if (!in_array($na, $core_tables) && in_array($nb, $core_tables)) return 1;
917
+ return strcmp($a, $b);
918
+ }
919
+
920
+ private function log_account_space() {
921
+ # Don't waste time if space is huge
922
+ if (!empty($this->account_space_oodles)) return;
923
+ global $iwp_backup_core;
924
+ $hosting_bytes_free = $iwp_backup_core->get_hosting_disk_quota_free();
925
+ if (is_array($hosting_bytes_free)) {
926
+ $perc = round(100*$hosting_bytes_free[1]/(max($hosting_bytes_free[2], 1)), 1);
927
+ $iwp_backup_core->log(sprintf('Free disk space in account: %s (%s used)', round($hosting_bytes_free[3]/1048576, 1)." MB", "$perc %"));
928
+ }
929
+ }
930
+
931
+ // Returns the basename up to and including the nonce (but not the entity)
932
+ private function get_backup_file_basename_from_time($use_time) {
933
+ global $iwp_backup_core;
934
+ $site_name = str_replace(array(
935
+ "_",
936
+ "/",
937
+ "~"
938
+ ), array(
939
+ "",
940
+ "-",
941
+ "-"
942
+ ), rtrim(remove_http(get_bloginfo('url')), "/"));
943
+ return $site_name.'_backup_'.get_date_from_gmt(gmdate('Y-m-d H:i:s', $use_time), 'Y-m-d-Hi').'_'.$this->blog_name.'_'.$iwp_backup_core->nonce.'_'.$iwp_backup_core->get_wordpress_version();
944
+ }
945
+
946
+ private function find_existing_zips($dir, $match_nonce) {
947
+ $zips = array();
948
+ if ($handle = opendir($dir)) {
949
+ while (false !== ($entry = readdir($handle))) {
950
+ if ($entry != "." && $entry != "..") {
951
+ if (preg_match('/^backup_(\d{4})-(\d{2})-(\d{2})-(\d{2})(\d{2})_.*_([0-9a-f]{12})-([\-a-z]+)([0-9]+)?\.zip$/i', $entry, $matches)) {
952
+ if ($matches[6] !== $match_nonce) continue;
953
+ $timestamp = mktime($matches[4], $matches[5], 0, $matches[2], $matches[3], $matches[1]);
954
+ $entity = $matches[7];
955
+ $index = empty($matches[8]) ? '0': $matches[8];
956
+ $zips[$entity][$index] = array($timestamp, $entry);
957
+ }
958
+ }
959
+ }
960
+ }
961
+ return $zips;
962
+ }
963
+
964
+ // $files should be an array as returned by find_existing_zips()
965
+ private function file_exists($files, $entity, $index = 0) {
966
+ if (isset($files[$entity]) && isset($files[$entity][$index])) {
967
+ $file = $files[$entity][$index];
968
+ // Return the filename
969
+ return $file[1];
970
+ } else {
971
+ return false;
972
+ }
973
+ }
974
+
975
+ // This function is resumable
976
+ private function backup_dirs($job_status) {
977
+
978
+ global $iwp_backup_core;
979
+
980
+ if (!$iwp_backup_core->backup_time) $iwp_backup_core->backup_time_nonce();
981
+
982
+ $use_time = apply_filters('IWP_backup_time_thisrun', $iwp_backup_core->backup_time);
983
+ $backup_file_basename = $this->get_backup_file_basename_from_time($use_time);
984
+
985
+ $backup_array = array();
986
+
987
+ $possible_backups = $iwp_backup_core->get_backupable_file_entities(true);
988
+
989
+ // Was there a check-in last time? If not, then reduce the amount of data attempted
990
+ if ($job_status != 'finished' && $iwp_backup_core->current_resumption >= 2) {
991
+ if (!empty($iwp_backup_core->no_checkin_last_time) || !$iwp_backup_core->newresumption_scheduled) {
992
+ if ($iwp_backup_core->current_resumption - $iwp_backup_core->last_successful_resumption > 2 || !$iwp_backup_core->newresumption_scheduled) {
993
+ $this->try_split = true;
994
+ } elseif ($iwp_backup_core->current_resumption<=10) {
995
+ $maxzipbatch = $iwp_backup_core->jobdata_get('maxzipbatch', 26214400);
996
+ if ((int)$maxzipbatch < 1) $maxzipbatch = 26214400;
997
+
998
+ $new_maxzipbatch = max(floor($maxzipbatch * 0.75), 20971520);
999
+ if ($new_maxzipbatch < $maxzipbatch) {
1000
+ $iwp_backup_core->log("No check-in was detected on the previous run - as a result, we are reducing the batch amount (old=$maxzipbatch, new=$new_maxzipbatch)");
1001
+ $iwp_backup_core->jobdata_set('maxzipbatch', $new_maxzipbatch);
1002
+ $iwp_backup_core->jobdata_set('maxzipbatch_ceiling', $new_maxzipbatch);
1003
+ }
1004
+ }
1005
+ }
1006
+ }
1007
+
1008
+ if($job_status != 'finished' && !$iwp_backup_core->really_is_writable($this->iwp_backup_dir)) {
1009
+ $iwp_backup_core->log("Backup directory (".$this->iwp_backup_dir.") is not writable, or does not exist");
1010
+ $iwp_backup_core->log(sprintf(__("Backup directory (%s) is not writable, or does not exist.", 'InfiniteWP'), $this->iwp_backup_dir), 'error');
1011
+ return array();
1012
+ }
1013
+
1014
+ $this->job_file_entities = $iwp_backup_core->jobdata_get('job_file_entities');
1015
+ # This is just used for the visual feedback (via the 'substatus' key)
1016
+ $which_entity = 0;
1017
+ # e.g. plugins, themes, uploads, others
1018
+ # $whichdir might be an array (if $youwhat is 'more')
1019
+
1020
+ # Returns an array (keyed off the entity) of ($timestamp, $filename) arrays
1021
+ $existing_zips = $this->find_existing_zips($this->iwp_backup_dir, $iwp_backup_core->nonce);
1022
+
1023
+ foreach ($possible_backups as $youwhat => $whichdir) {
1024
+
1025
+ if (isset($this->job_file_entities[$youwhat])) {
1026
+
1027
+ $index = (int)$this->job_file_entities[$youwhat]['index'];
1028
+ if (empty($index)) $index=0;
1029
+ $indextext = (0 == $index) ? '' : (1+$index);
1030
+
1031
+ $zip_file = $this->iwp_backup_dir.'/'.$backup_file_basename.'-'.$youwhat.$indextext.'.zip';
1032
+
1033
+ # Split needed?
1034
+ $split_every = max((int)$iwp_backup_core->jobdata_get('split_every'), 250);
1035
+ //if (file_exists($zip_file) && filesize($zip_file) > $split_every*1048576) {
1036
+ if (false != ($existing_file = $this->file_exists($existing_zips, $youwhat, $index)) && filesize($this->iwp_backup_dir.'/'.$existing_file) > $split_every*1048576) {
1037
+ $index++;
1038
+ $this->job_file_entities[$youwhat]['index'] = $index;
1039
+ $iwp_backup_core->jobdata_set('job_file_entities', $this->job_file_entities);
1040
+ }
1041
+
1042
+ // Populate prior parts of array, if we're on a subsequent zip file
1043
+ if ($index > 0) {
1044
+ for ($i=0; $i<$index; $i++) {
1045
+ $itext = (0 == $i) ? '' : ($i+1);
1046
+ // Get the previously-stored filename if possible (which should be always); failing that, base it on the current run
1047
+
1048
+ $zip_file = (isset($this->backup_files_array[$youwhat]) && isset($this->backup_files_array[$youwhat][$i])) ? $this->backup_files_array[$youwhat][$i] : $backup_file_basename.'-'.$youwhat.$itext.'.zip';
1049
+
1050
+ $backup_array[$youwhat][$i] = $zip_file;
1051
+ $z = $this->iwp_backup_dir.'/'.$zip_file;
1052
+ $itext = (0 == $i) ? '' : $i;
1053
+
1054
+ $fs_key = $youwhat.$itext.'-size';
1055
+ if (file_exists($z)) {
1056
+ $backup_array[$fs_key] = filesize($z);
1057
+ } elseif (isset($this->backup_files_array[$fs_key])) {
1058
+ $backup_array[$fs_key] = $this->backup_files_array[$fs_key];
1059
+ }
1060
+ }
1061
+ }
1062
+
1063
+ // I am not certain that all the conditions in here are possible. But there's no harm.
1064
+ if ('finished' == $job_status) {
1065
+ // Add the final part of the array
1066
+ if ($index > 0) {
1067
+ $zip_file = (isset($this->backup_files_array[$youwhat]) && isset($this->backup_files_array[$youwhat][$index])) ? $this->backup_files_array[$youwhat][$index] : $backup_file_basename.'-'.$youwhat.($index+1).'.zip';
1068
+
1069
+ //$fbase = $backup_file_basename.'-'.$youwhat.($index+1).'.zip';
1070
+ $z = $this->iwp_backup_dir.'/'.$zip_file;
1071
+ $fs_key = $youwhat.$index.'-size';
1072
+ if (file_exists($z)) {
1073
+ $backup_array[$youwhat][$index] = $fbase;
1074
+ $backup_array[$fs_key] = filesize($z);
1075
+ } elseif (isset($this->backup_files_array[$fs_key])) {
1076
+ $backup_array[$youwhat][$index] = $fbase;
1077
+ $backup_array[$fs_key] = $this->backup_files_array[$fskey];
1078
+ }
1079
+ } else {
1080
+ $zip_file = (isset($this->backup_files_array[$youwhat]) && isset($this->backup_files_array[$youwhat][0])) ? $this->backup_files_array[$youwhat][0] : $backup_file_basename.'-'.$youwhat.'.zip';
1081
+
1082
+ $backup_array[$youwhat] = $zip_file;
1083
+ $fs_key=$youwhat.'-size';
1084
+
1085
+ if (file_exists($zip_file)) {
1086
+ $backup_array[$fs_key] = filesize($zip_file);
1087
+ } elseif (isset($this->backup_files_array[$fs_key])) {
1088
+ $backup_array[$fs_key] = $this->backup_files_array[$fs_key];
1089
+ }
1090
+ }
1091
+ } else {
1092
+
1093
+ $which_entity++;
1094
+ $iwp_backup_core->jobdata_set('filecreating_substatus', array('e' => $youwhat, 'i' => $which_entity, 't' => count($this->job_file_entities)));
1095
+
1096
+ if ('others' == $youwhat) $iwp_backup_core->log("Beginning backup of other directories found in the content directory (index: $index)");
1097
+
1098
+ # Apply a filter to allow add-ons to provide their own method for creating a zip of the entity
1099
+ $created = apply_filters('IWP_backup_makezip_'.$youwhat, $whichdir, $backup_file_basename, $index);
1100
+ # If the filter did not lead to something being created, then use the default method
1101
+ if ($created === $whichdir) {
1102
+
1103
+ // http://www.phpconcept.net/pclzip/user-guide/53
1104
+ /* First parameter to create is:
1105
+ An array of filenames or dirnames,
1106
+ or
1107
+ A string containing the filename or a dirname,
1108
+ or
1109
+ A string containing a list of filename or dirname separated by a comma.
1110
+ */
1111
+
1112
+ if ('others' == $youwhat) {
1113
+ $dirlist = $iwp_backup_core->backup_others_dirlist(true);
1114
+ } elseif ('uploads' == $youwhat) {
1115
+ $dirlist = $iwp_backup_core->backup_uploads_dirlist(true);
1116
+ } elseif ('more' == $youwhat) {
1117
+ $dirlist = $iwp_backup_core->backup_more_dirlist($whichdir);
1118
+ }else {
1119
+ $dirlist = $whichdir;
1120
+ if (is_array($dirlist)) $dirlist=array_shift($dirlist);
1121
+ }
1122
+
1123
+ if (count($dirlist)>0) {
1124
+ $created = $this->create_zip($dirlist, $youwhat, $backup_file_basename, $index);
1125
+ # Now, store the results
1126
+ if (!is_string($created) && !is_array($created)) $iwp_backup_core->log("$youwhat: create_zip returned an error");
1127
+ } else {
1128
+ $iwp_backup_core->log("No backup of $youwhat: there was nothing found to back up");
1129
+ }
1130
+ }
1131
+
1132
+ if ($created != $whichdir && (is_string($created) || is_array($created))) {
1133
+ if (is_string($created)) $created=array($created);
1134
+ foreach ($created as $findex => $fname) {
1135
+ $backup_array[$youwhat][$index] = $fname;
1136
+ $itext = ($index == 0) ? '' : $index;
1137
+ $index++;
1138
+ $backup_array[$youwhat.$itext.'-size'] = filesize($this->iwp_backup_dir.'/'.$fname);
1139
+ }
1140
+ }
1141
+
1142
+ $this->job_file_entities[$youwhat]['index'] = $this->index;
1143
+ $iwp_backup_core->jobdata_set('job_file_entities', $this->job_file_entities);
1144
+
1145
+ }
1146
+ } else {
1147
+ $iwp_backup_core->log("No backup of $youwhat: excluded by user's options");
1148
+ }
1149
+ }
1150
+
1151
+ return $backup_array;
1152
+ }
1153
+
1154
+ // This uses a saved status indicator; its only purpose is to indicate *total* completion; there is no actual danger, just wasted time, in resuming when it was not needed. So the saved status indicator just helps save resources.
1155
+ public function resumable_backup_of_files($resumption_no) {
1156
+ global $iwp_backup_core;
1157
+ // Backup directories and return a numerically indexed array of file paths to the backup files
1158
+ $bfiles_status = $iwp_backup_core->jobdata_get('backup_files');
1159
+ $this->backup_files_array = $iwp_backup_core->jobdata_get('backup_files_array');;
1160
+ if (!is_array($this->backup_files_array)) $this->backup_files_array = array();
1161
+ if ('finished' == $bfiles_status) {
1162
+ $iwp_backup_core->log("Creation of backups of directories: already finished");
1163
+ # Check for recent activity
1164
+ foreach ($this->backup_files_array as $files) {
1165
+ if (!is_array($files)) $files=array($files);
1166
+ foreach ($files as $file) $iwp_backup_core->check_recent_modification($this->iwp_backup_dir.'/'.$file);
1167
+ }
1168
+ } elseif ('begun' == $bfiles_status) {
1169
+ $this->first_run = apply_filters('IWP_filerun_firstrun', 0);
1170
+ if ($resumption_no > $this->first_run) {
1171
+ $iwp_backup_core->log("Creation of backups of directories: had begun; will resume");
1172
+ } else {
1173
+ $iwp_backup_core->log("Creation of backups of directories: beginning");
1174
+ }
1175
+ $iwp_backup_core->jobdata_set('jobstatus', 'filescreating');
1176
+ $this->backup_files_array = $this->backup_dirs($bfiles_status);
1177
+ $iwp_backup_core->jobdata_set('backup_files_array', $this->backup_files_array);
1178
+ $iwp_backup_core->jobdata_set('backup_files', 'finished');
1179
+ $iwp_backup_core->jobdata_set('jobstatus', 'filescreated');
1180
+ } else {
1181
+ # This is not necessarily a backup run which is meant to contain files at all
1182
+ $iwp_backup_core->log('This backup run is not intended for files - skipping');
1183
+ return array();
1184
+ }
1185
+ return $this->backup_files_array;
1186
+ }
1187
+
1188
+ /* This function is resumable, using the following method:
1189
+ - Each table is written out to ($final_filename).table.tmp
1190
+ - When the writing finishes, it is renamed to ($final_filename).table
1191
+ - When all tables are finished, they are concatenated into the final file
1192
+ */
1193
+ // dbinfo is only used when whichdb != 'wp'; and the keys should be: user, pass, name, host, prefix
1194
+ public function backup_db($already_done = 'begun', $whichdb = 'wp', $dbinfo = array()) {
1195
+
1196
+ global $iwp_backup_core, $wpdb;
1197
+
1198
+ $this->whichdb = $whichdb;
1199
+ $this->whichdb_suffix = ('wp' == $whichdb) ? '' : $whichdb;
1200
+
1201
+ if (!$iwp_backup_core->backup_time) $iwp_backup_core->backup_time_nonce();
1202
+ if (!$iwp_backup_core->opened_log_time) $iwp_backup_core->logfile_open($iwp_backup_core->nonce);
1203
+
1204
+ if ('wp' == $this->whichdb) {
1205
+ $this->wpdb_obj = $wpdb;
1206
+ # The table prefix after being filtered - i.e. what filters what we'll actually back up
1207
+ $this->table_prefix = $iwp_backup_core->get_table_prefix(true);
1208
+ # The unfiltered table prefix - i.e. the real prefix that things are relative to
1209
+ $this->table_prefix_raw = $iwp_backup_core->get_table_prefix(false);
1210
+ $dbinfo['host'] = DB_HOST;
1211
+ $dbinfo['name'] = DB_NAME;
1212
+ $dbinfo['user'] = DB_USER;
1213
+ $dbinfo['pass'] = DB_PASSWORD;
1214
+ } else {
1215
+ if (!is_array($dbinfo) || empty($dbinfo['host'])) return false;
1216
+ # The methods that we may use: check_connection (WP>=3.9), get_results, get_row, query
1217
+ $this->wpdb_obj = new IWP_MMB_WPDB_OtherDB($dbinfo['user'], $dbinfo['pass'], $dbinfo['name'], $dbinfo['host']);
1218
+ if (!empty($this->wpdb_obj->error)) {
1219
+ $iwp_backup_core->log($dbinfo['user'].'@'.$dbinfo['host'].'/'.$dbinfo['name'].' : database connection attempt failed');
1220
+ $iwp_backup_core->log($dbinfo['user'].'@'.$dbinfo['host'].'/'.$dbinfo['name'].' : '.__('database connection attempt failed.', 'iwp_backup_core').' '.__('Connection failed: check your access details, that the database server is up, and that the network connection is not firewalled.', 'iwp_backup_core'), 'error');
1221
+ return $iwp_backup_core->log_wp_error($this->wpdb_obj->error);
1222
+ }
1223
+ $this->table_prefix = $dbinfo['prefix'];
1224
+ $this->table_prefix_raw = $dbinfo['prefix'];
1225
+ }
1226
+
1227
+ $this->dbinfo = $dbinfo;
1228
+
1229
+ $errors = 0;
1230
+
1231
+ $use_time = apply_filters('IWP_backup_time_thisrun', $iwp_backup_core->backup_time);
1232
+ $file_base = $this->get_backup_file_basename_from_time($use_time);
1233
+ $backup_file_base = $this->iwp_backup_dir.'/'.$file_base;
1234
+
1235
+ if ('finished' == $already_done) return basename($backup_file_base).'-db'.(('wp' == $whichdb) ? '' : $whichdb).'.gz';
1236
+ if ('encrypted' == $already_done) return basename($backup_file_base).'-db'.(('wp' == $whichdb) ? '' : $whichdb).'.gz.crypt';
1237
+
1238
+ $iwp_backup_core->jobdata_set('jobstatus', 'dbcreating'.$this->whichdb_suffix);
1239
+
1240
+ $binsqldump = $iwp_backup_core->find_working_sqldump();
1241
+
1242
+ $total_tables = 0;
1243
+
1244
+ # WP 3.9 onwards - https://core.trac.wordpress.org/browser/trunk/src/wp-includes/wp-db.php?rev=27925 - check_connection() allows us to get the database connection back if it had dropped
1245
+ if ('wp' == $whichdb && method_exists($this->wpdb_obj, 'check_connection') && (!defined('IWP_SUPPRESS_CONNECTION_CHECKS') || !IWP_SUPPRESS_CONNECTION_CHECKS)) {
1246
+ if (!$this->wpdb_obj->check_connection(false)) {
1247
+ $iwp_backup_core->reschedule(60);
1248
+ $iwp_backup_core->log("It seems the database went away; scheduling a resumption and terminating for now");
1249
+ $iwp_backup_core->record_still_alive();
1250
+ die;
1251
+ }
1252
+ }
1253
+
1254
+ // SHOW FULL - so that we get to know whether it's a BASE TABLE or a VIEW
1255
+ $all_tables = $this->wpdb_obj->get_results("SHOW FULL TABLES", ARRAY_N);
1256
+
1257
+ if (empty($all_tables) && !empty($this->wpdb_obj->last_error)) {
1258
+ $all_tables = $this->wpdb_obj->get_results("SHOW TABLES", ARRAY_N);
1259
+ $all_tables = array_map(array($this, 'cb_get_name_base_type'), $all_tables);
1260
+ } else {
1261
+ $all_tables = array_map(array($this, 'cb_get_name_type'), $all_tables);
1262
+ }
1263
+
1264
+ # If this is not the WP database, then we do not consider it a fatal error if there are no tables
1265
+ if ('wp' == $whichdb && 0 == count($all_tables)) {
1266
+ $extra = ($iwp_backup_core->newresumption_scheduled) ? ' - '.__('please wait for the rescheduled attempt', 'InfiniteWP') : '';
1267
+ $iwp_backup_core->log("Error: No WordPress database tables found (SHOW TABLES returned nothing)".$extra);
1268
+ $iwp_backup_core->log(__("No database tables found", 'InfiniteWP').$extra, 'error');
1269
+ die;
1270
+ }
1271
+
1272
+ // Put the options table first
1273
+ usort($all_tables, array($this, 'backup_db_sorttables'));
1274
+
1275
+ $all_table_names = array_map(array($this, 'cb_get_name'), $all_tables);
1276
+
1277
+ if (!$iwp_backup_core->really_is_writable($this->iwp_backup_dir)) {
1278
+ $iwp_backup_core->log("The backup directory (".$this->iwp_backup_dir.") could not be written to (could be account/disk space full, or wrong permissions).");
1279
+ $iwp_backup_core->log($this->iwp_backup_dir.": ".__('The backup directory is not writable (or disk space is full) - the database backup is expected to shortly fail.','InfiniteWP'), 'warning');
1280
+ # Why not just fail now? We saw a bizarre case when the results of really_is_writable() changed during the run.
1281
+ }
1282
+
1283
+ # This check doesn't strictly get all possible duplicates; it's only designed for the case that can happen when moving between deprecated Windows setups and Linux
1284
+ $this->duplicate_tables_exist = false;
1285
+ foreach ($all_table_names as $table) {
1286
+ if (strtolower($table) != $table && in_array(strtolower($table), $all_table_names)) {
1287
+ $this->duplicate_tables_exist = true;
1288
+ $iwp_backup_core->log("Tables with names differing only based on case-sensitivity exist in the MySQL database: $table / ".strtolower($table));
1289
+ }
1290
+ }
1291
+ $how_many_tables = count($all_tables);
1292
+
1293
+ $stitch_files = array();
1294
+ $found_options_table = false;
1295
+ $is_multisite = is_multisite();
1296
+
1297
+ foreach ($all_tables as $ti) {
1298
+
1299
+ $table = $ti['name'];
1300
+ $table_type = $ti['type'];
1301
+
1302
+ $manyrows_warning = false;
1303
+ $total_tables++;
1304
+
1305
+ // Increase script execution time-limit to 15 min for every table.
1306
+ @set_time_limit(IWP_SET_TIME_LIMIT);
1307
+ // The table file may already exist if we have produced it on a previous run
1308
+ $table_file_prefix = $file_base.'-db'.$this->whichdb_suffix.'-table-'.$table.'.table';
1309
+
1310
+ if ('wp' == $whichdb && (strtolower($this->table_prefix_raw.'options') == strtolower($table) || ($is_multisite && (strtolower($this->table_prefix_raw.'sitemeta') == strtolower($table) || strtolower($this->table_prefix_raw.'1_options') == strtolower($table))))) $found_options_table = true;
1311
+
1312
+ if (file_exists($this->iwp_backup_dir.'/'.$table_file_prefix.'.gz')) {
1313
+ $stitched = count($stitch_files);
1314
+ $skip_dblog = (($stitched > 10 && 0 != $stitched % 20) || ($stitched > 100 && 0 != $stitched % 100));
1315
+ $iwp_backup_core->log("Table $table: corresponding file already exists; moving on", 'notice', false, $skip_dblog);
1316
+ $stitch_files[] = $table_file_prefix;
1317
+ } else {
1318
+ # === is needed, otherwise 'false' matches (i.e. prefix does not match)
1319
+ if (empty($this->table_prefix) || ($this->duplicate_tables_exist == false && stripos($table, $this->table_prefix) === 0 ) || ($this->duplicate_tables_exist == true && strpos($table, $this->table_prefix) === 0 ) ) {
1320
+
1321
+ if (!apply_filters('IWP_backup_table', true, $table, $this->table_prefix, $whichdb, $dbinfo)) {
1322
+ $iwp_backup_core->log("Skipping table (filtered): $table");
1323
+ if (empty($this->skipped_tables)) $this->skipped_tables = array();
1324
+
1325
+ // whichdb could be an int in which case to get the name of the database and the array key use the name from dbinfo
1326
+ if ('wp' !== $whichdb) {
1327
+ $key = $dbinfo['name'];
1328
+ } else {
1329
+ $key = $whichdb;
1330
+ }
1331
+
1332
+ if (empty($this->skipped_tables[$key])) $this->skipped_tables[$key] = '';
1333
+ if ('' != $this->skipped_tables[$key]) $this->skipped_tables[$key] .= ',';
1334
+ $this->skipped_tables[$key] .= $table;
1335
+
1336
+ $total_tables--;
1337
+ } else {
1338
+
1339
+ $db_temp_file = $this->iwp_backup_dir.'/'.$table_file_prefix.'.tmp.gz';
1340
+ $iwp_backup_core->check_recent_modification($db_temp_file);
1341
+
1342
+ // Open file, store the handle
1343
+ $opened = $this->backup_db_open($db_temp_file, true);
1344
+ if (false === $opened) return false;
1345
+
1346
+ // Create the SQL statements
1347
+ $this->stow("# " . sprintf('Table: %s' ,$iwp_backup_core->backquote($table)) . "\n");
1348
+ $iwp_backup_core->jobdata_set('dbcreating_substatus', array('t' => $table, 'i' => $total_tables, 'a' => $how_many_tables));
1349
+
1350
+ $table_status = $this->wpdb_obj->get_row("SHOW TABLE STATUS WHERE Name='$table'");
1351
+ if (isset($table_status->Rows)) {
1352
+ $rows = $table_status->Rows;
1353
+ $iwp_backup_core->log("Table $table: Total expected rows (approximate): ".$rows);
1354
+ $this->stow("# Approximate rows expected in table: $rows\n");
1355
+ if ($rows > IWP_WARN_DB_ROWS) {
1356
+ $manyrows_warning = true;
1357
+ $iwp_backup_core->log(sprintf(__("Table %s has very many rows (%s) - we hope your web hosting company gives you enough resources to dump out that table in the backup", 'InfiniteWP'), $table, $rows).' '.__('If not, you will need to either remove data from this table, or contact your hosting company to request more resources.', 'InfiniteWP'), 'warning', 'manyrows_'.$this->whichdb_suffix.$table);
1358
+ }
1359
+ }
1360
+
1361
+ # Don't include the job data for any backups - so that when the database is restored, it doesn't continue an apparently incomplete backup
1362
+ if ('wp' == $this->whichdb && (!empty($this->table_prefix) && strtolower($this->table_prefix.'sitemeta') == strtolower($table))) {
1363
+ $where = 'meta_key NOT LIKE "IWP_jobdata_%"';
1364
+ } elseif ('wp' == $this->whichdb && (!empty($this->table_prefix) && strtolower($this->table_prefix.'options') == strtolower($table))) {
1365
+ if (strtolower(substr(PHP_OS, 0, 3)) == 'win') {
1366
+ $IWP_jobdata = "'IWP_jobdata_%'";
1367
+ $site_transient_update = "'_site_transient_update_%'";
1368
+ } else {
1369
+ $IWP_jobdata = '"IWP_jobdata_%"';
1370
+ $site_transient_update = '"_site_transient_update_%"';
1371
+ }
1372
+
1373
+ $where = 'option_name NOT LIKE '.$IWP_jobdata.' AND option_name NOT LIKE '.$site_transient_update.'';
1374
+ } else {
1375
+ $where = '';
1376
+ }
1377
+
1378
+ # If no check-in last time, then we could in future try the other method (but - any point in retrying slow method on large tables??)
1379
+
1380
+ # New Jul 2014: This attempt to use bindump instead at a lower threshold is quite conservative - only if the last successful run was exactly two resumptions ago - may be useful to expand
1381
+ $bindump_threshold = (!$iwp_backup_core->something_useful_happened && !empty($iwp_backup_core->current_resumption) && ($iwp_backup_core->current_resumption - $iwp_backup_core->last_successful_resumption == 2 )) ? 1000 : 8000;
1382
+
1383
+ $bindump = (isset($rows) && ($rows>$bindump_threshold || (defined('IWP_ALWAYS_TRY_MYSQLDUMP') && IWP_ALWAYS_TRY_MYSQLDUMP)) && is_string($binsqldump)) ? $this->backup_table_bindump($binsqldump, $table, $where) : false;
1384
+ if (true !== $bindump) $this->backup_table($table, $where, 'none', $table_type);
1385
+
1386
+ if (!empty($manyrows_warning)) $iwp_backup_core->log_removewarning('manyrows_'.$this->whichdb_suffix.$table);
1387
+
1388
+ $this->close();
1389
+
1390
+ $iwp_backup_core->log("Table $table: finishing file (${table_file_prefix}.gz - ".round(filesize($this->iwp_backup_dir.'/'.$table_file_prefix.'.tmp.gz')/1024,1)." KB)");
1391
+
1392
+ rename($db_temp_file, $this->iwp_backup_dir.'/'.$table_file_prefix.'.gz');
1393
+ $iwp_backup_core->something_useful_happened();
1394
+ $stitch_files[] = $table_file_prefix;
1395
+ }
1396
+ } else {
1397
+ $total_tables--;
1398
+ $iwp_backup_core->log("Skipping table (lacks our prefix (".$this->table_prefix.")): $table");
1399
+ }
1400
+
1401
+ }
1402
+ }
1403
+
1404
+ if ('wp' == $whichdb) {
1405
+ if (!$found_options_table) {
1406
+ if ($is_multisite) {
1407
+ $iwp_backup_core->log(__('The database backup appears to have failed', 'InfiniteWP').' - '.__('no options or sitemeta table was found', 'InfiniteWP'), 'warning', 'optstablenotfound');
1408
+ } else {
1409
+ $iwp_backup_core->log(__('The database backup appears to have failed', 'InfiniteWP').' - '.__('the options table was not found', 'InfiniteWP'), 'warning', 'optstablenotfound');
1410
+ }
1411
+ $time_this_run = time()-$IWP_backup->opened_log_time;
1412
+ if ($time_this_run > 2000) {
1413
+ # Have seen this happen; not sure how, but it was apparently deterministic; if the current process had been running for a long time, then apparently all database commands silently failed.
1414
+ # If we have been running that long, then the resumption may be far off; bring it closer
1415
+ $iwp_backup_core->reschedule(60);
1416
+ $iwp_backup_core->log("Have been running very long, and it seems the database went away; scheduling a resumption and terminating for now");
1417
+ $iwp_backup_core->record_still_alive();
1418
+ die;
1419
+ }
1420
+ } else {
1421
+ $iwp_backup_core->log_removewarning('optstablenotfound');
1422
+ }
1423
+ }
1424
+
1425
+ // Race detection - with zip files now being resumable, these can more easily occur, with two running side-by-side
1426
+ $backup_final_file_name = $backup_file_base.'-db'.$this->whichdb_suffix.'.gz';
1427
+ $time_now = time();
1428
+ $time_mod = (int)@filemtime($backup_final_file_name);
1429
+ if (file_exists($backup_final_file_name) && $time_mod>100 && ($time_now-$time_mod)<30) {
1430
+ $iwp_backup_core->terminate_due_to_activity($backup_final_file_name, $time_now, $time_mod);
1431
+ }
1432
+ if (file_exists($backup_final_file_name)) {
1433
+ $iwp_backup_core->log("The final database file ($backup_final_file_name) exists, but was apparently not modified within the last 30 seconds (time_mod=$time_mod, time_now=$time_now, diff=".($time_now-$time_mod)."). Thus we assume that another Backup terminated; thus we will continue.");
1434
+ }
1435
+
1436
+ // Finally, stitch the files together
1437
+ if (!function_exists('gzopen')) {
1438
+ if (function_exists('gzopen64')) {
1439
+ $iwp_backup_core->log("PHP function is disabled; abort expected: gzopen - buggy Ubuntu PHP version; try this plugin to help: https://wordpress.org/plugins/wp-ubuntu-gzopen-fix/");
1440
+ } else {
1441
+ $iwp_backup_core->log("PHP function is disabled; abort expected: gzopen");
1442
+ }
1443
+ }
1444
+
1445
+ if (false === $this->backup_db_open($backup_final_file_name, true)) return false;
1446
+
1447
+ $this->backup_db_header();
1448
+
1449
+ // We delay the unlinking because if two runs go concurrently and fail to detect each other (should not happen, but there's no harm in assuming the detection failed) then that leads to files missing from the db dump
1450
+ $unlink_files = array();
1451
+
1452
+ $sind = 1;
1453
+ foreach ($stitch_files as $table_file) {
1454
+ $iwp_backup_core->log("{$table_file}.gz ($sind/$how_many_tables): adding to final database dump");
1455
+ if (!$handle = gzopen($this->iwp_backup_dir.'/'.$table_file.'.gz', "r")) {
1456
+ $iwp_backup_core->log("Error: Failed to open database file for reading: ${table_file}.gz");
1457
+ $iwp_backup_core->log(__("Failed to open database file for reading:", 'InfiniteWP').' '.$table_file.'.gz', 'error');
1458
+ $errors++;
1459
+ } else {
1460
+ while ($line = gzgets($handle, 65536)) { $this->stow($line); }
1461
+ gzclose($handle);
1462
+ $unlink_files[] = $this->iwp_backup_dir.'/'.$table_file.'.gz';
1463
+ }
1464
+ $sind++;
1465
+ // Came across a database with 7600 tables... adding them all took over 500 seconds; and so when the resumption started up, no activity was detected
1466
+ if ($sind % 100 == 0) $iwp_backup_core->something_useful_happened();
1467
+ }
1468
+
1469
+ if (@constant("DB_CHARSET")) {
1470
+ $this->stow("/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n");
1471
+ }
1472
+
1473
+ $iwp_backup_core->log($file_base.'-db'.$this->whichdb_suffix.'.gz: finished writing out complete database file ('.round(filesize($backup_final_file_name)/1024,1).' KB)');
1474
+ if (!$this->close()) {
1475
+ $iwp_backup_core->log('An error occurred whilst closing the final database file');
1476
+ $iwp_backup_core->log(__('An error occurred whilst closing the final database file', 'InfiniteWP'), 'error');
1477
+ $errors++;
1478
+ }
1479
+
1480
+ foreach ($unlink_files as $unlink_file) @unlink($unlink_file);
1481
+
1482
+ if ($errors > 0) {
1483
+ return false;
1484
+ } else {
1485
+ # We no longer encrypt here - because the operation can take long, we made it resumable and moved it to the upload loop
1486
+ $iwp_backup_core->jobdata_set('jobstatus', 'dbcreated'.$this->whichdb_suffix);
1487
+
1488
+ $checksums = $iwp_backup_core->which_checksums();
1489
+
1490
+ $checksum_description = '';
1491
+
1492
+ foreach ($checksums as $checksum) {
1493
+
1494
+ $cksum = hash_file($checksum, $backup_final_file_name);
1495
+ $iwp_backup_core->jobdata_set($checksum.'-db'.(('wp' == $whichdb) ? '0' : $whichdb.'0'), $cksum);
1496
+ if ($checksum_description) $checksum_description .= ', ';
1497
+ $checksum_description .= "$checksum: $cksum";
1498
+
1499
+ }
1500
+
1501
+ $iwp_backup_core->log("Total database tables backed up: $total_tables (".basename($backup_final_file_name).", size: ".filesize($backup_final_file_name).", $checksum)");
1502
+
1503
+ return basename($backup_final_file_name);
1504
+ }
1505
+
1506
+ }
1507
+
1508
+ private function backup_table_bindump($potsql, $table_name, $where) {
1509
+
1510
+ $microtime = microtime(true);
1511
+
1512
+ global $iwp_backup_core;
1513
+
1514
+ // Deal with Windows/old MySQL setups with erroneous table prefixes differing in case
1515
+ // Can't get binary mysqldump to make this transformation
1516
+ // $dump_as_table = ($this->duplicate_tables_exist == false && stripos($table, $this->table_prefix) === 0 && strpos($table, $this->table_prefix) !== 0) ? $this->table_prefix.substr($table, strlen($this->table_prefix)) : $table;
1517
+
1518
+ $pfile = md5(time().rand()).'.tmp';
1519
+ file_put_contents($this->iwp_backup_dir.'/'.$pfile, "[mysqldump]\npassword=".$this->dbinfo['pass']."\n");
1520
+
1521
+ // Note: escapeshellarg() adds quotes around the string
1522
+ if ($where) $where="--where=".escapeshellarg($where);
1523
+
1524
+ if (strtolower(substr(PHP_OS, 0, 3)) == 'win') {
1525
+ $exec = "cd ".escapeshellarg(str_replace('/', '\\', $this->iwp_backup_dir))." & ";
1526
+ } else {
1527
+ $exec = "cd ".escapeshellarg($this->iwp_backup_dir)."; ";
1528
+ }
1529
+
1530
+ $exec .= "$potsql --defaults-file=$pfile $where --max_allowed_packet=1M --quote-names --add-drop-table --skip-comments --skip-set-charset --allow-keywords --dump-date --extended-insert --user=".escapeshellarg($this->dbinfo['user'])." --host=".escapeshellarg($this->dbinfo['host'])." ".$this->dbinfo['name']." ".escapeshellarg($table_name);
1531
+
1532
+ $ret = false;
1533
+ $any_output = false;
1534
+ $writes = 0;
1535
+ $handle = popen($exec, "r");
1536
+ if ($handle) {
1537
+ while (!feof($handle)) {
1538
+ $w = fgets($handle);
1539
+ if ($w) {
1540
+ $this->stow($w);
1541
+ $writes++;
1542
+ $any_output = true;
1543
+ }
1544
+ }
1545
+ $ret = pclose($handle);
1546
+ if ($ret != 0) {
1547
+ $iwp_backup_core->log("Binary mysqldump: error (code: $ret)");
1548
+ // Keep counter of failures? Change value of binsqldump?
1549
+ } else {
1550
+ if ($any_output) {
1551
+ $iwp_backup_core->log("Table $table_name: binary mysqldump finished (writes: $writes) in ".sprintf("%.02f",max(microtime(true)-$microtime,0.00001))." seconds");
1552
+ $ret = true;
1553
+ }
1554
+ }
1555
+ } else {
1556
+ $iwp_backup_core->log("Binary mysqldump error: bindump popen failed");
1557
+ }
1558
+
1559
+ # Clean temporary files
1560
+ @unlink($this->iwp_backup_dir.'/'.$pfile);
1561
+
1562
+ return $ret;
1563
+
1564
+ }
1565
+
1566
+ /**
1567
+ * Taken partially from phpMyAdmin and partially from
1568
+ * Alain Wolf, Zurich - Switzerland
1569
+ * Website: http://restkultur.ch/personal/wolf/scripts/db_backup/
1570
+ * Modified by Scott Merrill (http://www.skippy.net/)
1571
+ * to use the WordPress $wpdb object
1572
+ * @param string $table
1573
+ * @param string $segment
1574
+ * @return void
1575
+ */
1576
+ private function backup_table($table, $where = '', $segment = 'none', $table_type = 'BASE TABLE') {
1577
+ global $iwp_backup_core;
1578
+
1579
+ $microtime = microtime(true);
1580
+ $total_rows = 0;
1581
+
1582
+ // Deal with Windows/old MySQL setups with erroneous table prefixes differing in case
1583
+ $dump_as_table = ($this->duplicate_tables_exist == false && stripos($table, $this->table_prefix) === 0 && strpos($table, $this->table_prefix) !== 0) ? $this->table_prefix.substr($table, strlen($this->table_prefix)) : $table;
1584
+
1585
+ $table_structure = $this->wpdb_obj->get_results("DESCRIBE ".$iwp_backup_core->backquote($table));
1586
+ if (! $table_structure) {
1587
+ return false;
1588
+ }
1589
+
1590
+ if($segment == 'none' || $segment == 0) {
1591
+ // Add SQL statement to drop existing table
1592
+ $this->stow("\n# Delete any existing table ".$iwp_backup_core->backquote($table)."\n\n");
1593
+ $this->stow("DROP TABLE IF EXISTS " . $iwp_backup_core->backquote($dump_as_table) . ";\n");
1594
+
1595
+ if ('VIEW' == $table_type) {
1596
+ $this->stow("DROP VIEW IF EXISTS " . $iwp_backup_core->backquote($dump_as_table) . ";\n");
1597
+ }
1598
+
1599
+ // Table structure
1600
+ // Comment in SQL-file
1601
+
1602
+ $description = ('VIEW' == $table_type) ? 'view' : 'table';
1603
+
1604
+ $this->stow("\n# Table structure of $description ".$iwp_backup_core->backquote($table)."\n\n");
1605
+
1606
+ $create_table = $this->wpdb_obj->get_results("SHOW CREATE TABLE ".$iwp_backup_core->backquote($table), ARRAY_N);
1607
+ if (false === $create_table) {
1608
+ $err_msg ='Error with SHOW CREATE TABLE for '.$table;
1609
+ //$iwp_backup_core->log($err_msg, 'error');
1610
+ $this->stow("#\n# $err_msg\n#\n");
1611
+ }
1612
+ $create_line = $iwp_backup_core->str_lreplace('TYPE=', 'ENGINE=', $create_table[0][1]);
1613
+
1614
+ # Remove PAGE_CHECKSUM parameter from MyISAM - was internal, undocumented, later removed (so causes errors on import)
1615
+ if (preg_match('/ENGINE=([^\s;]+)/', $create_line, $eng_match)) {
1616
+ $engine = $eng_match[1];
1617
+ if ('myisam' == strtolower($engine)) {
1618
+ $create_line = preg_replace('/PAGE_CHECKSUM=\d\s?/', '', $create_line, 1);
1619
+ }
1620
+ }
1621
+
1622
+ if ($dump_as_table !== $table) $create_line = $iwp_backup_core->str_replace_once($table, $dump_as_table, $create_line);
1623
+
1624
+ $this->stow($create_line.' ;');
1625
+
1626
+ if (false === $table_structure) {
1627
+ $err_msg = sprintf("Error getting $description structure of %s", $table);
1628
+ $this->stow("#\n# $err_msg\n#\n");
1629
+ }
1630
+
1631
+ // Comment in SQL-file
1632
+ $this->stow("\n\n# " . sprintf("Data contents of $description %s", $iwp_backup_core->backquote($table)) . "\n\n");
1633
+
1634
+ }
1635
+
1636
+ # Some tables have optional data, and should be skipped if they do not work
1637
+ $table_sans_prefix = substr($table, strlen($this->table_prefix_raw));
1638
+ $data_optional_tables = ('wp' == $this->whichdb) ? apply_filters('IWP_data_optional_tables', explode(',', IWP_DATA_OPTIONAL_TABLES)) : array();
1639
+ if (in_array($table_sans_prefix, $data_optional_tables)) {
1640
+ if (!$iwp_backup_core->something_useful_happened && !empty($iwp_backup_core->current_resumption) && ($iwp_backup_core->current_resumption - $iwp_backup_core->last_successful_resumption > 2)) {
1641
+ $iwp_backup_core->log("Table $table: Data skipped (previous attempts failed, and table is marked as non-essential)");
1642
+ return true;
1643
+ }
1644
+ }
1645
+
1646
+ if('VIEW' != $table_type && ($segment == 'none' || $segment >= 0)) {
1647
+ $defs = array();
1648
+ $integer_fields = array();
1649
+ // $table_structure was from "DESCRIBE $table"
1650
+ foreach ($table_structure as $struct) {
1651
+ if ( (0 === strpos($struct->Type, 'tinyint')) || (0 === strpos(strtolower($struct->Type), 'smallint')) ||
1652
+ (0 === strpos(strtolower($struct->Type), 'mediumint')) || (0 === strpos(strtolower($struct->Type), 'int')) || (0 === strpos(strtolower($struct->Type), 'bigint')) ) {
1653
+ $defs[strtolower($struct->Field)] = ( null === $struct->Default ) ? 'NULL' : $struct->Default;
1654
+ $integer_fields[strtolower($struct->Field)] = "1";
1655
+ }
1656
+ }
1657
+
1658
+ // Experimentation here shows that on large tables (we tested with 180,000 rows) on MyISAM, 1000 makes the table dump out 3x faster than the previous value of 100. After that, the benefit diminishes (increasing to 4000 only saved another 12%)
1659
+
1660
+ $increment = 1000;
1661
+ if (!$iwp_backup_core->something_useful_happened && !empty($iwp_backup_core->current_resumption) && ($iwp_backup_core->current_resumption - $iwp_backup_core->last_successful_resumption > 1)) {
1662
+ # This used to be fixed at 500; but we (after a long time) saw a case that looked like an out-of-memory even at this level. We must be careful about going too low, though - otherwise we increase the risks of timeouts.
1663
+ $increment = ( $iwp_backup_core->current_resumption - $iwp_backup_core->last_successful_resumption > 2 ) ? 350 : 500;
1664
+ }
1665
+
1666
+ if($segment == 'none') {
1667
+ $row_start = 0;
1668
+ $row_inc = $increment;
1669
+ } else {
1670
+ $row_start = $segment * $increment;
1671
+ $row_inc = $increment;
1672
+ }
1673
+
1674
+ $search = array("\x00", "\x0a", "\x0d", "\x1a");
1675
+ $replace = array('\0', '\n', '\r', '\Z');
1676
+
1677
+ if ($where) $where = "WHERE $where";
1678
+
1679
+ do {
1680
+ @set_time_limit(IWP_SET_TIME_LIMIT);
1681
+
1682
+ $table_data = $this->wpdb_obj->get_results("SELECT * FROM ".$iwp_backup_core->backquote($table)." $where LIMIT {$row_start}, {$row_inc}", ARRAY_A);
1683
+ $entries = 'INSERT INTO ' . $iwp_backup_core->backquote($dump_as_table) . ' VALUES ';
1684
+ // \x08\\x09, not required
1685
+ if($table_data) {
1686
+ $thisentry = "";
1687
+ foreach ($table_data as $row) {
1688
+ $total_rows++;
1689
+ $values = array();
1690
+ foreach ($row as $key => $value) {
1691
+ if (isset($integer_fields[strtolower($key)])) {
1692
+ // make sure there are no blank spots in the insert syntax,
1693
+ // yet try to avoid quotation marks around integers
1694
+ $value = ( null === $value || '' === $value) ? $defs[strtolower($key)] : $value;
1695
+ $values[] = ( '' === $value ) ? "''" : $value;
1696
+ } else {
1697
+ $values[] = (null === $value) ? 'NULL' : "'" . str_replace($search, $replace, str_replace('\'', '\\\'', str_replace('\\', '\\\\', $value))) . "'";
1698
+ }
1699
+ }
1700
+ if ($thisentry) $thisentry .= ", ";
1701
+ $thisentry .= '('.implode(', ', $values).')';
1702
+ // Flush every 512KB
1703
+ if (strlen($thisentry) > 524288) {
1704
+ $this->stow(" \n".$entries.$thisentry.';');
1705
+ $thisentry = "";
1706
+ }
1707
+
1708
+ }
1709
+ if ($thisentry) $this->stow(" \n".$entries.$thisentry.';');
1710
+ $row_start += $row_inc;
1711
+ }
1712
+ } while(count($table_data) > 0 && 'none' == $segment);
1713
+ }
1714
+
1715
+ if(($segment == 'none') || ($segment < 0)) {
1716
+ // Create footer/closing comment in SQL-file
1717
+ $this->stow("\n");
1718
+ $this->stow("# End of data contents of table ".$iwp_backup_core->backquote($table) . "\n");
1719
+ $this->stow("\n");
1720
+ }
1721
+ $iwp_backup_core->log("Table $table: Total rows added: $total_rows in ".sprintf("%.02f",max(microtime(true)-$microtime,0.00001))." seconds");
1722
+
1723
+ } // end backup_table()
1724
+
1725
+
1726
+ /*END OF WP-DB-BACKUP BLOCK */
1727
+
1728
+ // Encrypts the file if the option is set; returns the basename of the file (according to whether it was encrypted or nto)
1729
+ public function encrypt_file($file) {
1730
+ global $iwp_backup_core;
1731
+ $encryption = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_encryptionphrase');
1732
+ if (strlen($encryption) > 0) {
1733
+ $iwp_backup_core->log("Attempting to encrypt backup file");
1734
+ $result = apply_filters('IWP_encrypt_file', null, $file, $encryption, $this->whichdb, $this->whichdb_suffix);
1735
+ if (null === $result) {
1736
+ return basename($file);
1737
+ }
1738
+ return $result;
1739
+ } else {
1740
+ return basename($file);
1741
+ }
1742
+ }
1743
+
1744
+ public function close() {
1745
+ return ($this->dbhandle_isgz) ? gzclose($this->dbhandle) : fclose($this->dbhandle);
1746
+ }
1747
+
1748
+ // Open a file, store its filehandle
1749
+ public function backup_db_open($file, $allow_gz = true) {
1750
+ if (function_exists('gzopen') && $allow_gz == true) {
1751
+ $this->dbhandle = @gzopen($file, 'w');
1752
+ $this->dbhandle_isgz = true;
1753
+ } else {
1754
+ $this->dbhandle = @fopen($file, 'w');
1755
+ $this->dbhandle_isgz = false;
1756
+ }
1757
+ if(false === $this->dbhandle) {
1758
+ global $iwp_backup_core;
1759
+ $iwp_backup_core->log("ERROR: $file: Could not open the backup file for writing");
1760
+ $iwp_backup_core->log($file.": ".__("Could not open the backup file for writing",'InfiniteWP'), 'error');
1761
+ }
1762
+ return $this->dbhandle;
1763
+ }
1764
+
1765
+ public function stow($query_line) {
1766
+ if ($this->dbhandle_isgz) {
1767
+ if(false == ($ret = @gzwrite($this->dbhandle, $query_line))) {
1768
+ //$iwp_backup_core->log(__('There was an error writing a line to the backup script:','wp-db-backup') . ' ' . $query_line . ' ' . $php_errormsg, 'error');
1769
+ }
1770
+ } else {
1771
+ if(false == ($ret = @fwrite($this->dbhandle, $query_line))) {
1772
+ //$iwp_backup_core->log(__('There was an error writing a line to the backup script:','wp-db-backup') . ' ' . $query_line . ' ' . $php_errormsg, 'error');
1773
+ }
1774
+ }
1775
+ return $ret;
1776
+ }
1777
+
1778
+ private function backup_db_header() {
1779
+
1780
+ @include(ABSPATH.WPINC.'/version.php');
1781
+ global $wp_version, $iwp_backup_core;
1782
+
1783
+ $mysql_version = $this->wpdb_obj->db_version();
1784
+ # (function_exists('mysql_get_server_info')) ? @mysql_get_server_info() : '?';
1785
+
1786
+ if ('wp' == $this->whichdb) {
1787
+ $wp_upload_dir = wp_upload_dir();
1788
+ $this->stow("# WordPress MySQL database backup\n");
1789
+ $this->stow("# Created by InfiniteWP version ".$iwp_backup_core->version."\n");
1790
+ $this->stow("# WordPress Version: $wp_version, running on PHP ".phpversion()." (".$_SERVER["SERVER_SOFTWARE"]."), MySQL $mysql_version\n");
1791
+ $this->stow("# Backup of: ".untrailingslashit(site_url())."\n");
1792
+ $this->stow("# Home URL: ".untrailingslashit(home_url())."\n");
1793
+ $this->stow("# Content URL: ".untrailingslashit(content_url())."\n");
1794
+ $this->stow("# Uploads URL: ".untrailingslashit($wp_upload_dir['baseurl'])."\n");
1795
+ $this->stow("# Table prefix: ".$this->table_prefix_raw."\n");
1796
+ $this->stow("# Filtered table prefix: ".$this->table_prefix."\n");
1797
+ $this->stow("# Site info: multisite=".(is_multisite() ? '1' : '0')."\n");
1798
+ $this->stow("# Site info: end\n");
1799
+ } else {
1800
+ $this->stow("# MySQL database backup (supplementary database ".$this->whichdb.")\n");
1801
+ $this->stow("# Created by InfiniteWP version ".$iwp_backup_core->version."\n");
1802
+ $this->stow("# WordPress Version: $wp_version, running on PHP ".phpversion()." (".$_SERVER["SERVER_SOFTWARE"]."), MySQL $mysql_version\n");
1803
+ $this->stow("# ".sprintf('External database: (%s)', $this->dbinfo['user'].'@'.$this->dbinfo['host'].'/'.$this->dbinfo['name'])."\n");
1804
+ $this->stow("# Backup created by: ".untrailingslashit(site_url())."\n");
1805
+ $this->stow("# Table prefix: ".$this->table_prefix_raw."\n");
1806
+ $this->stow("# Filtered table prefix: ".$this->table_prefix."\n");
1807
+ }
1808
+
1809
+ $label = $iwp_backup_core->jobdata_get('label');
1810
+ if (!empty($label)) $this->stow("# Label: $label\n");
1811
+
1812
+ $this->stow("#\n");
1813
+ $this->stow("# Generated: ".date("l j. F Y H:i T")."\n");
1814
+ $this->stow("# Hostname: ".$this->dbinfo['host']."\n");
1815
+ $this->stow("# Database: ".$iwp_backup_core->backquote($this->dbinfo['name'])."\n");
1816
+
1817
+ if (!empty($this->skipped_tables)) {
1818
+ if ('wp' == $this->whichdb) {
1819
+ $this->stow("# Skipped tables: " . $this->skipped_tables[$this->whichdb]."\n");
1820
+ } elseif (isset($this->skipped_tables[$this->dbinfo['name']])) {
1821
+ $this->stow("# Skipped tables: " . $this->skipped_tables[$this->dbinfo['name']]."\n");
1822
+ }
1823
+ }
1824
+
1825
+ $this->stow("# --------------------------------------------------------\n");
1826
+
1827
+ if (@constant("DB_CHARSET")) {
1828
+ $this->stow("/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n");
1829
+ $this->stow("/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n");
1830
+ $this->stow("/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n");
1831
+ $this->stow("/*!40101 SET NAMES " . DB_CHARSET . " */;\n");
1832
+ }
1833
+ $this->stow("/*!40101 SET foreign_key_checks = 0 */;\n\n");
1834
+ }
1835
+
1836
+ private function makezip_recursive_add($fullpath, $use_path_when_storing, $original_fullpath, $startlevels = 1, &$exclude) {
1837
+
1838
+ // $zipfile = $this->zip_basename.(($this->index == 0) ? '' : ($this->index+1)).'.zip.tmp';
1839
+
1840
+ global $iwp_backup_core;
1841
+
1842
+ // Only BinZip supports symlinks. This means that as a consistent outcome, the only think that can be done with directory symlinks is either a) potentially duplicate the data or b) skip it. Whilst with internal WP entities (e.g. plugins) we definitely want the data, in the case of user-selected directories, we assume the user knew what they were doing when they chose the directory - i.e. we can skip symlink-accessed data that's outside.
1843
+ if (is_link($fullpath) && is_dir($fullpath) && 'more' == $this->whichone) {
1844
+ $iwp_backup_core->log("Directory symlink encounted in more files backup: $use_path_when_storing -> ".readlink($fullpath).": skipping");
1845
+ return true;
1846
+ }
1847
+
1848
+ // De-reference. Important to do to both, because on Windows only doing it to one can make them non-equal, where they were previously equal - something which we later rely upon
1849
+ $fullpath = realpath($fullpath);
1850
+ $original_fullpath = realpath($original_fullpath);
1851
+
1852
+ // Is the place we've ended up above the original base? That leads to infinite recursion
1853
+ if (($fullpath !== $original_fullpath && strpos($original_fullpath, $fullpath) === 0) || ($original_fullpath == $fullpath && ((1== $startlevels && strpos($use_path_when_storing, '/') !== false) || (2 == $startlevels && substr_count($use_path_when_storing, '/') >1)))) {
1854
+ $iwp_backup_core->log("Infinite recursion: symlink led us to $fullpath, which is within $original_fullpath");
1855
+ $iwp_backup_core->log(__("Infinite recursion: consult your log for more information",'InfiniteWP'), 'error');
1856
+ return false;
1857
+ }
1858
+
1859
+ # This is sufficient for the ones we have exclude options for - uploads, others, wpcore
1860
+ $stripped_storage_path = (1 == $startlevels) ? $use_path_when_storing : substr($use_path_when_storing, strpos($use_path_when_storing, '/') + 1);
1861
+ if (false !== ($fkey = array_search($stripped_storage_path, $exclude))) {
1862
+ $iwp_backup_core->log("Entity excluded by configuration option: $stripped_storage_path");
1863
+ unset($exclude[$fkey]);
1864
+ return true;
1865
+ }
1866
+
1867
+ $if_altered_since = $this->makezip_if_altered_since;
1868
+
1869
+ if (is_file($fullpath)) {
1870
+ if (!empty($this->excluded_extensions) && $this->is_entity_excluded_by_extension($fullpath)) {
1871
+ $iwp_backup_core->log("Entity excluded by configuration option (extension): ".basename($fullpath));
1872
+ } elseif (!empty($this->excluded_prefixes) && $this->is_entity_excluded_by_prefix($fullpath)) {
1873
+ $iwp_backup_core->log("Entity excluded by configuration option (prefix): ".basename($fullpath));
1874
+ } elseif (apply_filters('IWP_exclude_file', false, $fullpath)) {
1875
+ $iwp_backup_core->log("Entity excluded by filter: ".basename($fullpath));
1876
+ } elseif (is_readable($fullpath)) {
1877
+ $mtime = filemtime($fullpath);
1878
+ $key = ($fullpath == $original_fullpath) ? ((2 == $startlevels) ? $use_path_when_storing : $this->basename($fullpath)) : $use_path_when_storing.'/'.$this->basename($fullpath);
1879
+ if ($mtime > 0 && $mtime > $if_altered_since) {
1880
+ $this->zipfiles_batched[$fullpath] = $key;
1881
+ $this->makezip_recursive_batchedbytes += @filesize($fullpath);
1882
+ #@touch($zipfile);
1883
+ } else {
1884
+ $this->zipfiles_skipped_notaltered[$fullpath] = $key;
1885
+ }
1886
+ } else {
1887
+ $iwp_backup_core->log("$fullpath: unreadable file");
1888
+ $iwp_backup_core->log(sprintf(__("%s: unreadable file - could not be backed up (check the file permissions and ownership)", 'iwp_backup_core'), $fullpath), 'warning');
1889
+ }
1890
+ } elseif (is_dir($fullpath)) {
1891
+ if ($fullpath == $this->iwp_backup_dir_realpath) {
1892
+ $iwp_backup_core->log("Skip directory (InfiniteWP backup directory): $use_path_when_storing");
1893
+ return true;
1894
+ }
1895
+
1896
+ if (apply_filters('IWP_exclude_directory', false, $fullpath)) {
1897
+ $iwp_backup_core->log("Skip filtered directory: $use_path_when_storing");
1898
+ return true;
1899
+ }
1900
+
1901
+ if (file_exists($fullpath.'/.donotbackup')) {
1902
+ $iwp_backup_core->log("Skip directory (.donotbackup file found): $use_path_when_storing");
1903
+ return true;
1904
+ }
1905
+
1906
+ if (!isset($this->existing_files[$use_path_when_storing])) $this->zipfiles_dirbatched[] = $use_path_when_storing;
1907
+
1908
+ if (!$dir_handle = @opendir($fullpath)) {
1909
+ $iwp_backup_core->log("Failed to open directory: $fullpath");
1910
+ $iwp_backup_core->log(sprintf(__("Failed to open directory (check the file permissions and ownership): %s",'iwp_backup_dir'), $fullpath), 'error');
1911
+ return false;
1912
+ }
1913
+
1914
+ while (false !== ($e = readdir($dir_handle))) {
1915
+ if ('.' == $e || '..' == $e) continue;
1916
+ // if ($this->whichone == 'more') {
1917
+ // if (!in_array(str_replace(ABSPATH, '', $e), explode(',',IWP_DEFAULT_INCLUDES))) {
1918
+ // continue;
1919
+ // }
1920
+ // }
1921
+
1922
+ if (is_link($fullpath.'/'.$e)) {
1923
+ $deref = realpath($fullpath.'/'.$e);
1924
+ if (is_file($deref)) {
1925
+ if (is_readable($deref)) {
1926
+ $use_stripped = $stripped_storage_path.'/'.$e;
1927
+ if (false !== ($fkey = array_search($use_stripped, $exclude))) {
1928
+ $iwp_backup_core->log("Entity excluded by configuration option: $use_stripped");
1929
+ unset($exclude[$fkey]);
1930
+ } elseif (!empty($this->excluded_extensions) && $this->is_entity_excluded_by_extension($e)) {
1931
+ $iwp_backup_core->log("Entity excluded by configuration option (extension): $use_stripped");
1932
+ } elseif (!empty($this->excluded_prefixes) && $this->is_entity_excluded_by_prefix($e)) {
1933
+ $iwp_backup_core->log("Entity excluded by configuration option (prefix): $use_stripped");
1934
+ } elseif (apply_filters('IWP_exclude_file', false, $deref)) {
1935
+ $iwp_backup_core->log("Entity excluded by filter: $use_stripped");
1936
+ } else {
1937
+ $mtime = filemtime($deref);
1938
+ if ($mtime > 0 && $mtime > $if_altered_since) {
1939
+ $this->zipfiles_batched[$deref] = $use_path_when_storing.'/'.$e;
1940
+ $this->makezip_recursive_batchedbytes += @filesize($deref);
1941
+ #@touch($zipfile);
1942
+ } else {
1943
+ $this->zipfiles_skipped_notaltered[$deref] = $use_path_when_storing.'/'.$e;
1944
+ }
1945
+ }
1946
+ } else {
1947
+ $iwp_backup_core->log("$deref: unreadable file");
1948
+ $iwp_backup_core->log(sprintf(__("%s: unreadable file - could not be backed up"), $deref), 'warning');
1949
+ }
1950
+ } elseif (is_dir($deref)) {
1951
+
1952
+ // $link_target = readlink($deref);
1953
+
1954
+ $this->makezip_recursive_add($deref, $use_path_when_storing.'/'.$e, $original_fullpath, $startlevels, $exclude);
1955
+ }
1956
+ } elseif (is_file($fullpath.'/'.$e)) {
1957
+ if (is_readable($fullpath.'/'.$e)) {
1958
+ $use_stripped = $stripped_storage_path.'/'.$e;
1959
+ if (false !== ($fkey = array_search($use_stripped, $exclude))) {
1960
+ $iwp_backup_core->log("Entity excluded by configuration option: $use_stripped");
1961
+ unset($exclude[$fkey]);
1962
+ } elseif (!empty($this->excluded_extensions) && $this->is_entity_excluded_by_extension($e)) {
1963
+ $iwp_backup_core->log("Entity excluded by configuration option (extension): $use_stripped");
1964
+ } elseif (!empty($this->excluded_prefixes) && $this->is_entity_excluded_by_prefix($e)) {
1965
+ $iwp_backup_core->log("Entity excluded by configuration option (prefix): $use_stripped");
1966
+ } elseif (apply_filters('IWP_exclude_file', false, $fullpath.'/'.$e)) {
1967
+ $iwp_backup_core->log("Entity excluded by filter: $use_stripped");
1968
+ } else {
1969
+ $mtime = filemtime($fullpath.'/'.$e);
1970
+ if ($mtime > 0 && $mtime > $if_altered_since) {
1971
+ $this->zipfiles_batched[$fullpath.'/'.$e] = $use_path_when_storing.'/'.$e;
1972
+ $this->makezip_recursive_batchedbytes += @filesize($fullpath.'/'.$e);
1973
+ } else {
1974
+ $this->zipfiles_skipped_notaltered[$fullpath.'/'.$e] = $use_path_when_storing.'/'.$e;
1975
+ }
1976
+ }
1977
+ } else {
1978
+ $iwp_backup_core->log("$fullpath/$e: unreadable file");
1979
+ $iwp_backup_core->log(sprintf(__("%s: unreadable file - could not be backed up", 'iwp_backup_dir'), $use_path_when_storing.'/'.$e), 'warning', "unrfile-$e");
1980
+ }
1981
+ } elseif (is_dir($fullpath.'/'.$e)) {
1982
+ if ('wpcore' == $this->whichone && 'updraft' == $e && basename($use_path_when_storing) == 'wp-content' && (!defined('IWP_WPCORE_INCLUDE_IWP_DIRS') || !IWP_WPCORE_INCLUDE_IWP_DIRS)) {
1983
+ // This test, of course, won't catch everything - it just aims to make things better by default
1984
+ $iwp_backup_core->log("Directory excluded for looking like a sub-site's internal IWP directory (enable by defining IWP_WPCORE_INCLUDE_IWP_DIRS): ".$use_path_when_storing.'/'.$e);
1985
+ } else {
1986
+ // no need to addEmptyDir here, as it gets done when we recurse
1987
+ $this->makezip_recursive_add($fullpath.'/'.$e, $use_path_when_storing.'/'.$e, $original_fullpath, $startlevels, $exclude);
1988
+ }
1989
+ }
1990
+ }
1991
+ closedir($dir_handle);
1992
+ } else {
1993
+ $iwp_backup_core->log("Unexpected: path ($use_path_when_storing) fails both is_file() and is_dir()");
1994
+ }
1995
+
1996
+ return true;
1997
+
1998
+ }
1999
+
2000
+ private function get_excluded_extensions($exclude) {
2001
+ if (!is_array($exclude)) $exclude = array();
2002
+ $exclude_extensions = array();
2003
+ foreach ($exclude as $ex) {
2004
+ if (preg_match('/^ext:(.+)$/i', $ex, $matches)) {
2005
+ $exclude_extensions[] = strtolower($matches[1]);
2006
+ }
2007
+ }
2008
+
2009
+ if (defined('IWP_EXCLUDE_EXTENSIONS')) {
2010
+ $exclude_from_define = explode(',', IWP_EXCLUDE_EXTENSIONS);
2011
+ foreach ($exclude_from_define as $ex) {
2012
+ $exclude_extensions[] = strtolower(trim($ex));
2013
+ }
2014
+ }
2015
+
2016
+ return $exclude_extensions;
2017
+ }
2018
+
2019
+ private function get_excluded_prefixes($exclude) {
2020
+ if (!is_array($exclude)) $exclude = array();
2021
+ $exclude_prefixes = array();
2022
+ foreach ($exclude as $pref) {
2023
+ if (preg_match('/^prefix:(.+)$/i', $pref, $matches)) {
2024
+ $exclude_prefixes[] = strtolower($matches[1]);
2025
+ }
2026
+ }
2027
+
2028
+ return $exclude_prefixes;
2029
+ }
2030
+
2031
+ private function is_entity_excluded_by_extension($entity) {
2032
+ foreach ($this->excluded_extensions as $ext) {
2033
+ if (!$ext) continue;
2034
+ $eln = strlen($ext);
2035
+ if (strtolower(substr($entity, -$eln, $eln)) == $ext) return true;
2036
+ }
2037
+ return false;
2038
+ }
2039
+
2040
+ private function is_entity_excluded_by_prefix($entity) {
2041
+ $entity = basename($entity);
2042
+ foreach ($this->excluded_prefixes as $pref) {
2043
+ if (!$pref) continue;
2044
+ $eln = strlen($pref);
2045
+ if (strtolower(substr($entity, 0, $eln)) == $pref) return true;
2046
+ }
2047
+ return false;
2048
+ }
2049
+
2050
+ private function unserialize_gz_cache_file($file) {
2051
+ if (!$whandle = gzopen($file, 'r')) return false;
2052
+ global $iwp_backup_core;
2053
+ $emptimes = 0;
2054
+ $var = '';
2055
+ while (!gzeof($whandle)) {
2056
+ $bytes = @gzread($whandle, 1048576);
2057
+ if (empty($bytes)) {
2058
+ $emptimes++;
2059
+ $iwp_backup_core->log("Got empty gzread ($emptimes times)");
2060
+ if ($emptimes>2) return false;
2061
+ } else {
2062
+ $var .= $bytes;
2063
+ }
2064
+ }
2065
+ gzclose($whandle);
2066
+ return unserialize($var);
2067
+ }
2068
+
2069
+ // Caution: $source is allowed to be an array, not just a filename
2070
+ // $destination is the temporary file (ending in .tmp)
2071
+ private function make_zipfile($source, $backup_file_basename, $whichone, $retry_on_error = true) {
2072
+
2073
+ global $iwp_backup_core;
2074
+
2075
+ $original_index = $this->index;
2076
+
2077
+ $itext = (empty($this->index)) ? '' : ($this->index+1);
2078
+ $destination_base = $backup_file_basename.'-'.$whichone.$itext.'.zip.tmp';
2079
+ $destination = $this->iwp_backup_dir.'/'.$destination_base;
2080
+
2081
+ // Legacy/redundant
2082
+ //if (empty($whichone) && is_string($whichone)) $whichone = basename($source);
2083
+
2084
+ // When to prefer PCL:
2085
+ // - We were asked to
2086
+ // - No zip extension present and no relevant method present
2087
+ // The zip extension check is not redundant, because method_exists segfaults some PHP installs, leading to support requests
2088
+
2089
+ // We need meta-info about $whichone
2090
+ $backupable_entities = $iwp_backup_core->get_backupable_file_entities(true, false);
2091
+ # This is only used by one corner-case in BinZip
2092
+ #$this->make_zipfile_source = (isset($backupable_entities[$whichone])) ? $backupable_entities[$whichone] : $source;
2093
+ $this->make_zipfile_source = (is_array($source) && isset($backupable_entities[$whichone])) ? (('uploads' == $whichone) ? dirname($backupable_entities[$whichone]) : $backupable_entities[$whichone]) : dirname($source);
2094
+
2095
+ $this->existing_files = array();
2096
+ # Used for tracking compression ratios
2097
+ $this->existing_files_rawsize = 0;
2098
+ $this->existing_zipfiles_size = 0;
2099
+
2100
+ // Enumerate existing files
2101
+ // Usually first_linked_index is zero; the exception being with more files, where previous zips' contents are irrelevant
2102
+ for ($j=$this->first_linked_index; $j<=$this->index; $j++) {
2103
+ $jtext = ($j == 0) ? '' : ($j+1);
2104
+ # This is, in a non-obvious way, compatible with filenames which indicate increments
2105
+ # $j does not need to start at zero; it should start at the index which the current entity split at. However, this is not directly known, and can only be deduced from examining the filenames. And, for other indexes from before the current increment, the searched-for filename won't exist (even if there is no cloud storage). So, this indirectly results in the desired outcome when we start from $j=0.
2106
+ $examine_zip = $this->iwp_backup_dir.'/'.$backup_file_basename.'-'.$whichone.$jtext.'.zip'.(($j == $this->index) ? '.tmp' : '');
2107
+
2108
+
2109
+ if ($j != $this->index && !file_exists($examine_zip)) {
2110
+ $alt_examine_zip = $this->iwp_backup_dir.'/'.$backup_file_basename.'-'.$whichone.$jtext.'.zip'.(($j == $this->index - 1) ? '.tmp' : '');
2111
+ if ($alt_examine_zip != $examine_zip && file_exists($alt_examine_zip) && is_readable($alt_examine_zip) && filesize($alt_examine_zip)>0) {
2112
+ $iwp_backup_core->log("Looked-for zip file not found; but non-zero .tmp zip was, despite not being current index ($j != ".$this->index." - renaming zip (assume previous resumption's IO was lost before kill)");
2113
+ if (rename($alt_examine_zip, $examine_zip)) {
2114
+ clearstatcache();
2115
+ } else {
2116
+ $iwp_backup_core->log("Rename failed - backup zips likely to not have sequential numbers (does not affect backup integrity, but can cause user confusion)");
2117
+ }
2118
+ }
2119
+ }
2120
+
2121
+ // If the file exists, then we should grab its index of files inside, and sizes
2122
+ // Then, when we come to write a file, we should check if it's already there, and only add if it is not
2123
+ if (file_exists($examine_zip) && is_readable($examine_zip) && filesize($examine_zip)>0) {
2124
+ $this->existing_zipfiles_size += filesize($examine_zip);
2125
+ $zip = new $this->use_zip_object;
2126
+ if (true !== $zip->open($examine_zip)) {
2127
+ $iwp_backup_core->log("Could not open zip file to examine (".$zip->last_error."); will remove: ".basename($examine_zip));
2128
+ @unlink($examine_zip);
2129
+ } else {
2130
+
2131
+ # Don't put this in the for loop, or the magic __get() method gets called and opens the zip file every time the loop goes round
2132
+ $numfiles = $zip->z;
2133
+
2134
+ for ($i=0; $i < $numfiles; $i++) {
2135
+ $si = $zip->statIndex($i);
2136
+ $name = $si['name'];
2137
+ $this->existing_files[$name] = $si['size'];
2138
+ $this->existing_files_rawsize += $si['size'];
2139
+ }
2140
+
2141
+ @$zip->close();
2142
+ }
2143
+
2144
+ $iwp_backup_core->log(basename($examine_zip).": Zip file already exists, with ".count($this->existing_files)." files");
2145
+
2146
+ # try_split is set if there have been no check-ins recently - or if it needs to be split anyway
2147
+ if ($j == $this->index) {
2148
+ if (isset($this->try_split)) {
2149
+ if (filesize($examine_zip) > 50*1048576) {
2150
+ # We could, as a future enhancement, save this back to the job data, if we see a case that needs it
2151
+ $this->zip_split_every = max(
2152
+ (int)$this->zip_split_every/2,
2153
+ IWP_SPLIT_MIN*1048576,
2154
+ min(filesize($examine_zip)-1048576, $this->zip_split_every)
2155
+ );
2156
+ $iwp_backup_core->jobdata_set('split_every', (int)($this->zip_split_every/1048576));
2157
+ $iwp_backup_core->log("No check-in on last two runs; bumping index and reducing zip split to: ".round($this->zip_split_every/1048576, 1)." MB");
2158
+ $do_bump_index = true;
2159
+ }
2160
+ unset($this->try_split);
2161
+ } elseif (filesize($examine_zip) > $this->zip_split_every) {
2162
+ $iwp_backup_core->log(sprintf("Zip size is at/near split limit (%s MB / %s MB) - bumping index (from: %d)", filesize($examine_zip), round($this->zip_split_every/1048576, 1), $this->index));
2163
+ $do_bump_index = true;
2164
+ }
2165
+ }
2166
+
2167
+ } elseif (file_exists($examine_zip)) {
2168
+ $iwp_backup_core->log("Zip file already exists, but is not readable or was zero-sized; will remove: ".basename($examine_zip));
2169
+ @unlink($examine_zip);
2170
+ }
2171
+ }
2172
+
2173
+ $this->zip_last_ratio = ($this->existing_files_rawsize > 0) ? ($this->existing_zipfiles_size/$this->existing_files_rawsize) : 1;
2174
+
2175
+ $this->zipfiles_added = 0;
2176
+ $this->zipfiles_added_thisrun = 0;
2177
+ $this->zipfiles_dirbatched = array();
2178
+ $this->zipfiles_batched = array();
2179
+ $this->zipfiles_skipped_notaltered = array();
2180
+ $this->zipfiles_lastwritetime = time();
2181
+ $this->zip_basename = $this->iwp_backup_dir.'/'.$backup_file_basename.'-'.$whichone;
2182
+
2183
+ if (!empty($do_bump_index)) $this->bump_index();
2184
+
2185
+ $error_occurred = false;
2186
+
2187
+ # Store this in its original form
2188
+ $this->source = $source;
2189
+
2190
+ # Reset. This counter is used only with PcLZip, to decide if it's better to do it all-in-one
2191
+ $this->makezip_recursive_batchedbytes = 0;
2192
+ if (!is_array($source)) $source=array($source);
2193
+
2194
+ $exclude = $iwp_backup_core->get_exclude($whichone);
2195
+
2196
+ $files_enumerated_at = $iwp_backup_core->jobdata_get('files_enumerated_at');
2197
+ if (!is_array($files_enumerated_at)) $files_enumerated_at = array();
2198
+ $files_enumerated_at[$whichone] = time();
2199
+ $iwp_backup_core->jobdata_set('files_enumerated_at', $files_enumerated_at);
2200
+
2201
+ $this->makezip_if_altered_since = (is_array($this->altered_since)) ? (isset($this->altered_since[$whichone]) ? $this->altered_since[$whichone] : -1) : -1;
2202
+
2203
+ // Reset
2204
+ $got_uploads_from_cache = false;
2205
+
2206
+ // Uploads: can/should we get it back from the cache?
2207
+ // || 'others' == $whichone
2208
+ if (('uploads' == $whichone || 'others' == $whichone) && function_exists('gzopen') && function_exists('gzread')) {
2209
+ $use_cache_files = false;
2210
+ $cache_file_base = $this->zip_basename.'-cachelist-'.$this->makezip_if_altered_since;
2211
+ // Cache file suffixes: -zfd.gz.tmp, -zfb.gz.tmp, -info.tmp, (possible)-zfs.gz.tmp
2212
+ if (file_exists($cache_file_base.'-zfd.gz.tmp') && file_exists($cache_file_base.'-zfb.gz.tmp') && file_exists($cache_file_base.'-info.tmp')) {
2213
+ // Cache files exist; shall we use them?
2214
+ $mtime = filemtime($cache_file_base.'-zfd.gz.tmp');
2215
+ // Require < 30 minutes old
2216
+ if (time() - $mtime < 1800) { $use_cache_files = true; }
2217
+ $any_failures = false;
2218
+ if ($use_cache_files) {
2219
+ $var = $this->unserialize_gz_cache_file($cache_file_base.'-zfd.gz.tmp');
2220
+ if (is_array($var)) {
2221
+ $this->zipfiles_dirbatched = $var;
2222
+ $var = $this->unserialize_gz_cache_file($cache_file_base.'-zfb.gz.tmp');
2223
+ if (is_array($var)) {
2224
+ $this->zipfiles_batched = $var;
2225
+ if (file_exists($cache_file_base.'-info.tmp')) {
2226
+ $var = maybe_unserialize(file_get_contents($cache_file_base.'-info.tmp'));
2227
+ if (is_array($var) && isset($var['makezip_recursive_batchedbytes'])) {
2228
+ $this->makezip_recursive_batchedbytes = $var['makezip_recursive_batchedbytes'];
2229
+ if (file_exists($cache_file_base.'-zfs.gz.tmp')) {~
2230
+ $var = $this->unserialize_gz_cache_file($cache_file_base.'-zfs.gz.tmp');
2231
+ if (is_array($var)) {
2232
+ $this->zipfiles_skipped_notaltered = $var;
2233
+ } else {
2234
+ $any_failures = true;
2235
+ }
2236
+ } else {
2237
+ $this->zipfiles_skipped_notaltered = array();
2238
+ }
2239
+ } else {
2240
+ $any_failures = true;
2241
+ }
2242
+ }
2243
+ } else {
2244
+ $any_failures = true;
2245
+ }
2246
+ } else {
2247
+ $any_failures = true;
2248
+ }
2249
+ if ($any_failures) {
2250
+ $iwp_backup_core->log("Failed to recover file lists from existing cache files");
2251
+ // Reset it all
2252
+ $this->zipfiles_skipped_notaltered = array();
2253
+ $this->makezip_recursive_batchedbytes = 0;
2254
+ $this->zipfiles_batched = array();
2255
+ $this->zipfiles_dirbatched = array();
2256
+ } else {
2257
+ $iwp_backup_core->log("File lists recovered from cache files; sizes: ".count($this->zipfiles_batched).", ".count($this->zipfiles_batched).", ".count($this->zipfiles_skipped_notaltered).")");
2258
+ $got_uploads_from_cache = true;
2259
+ }
2260
+ }
2261
+ }
2262
+ }
2263
+
2264
+ $time_counting_began = time();
2265
+
2266
+ $this->excluded_extensions = $this->get_excluded_extensions($exclude);
2267
+ $this->excluded_prefixes = $this->get_excluded_prefixes($exclude);
2268
+
2269
+ foreach ($source as $element) {
2270
+ #makezip_recursive_add($fullpath, $use_path_when_storing, $original_fullpath, $startlevels = 1, $exclude_array)
2271
+ if ('uploads' == $whichone) {
2272
+ if (empty($got_uploads_from_cache)) {
2273
+ $dirname = dirname($element);
2274
+ $basename = $this->basename($element);
2275
+ $add_them = $this->makezip_recursive_add($element, basename($dirname).'/'.$basename, $element, 2, $exclude);
2276
+ } else {
2277
+ $add_them = true;
2278
+ }
2279
+ } else {
2280
+ if (empty($got_uploads_from_cache)) {
2281
+ $add_them = $this->makezip_recursive_add($element, $this->basename($element), $element, 1, $exclude);
2282
+ } else {
2283
+ $add_them = true;
2284
+ }
2285
+ }
2286
+ if (is_wp_error($add_them) || false === $add_them) $error_occurred = true;
2287
+ }
2288
+
2289
+ $time_counting_ended = time();
2290
+
2291
+ // Cache the file scan, if it looks like it'll be useful
2292
+ // We use gzip to reduce the size as on hosts which limit disk I/O, the cacheing may make things worse
2293
+ // || 'others' == $whichone
2294
+ if (('uploads' == $whichone|| 'others' == $whichone) && !$error_occurred && function_exists('gzopen') && function_exists('gzwrite')) {
2295
+ $cache_file_base = $this->zip_basename.'-cachelist-'.$this->makezip_if_altered_since;
2296
+
2297
+ // Just approximate - we're trying to avoid an otherwise-unpredictable PHP fatal error. Cacheing only happens if file enumeration took a long time - so presumably there are very many.
2298
+ $memory_needed_estimate = 0;
2299
+ foreach ($this->zipfiles_batched as $k => $v) { $memory_needed_estimate += strlen($k)+strlen($v)+12; }
2300
+
2301
+ // We haven't bothered to check if we just fetched the files from cache, as that shouldn't take a long time and so shouldn't trigger this
2302
+ // Let us suppose we need 15% overhead for gzipping
2303
+
2304
+ $memory_limit = ini_get('memory_limit');
2305
+ $memory_usage = round(@memory_get_usage(false)/1048576, 1);
2306
+ $memory_usage2 = round(@memory_get_usage(true)/1048576, 1);
2307
+
2308
+ if ($time_counting_ended-$time_counting_began > 20 && $iwp_backup_core->verify_free_memory($memory_needed_estimate*0.15) && $whandle = gzopen($cache_file_base.'-zfb.gz.tmp', 'w')) {
2309
+ $iwp_backup_core->log("File counting took a long time (".($time_counting_ended - $time_counting_began)."s); will attempt to cache results (memory_limit: $memory_limit (used: ${memory_usage}M | ${memory_usage2}M), estimated uncompressed bytes: ".round($memory_needed_estimate/1024, 1)." Kb)");
2310
+
2311
+ $buf = 'a:'.count($this->zipfiles_batched).':{';
2312
+ foreach ($this->zipfiles_batched as $file => $add_as) {
2313
+ $k = addslashes($file);
2314
+ $v = addslashes($add_as);
2315
+ $buf .= 's:'.strlen($k).':"'.$k.'";s:'.strlen($v).':"'.$v.'";';
2316
+ if (strlen($buf) > 1048576) {
2317
+ gzwrite($whandle, $buf, strlen($buf));
2318
+ $buf = '';
2319
+ }
2320
+ }
2321
+ $buf .= '}';
2322
+ $final = gzwrite($whandle, $buf);
2323
+ unset($buf);
2324
+
2325
+
2326
+ if (!$final) {
2327
+ @unlink($cache_file_base.'-zfb.gz.tmp');
2328
+ @gzclose($whandle);
2329
+ } else {
2330
+ gzclose($whandle);
2331
+ if (!empty($this->zipfiles_skipped_notaltered)) {
2332
+ if ($shandle = gzopen($cache_file_base.'-zfs.gz.tmp', 'w')) {
2333
+ if (!gzwrite($shandle, serialize($this->zipfiles_skipped_notaltered))) {
2334
+ $aborted_on_skipped = true;
2335
+ }
2336
+ gzclose($shandle);
2337
+ } else {
2338
+ $aborted_on_skipped = true;
2339
+ }
2340
+ }
2341
+ if (!empty($aborted_on_skipped)) {
2342
+ @unlink($cache_file_base.'-zfs.gz.tmp');
2343
+ @unlink($cache_file_base.'-zfb.gz.tmp');
2344
+ } else {
2345
+ $info_array = array('makezip_recursive_batchedbytes' => $this->makezip_recursive_batchedbytes);
2346
+ if (!file_put_contents($cache_file_base.'-info.tmp', serialize($info_array))) {
2347
+ @unlink($cache_file_base.'-zfs.gz.tmp');
2348
+ @unlink($cache_file_base.'-zfb.gz.tmp');
2349
+ }
2350
+ if ($dhandle = gzopen($cache_file_base.'-zfd.gz.tmp', 'w')) {
2351
+ if (!gzwrite($dhandle, serialize($this->zipfiles_dirbatched))) {
2352
+ $aborted_on_dirbatched = true;
2353
+ }
2354
+ gzclose($dhandle);
2355
+ } else {
2356
+ $aborted_on_dirbatched = true;
2357
+ }
2358
+ if (!empty($aborted_on_dirbatched)) {
2359
+ @unlink($cache_file_base.'-zfs.gz.tmp');
2360
+ @unlink($cache_file_base.'-zfd.gz.tmp');
2361
+ @unlink($cache_file_base.'-zfb.gz.tmp');
2362
+ @unlink($cache_file_base.'-info.tmp');
2363
+ } else {
2364
+ // Success.
2365
+ }
2366
+ }
2367
+ }
2368
+ }
2369
+
2370
+ /*
2371
+ Class variables that get altered:
2372
+ zipfiles_batched
2373
+ makezip_recursive_batchedbytes
2374
+ zipfiles_skipped_notaltered
2375
+ zipfiles_dirbatched
2376
+
2377
+ Class variables that the result depends upon (other than the state of the filesystem):
2378
+ makezip_if_altered_since
2379
+ existing_files
2380
+ */
2381
+
2382
+ }
2383
+
2384
+ // Any not yet dispatched? Under our present scheme, at this point nothing has yet been despatched. And since the enumerating of all files can take a while, we can at this point do a further modification check to reduce the chance of overlaps.
2385
+ // This relies on us *not* touch()ing the zip file to indicate to any resumption 'behind us' that we're already here. Rather, we're relying on the combined facts that a) if it takes us a while to search the directory tree, then it should do for the one behind us too (though they'll have the benefit of cache, so could catch very fast) and b) we touch *immediately* after finishing the enumeration of the files to add.
2386
+ // $retry_on_error is here being used as a proxy for 'not the second time around, when there might be the remains of the file on the first time around'
2387
+ if ($retry_on_error) $iwp_backup_core->check_recent_modification($destination);
2388
+ // Here we're relying on the fact that both PclZip and ZipArchive will happily operate on an empty file. Note that BinZip *won't* (for that, may need a new strategy - e.g. add the very first file on its own, in order to 'lay down a marker')
2389
+ if (empty($do_bump_index)) @touch($destination);
2390
+
2391
+ if (count($this->zipfiles_dirbatched) > 0 || count($this->zipfiles_batched) > 0) {
2392
+
2393
+ $iwp_backup_core->log(sprintf("Total entities for the zip file: %d directories, %d files (%d skipped as non-modified), %s MB", count($this->zipfiles_dirbatched), count($this->zipfiles_batched), count($this->zipfiles_skipped_notaltered), round($this->makezip_recursive_batchedbytes/1048576,1)));
2394
+
2395
+ // No need to warn if we're going to retry anyway. (And if we get killed, the zip will be rescanned for its contents upon resumption).
2396
+ $warn_on_failures = ($retry_on_error) ? false : true;
2397
+ $add_them = $this->makezip_addfiles($warn_on_failures);
2398
+
2399
+ if (is_wp_error($add_them)) {
2400
+ foreach ($add_them->get_error_messages() as $msg) {
2401
+ $iwp_backup_core->log("Error returned from makezip_addfiles: ".$msg);
2402
+ }
2403
+ $error_occurred = true;
2404
+ } elseif (false === $add_them) {
2405
+ $iwp_backup_core->log("Error: makezip_addfiles returned false");
2406
+ $error_occurred = true;
2407
+ }
2408
+
2409
+ }
2410
+
2411
+ // Reset these variables because the index may have changed since we began
2412
+
2413
+ $itext = (empty($this->index)) ? '' : ($this->index+1);
2414
+ $destination_base = $backup_file_basename.'-'.$whichone.$itext.'.zip.tmp';
2415
+ $destination = $this->iwp_backup_dir.'/'.$destination_base;
2416
+
2417
+ // ZipArchive::addFile sometimes fails - there's nothing when we expected something.
2418
+ // Did not used to have || $error_occured here. But it is better to retry, than to simply warn the user to check his logs.
2419
+ if (((file_exists($destination) || $this->index == $original_index) && @filesize($destination) < 90 && 'IWP_MMB_ZipArchive' == $this->use_zip_object) || ($error_occurred && $retry_on_error)) {
2420
+ // This can be made more sophisticated if feedback justifies it. Currently we just switch to PclZip. But, it may have been a BinZip failure, so we could then try ZipArchive if that is available. If doing that, make sure that an infinite recursion isn't made possible.
2421
+ $iwp_backup_core->log("makezip_addfiles(".$this->use_zip_object.") apparently failed (file=".basename($destination).", type=$whichone, size=".filesize($destination).") - retrying with PclZip");
2422
+ $saved_zip_object = $this->use_zip_object;
2423
+ $this->use_zip_object = 'IWP_MMB_PclZip';
2424
+ $ret = $this->make_zipfile($source, $backup_file_basename, $whichone, false);
2425
+ $this->use_zip_object = $saved_zip_object;
2426
+ return $ret;
2427
+ }
2428
+
2429
+ // zipfiles_added > 0 means that $zip->close() has been called. i.e. An attempt was made to add something: something _should_ be there.
2430
+ // Why return true even if $error_occurred may be set? 1) Because in that case, a warning has already been logged. 2) Because returning false causes an error to be logged, which means it'll all be retried again. Also 3) this has been the pattern of the code for a long time, and the algorithm has been proven in the real-world: don't change what's not broken.
2431
+ // (file_exists($destination) || $this->index == $original_index) might be an alternative to $this->zipfiles_added > 0 - ? But, don't change what's not broken.
2432
+ if ($error_occurred == false || $this->zipfiles_added > 0) {
2433
+ return true;
2434
+ } else {
2435
+ $iwp_backup_core->log("makezip failure: zipfiles_added=".$this->zipfiles_added.", error_occurred=".$error_occurred." (method=".$this->use_zip_object.")");
2436
+ return false;
2437
+ }
2438
+
2439
+ }
2440
+
2441
+ private function basename($element) {
2442
+ # This function is an ugly, conservative workaround for https://bugs.php.net/bug.php?id=62119. It does not aim to always work-around, but to ensure that nothing is made worse.
2443
+ $dirname = dirname($element);
2444
+ $basename_manual = preg_replace('#^[\\/]+#', '', substr($element, strlen($dirname)));
2445
+ $basename = basename($element);
2446
+ if ($basename_manual != $basename) {
2447
+ $locale = setlocale(LC_CTYPE, "0");
2448
+ if ('C' == $locale) {
2449
+ setlocale(LC_CTYPE, 'en_US.UTF8');
2450
+ $basename_new = basename($element);
2451
+ if ($basename_new == $basename_manual) $basename = $basename_new;
2452
+ setlocale(LC_CTYPE, $locale);
2453
+ }
2454
+ }
2455
+ return $basename;
2456
+ }
2457
+
2458
+ private function file_should_be_stored_without_compression($file) {
2459
+ if (!is_array($this->extensions_to_not_compress)) return false;
2460
+ foreach ($this->extensions_to_not_compress as $ext) {
2461
+ $ext_len = strlen($ext);
2462
+ if (strtolower(substr($file, -$ext_len, $ext_len)) == $ext) return true;
2463
+ }
2464
+ return false;
2465
+ }
2466
+
2467
+ // Q. Why don't we only open and close the zip file just once?
2468
+ // A. Because apparently PHP doesn't write out until the final close, and it will return an error if anything file has vanished in the meantime. So going directory-by-directory reduces our chances of hitting an error if the filesystem is changing underneath us (which is very possible if dealing with e.g. 1GB of files)
2469
+
2470
+ // We batch up the files, rather than do them one at a time. So we are more efficient than open,one-write,close.
2471
+ // To call into here, the array $this->zipfiles_batched must be populated (keys=paths, values=add-to-zip-as values). It gets reset upon exit from here.
2472
+ private function makezip_addfiles($warn_on_failures) {
2473
+
2474
+ global $iwp_backup_core;
2475
+
2476
+ # Used to detect requests to bump the size
2477
+ $bump_index = false;
2478
+ $ret = true;
2479
+
2480
+ $zipfile = $this->zip_basename.(($this->index == 0) ? '' : ($this->index+1)).'.zip.tmp';
2481
+
2482
+ $maxzipbatch = $iwp_backup_core->jobdata_get('maxzipbatch', 26214400);
2483
+ if ((int)$maxzipbatch < 1024) $maxzipbatch = 26214400;
2484
+
2485
+ // Short-circuit the null case, because we want to detect later if something useful happenned
2486
+ if (count($this->zipfiles_dirbatched) == 0 && count($this->zipfiles_batched) == 0) return true;
2487
+
2488
+ # If on PclZip, then if possible short-circuit to a quicker method (makes a huge time difference - on a folder of 1500 small files, 2.6s instead of 76.6)
2489
+ # This assumes that makezip_addfiles() is only called once so that we know about all needed files (the new style)
2490
+ # This is rather conservative - because it assumes zero compression. But we can't know that in advance.
2491
+ $force_allinone = false;
2492
+ if (0 == $this->index && $this->makezip_recursive_batchedbytes < $this->zip_split_every) {
2493
+ # So far, we only have a processor for this for PclZip; but that check can be removed - need to address the below items
2494
+ # TODO: Is this really what we want? Always go all-in-one for < 500MB???? Should be more conservative? Or, is it always faster to go all-in-one? What about situations where we might want to auto-split because of slowness - check that that is still working.
2495
+ # TODO: Test this new method for PclZip - are we still getting the performance gains? Test for ZipArchive too.
2496
+ if ('IWP_MMB_PclZip' == $this->use_zip_object && ($this->makezip_recursive_batchedbytes < 512*1048576 || (defined('IWP_PCLZIP_FORCEALLINONE') && IWP_PCLZIP_FORCEALLINONE == true && 'IWP_MMB_PclZip' == $this->use_zip_object))) {
2497
+ $iwp_backup_core->log("Only one archive required (".$this->use_zip_object.") - will attempt to do in single operation (data: ".round($this->makezip_recursive_batchedbytes/1024,1)." KB, split: ".round($this->zip_split_every/1024, 1)." KB)");
2498
+ }
2499
+ }
2500
+
2501
+ // 05-Mar-2013 - added a new check on the total data added; it appears that things fall over if too much data is contained in the cumulative total of files that were addFile'd without a close-open cycle; presumably data is being stored in memory. In the case in question, it was a batch of MP3 files of around 100MB each - 25 of those equals 2.5GB!
2502
+
2503
+ $data_added_since_reopen = 0;
2504
+ # The following array is used only for error reporting if ZipArchive::close fails (since that method itself reports no error messages - we have to track manually what we were attempting to add)
2505
+ $files_zipadded_since_open = array();
2506
+
2507
+ $zip = new $this->use_zip_object;
2508
+ if (file_exists($zipfile)) {
2509
+ $opencode = $zip->open($zipfile);
2510
+ $original_size = filesize($zipfile);
2511
+ clearstatcache();
2512
+ } else {
2513
+ $create_code = (version_compare(PHP_VERSION, '5.2.12', '>') && defined('ZIPARCHIVE::CREATE')) ? ZIPARCHIVE::CREATE : 1;
2514
+ $opencode = $zip->open($zipfile, $create_code);
2515
+ $original_size = 0;
2516
+ }
2517
+
2518
+ if ($opencode !== true) return new WP_Error('no_open', sprintf(__('Failed to open the zip file (%s) - %s', 'InfiniteWP'),$zipfile, $zip->last_error));
2519
+ # TODO: This action isn't being called for the all-in-one case - should be, I think
2520
+ do_action("IWP_makezip_addfiles_prepack", $this, $this->whichone);
2521
+
2522
+ // Make sure all directories are created before we start creating files
2523
+ while ($dir = array_pop($this->zipfiles_dirbatched)) $zip->addEmptyDir($dir);
2524
+ $zipfiles_added_thisbatch = 0;
2525
+
2526
+ // Go through all those batched files
2527
+ foreach ($this->zipfiles_batched as $file => $add_as) {
2528
+
2529
+ if (!file_exists($file)) {
2530
+ $iwp_backup_core->log("File has vanished from underneath us; dropping: ".$add_as);
2531
+ continue;
2532
+ }
2533
+
2534
+ $fsize = filesize($file);
2535
+
2536
+ if (@constant('IWP_SKIP_FILE_OVER_SIZE') && $fsize > IWP_SKIP_FILE_OVER_SIZE) {
2537
+ $iwp_backup_core->log("File is larger than the user-configured (IWP_SKIP_FILE_OVER_SIZE) maximum (is: ".round($fsize/1024, 1)." KB); will skip: ".$add_as);
2538
+ continue;
2539
+ } elseif ($fsize > IWP_WARN_FILE_SIZE) {
2540
+ $iwp_backup_core->log(sprintf(__('A very large file was encountered: %s (size: %s Mb)', 'InfiniteWP'), $add_as, round($fsize/1048576, 1)), 'warning', 'vlargefile_'.md5($this->whichone.'#'.$add_as));
2541
+ }
2542
+
2543
+ // Skips files that are already added
2544
+ if (!isset($this->existing_files[$add_as]) || $this->existing_files[$add_as] != $fsize) {
2545
+
2546
+ @touch($zipfile);
2547
+ $zip->addFile($file, $add_as);
2548
+ $zipfiles_added_thisbatch++;
2549
+
2550
+ if (method_exists($zip, 'setCompressionName') && $this->file_should_be_stored_without_compression($add_as)) {
2551
+ if (false == ($set_compress = $zip->setCompressionName($add_as, ZipArchive::CM_STORE))) {
2552
+ $iwp_backup_core->log("Zip: setCompressionName failed on: $add_as");
2553
+ }
2554
+ }
2555
+
2556
+ // N.B., Since makezip_addfiles() can get called more than once if there were errors detected, potentially $zipfiles_added_thisrun can exceed the total number of batched files (if they get processed twice).
2557
+ $this->zipfiles_added_thisrun++;
2558
+ $files_zipadded_since_open[] = array('file' => $file, 'addas' => $add_as);
2559
+
2560
+ $data_added_since_reopen += $fsize;
2561
+ /* Conditions for forcing a write-out and re-open:
2562
+ - more than $maxzipbatch bytes have been batched
2563
+ - more than 2.0 seconds have passed since the last time we wrote
2564
+ - that adding this batch of data is likely already enough to take us over the split limit (and if that happens, then do actually split - to prevent a scenario of progressively tinier writes as we approach but don't actually reach the limit)
2565
+ - more than 500 files batched (should perhaps intelligently lower this as the zip file gets bigger - not yet needed)
2566
+ */
2567
+
2568
+ # Add 10% margin. It only really matters when the OS has a file size limit, exceeding which causes failure (e.g. 2GB on 32-bit)
2569
+ # Since we don't test before the file has been created (so that zip_last_ratio has meaningful data), we rely on max_zip_batch being less than zip_split_every - which should always be the case
2570
+ $reaching_split_limit = ( $this->zip_last_ratio > 0 && $original_size>0 && ($original_size + 1.1*$data_added_since_reopen*$this->zip_last_ratio) > $this->zip_split_every) ? true : false;
2571
+
2572
+ if (!$force_allinone && ($zipfiles_added_thisbatch > IWP_MAXBATCHFILES || $reaching_split_limit || $data_added_since_reopen > $maxzipbatch || (time() - $this->zipfiles_lastwritetime) > 2)) {
2573
+
2574
+ @set_time_limit(IWP_SET_TIME_LIMIT);
2575
+ $something_useful_sizetest = false;
2576
+
2577
+ if ($data_added_since_reopen > $maxzipbatch) {
2578
+ $something_useful_sizetest = true;
2579
+ $iwp_backup_core->log("Adding batch to zip file (".$this->use_zip_object."): over ".round($maxzipbatch/1048576,1)." MB added on this batch (".round($data_added_since_reopen/1048576,1)." MB, ".count($this->zipfiles_batched)." files batched, $zipfiles_added_thisbatch (".$this->zipfiles_added_thisrun.") added so far); re-opening (prior size: ".round($original_size/1024,1).' KB)');
2580
+ } elseif ($zipfiles_added_thisbatch > IWP_MAXBATCHFILES) {
2581
+ $iwp_backup_core->log("Adding batch to zip file (".$this->use_zip_object."): over ".IWP_MAXBATCHFILES." files added on this batch (".round($data_added_since_reopen/1048576,1)." MB, ".count($this->zipfiles_batched)." files batched, $zipfiles_added_thisbatch (".$this->zipfiles_added_thisrun.") added so far); re-opening (prior size: ".round($original_size/1024,1).' KB)');
2582
+ } elseif (!$reaching_split_limit) {
2583
+ $iwp_backup_core->log("Adding batch to zip file (".$this->use_zip_object."): over 2.0 seconds have passed since the last write (".round($data_added_since_reopen/1048576,1)." MB, $zipfiles_added_thisbatch (".$this->zipfiles_added_thisrun.") files added so far); re-opening (prior size: ".round($original_size/1024,1).' KB)');
2584
+ } else {
2585
+ $iwp_backup_core->log("Adding batch to zip file (".$this->use_zip_object."): possibly approaching split limit (".round($data_added_since_reopen/1048576,1)." MB, $zipfiles_added_thisbatch (".$this->zipfiles_added_thisrun.") files added so far); last ratio: ".round($this->zip_last_ratio,4)."; re-opening (prior size: ".round($original_size/1024,1).' KB)');
2586
+ }
2587
+
2588
+ if (!$zip->close()) {
2589
+ // Though we will continue processing the files we've got, the final error code will be false, to allow a second attempt on the failed ones. This also keeps us consistent with a negative result for $zip->close() further down. We don't just retry here, because we have seen cases (with BinZip) where upon failure, the existing zip had actually been deleted. So, to be safe we need to re-scan the existing zips.
2590
+ $ret = false;
2591
+ $this->record_zip_error($files_zipadded_since_open, $zip->last_error, $warn_on_failures);
2592
+ }
2593
+
2594
+ $zipfiles_added_thisbatch = 0;
2595
+
2596
+ # This triggers a re-open, later
2597
+ unset($zip);
2598
+ $files_zipadded_since_open = array();
2599
+ // Call here, in case we've got so many big files that we don't complete the whole routine
2600
+ if (filesize($zipfile) > $original_size) {
2601
+
2602
+ # It is essential that this does not go above 1, even though in reality (and this can happen at the start, if just 1 file is added (e.g. due to >2.0s detection) the 'compressed' zip file may be *bigger* than the files stored in it. When that happens, if the ratio is big enough, it can then fire the "approaching split limit" detection (very) prematurely
2603
+ $this->zip_last_ratio = ($data_added_since_reopen > 0) ? min((filesize($zipfile) - $original_size)/$data_added_since_reopen, 1) : 1;
2604
+
2605
+ # We need a rolling update of this
2606
+ $original_size = filesize($zipfile);
2607
+
2608
+ # Move on to next zip?
2609
+ if ($reaching_split_limit || filesize($zipfile) > $this->zip_split_every) {
2610
+ $bump_index = true;
2611
+ # Take the filesize now because later we wanted to know we did clearstatcache()
2612
+ $bumped_at = round(filesize($zipfile)/1048576, 1);
2613
+ }
2614
+
2615
+ # Need to make sure that something_useful_happened() is always called
2616
+
2617
+ # How long since the current run began? If it's taken long (and we're in danger of not making it at all), or if that is forseeable in future because of general slowness, then we should reduce the parameters.
2618
+ if (!$something_useful_sizetest) {
2619
+ $iwp_backup_core->something_useful_happened();
2620
+ } else {
2621
+
2622
+ // Do this as early as possible
2623
+ $iwp_backup_core->something_useful_happened();
2624
+
2625
+ $time_since_began = max(microtime(true)- $this->zipfiles_lastwritetime, 0.000001);
2626
+ $normalised_time_since_began = $time_since_began*($maxzipbatch/$data_added_since_reopen);
2627
+
2628
+ // Don't measure speed until after ZipArchive::close()
2629
+ $rate = round($data_added_since_reopen/$time_since_began, 1);
2630
+
2631
+ $iwp_backup_core->log(sprintf("A useful amount of data was added after this amount of zip processing: %s s (normalised: %s s, rate: %s KB/s)", round($time_since_began, 1), round($normalised_time_since_began, 1), round($rate/1024, 1)));
2632
+
2633
+ // We want to detect not only that we need to reduce the size of batches, but also the capability to increase them. This is particularly important because of ZipArchive()'s (understandable, given the tendency of PHP processes being terminated without notice) practice of first creating a temporary zip file via copying before acting on that zip file (so the information is atomic). Unfortunately, once the size of the zip file gets over 100MB, the copy operation beguns to be significant. By the time you've hit 500MB on many web hosts the copy is the majority of the time taken. So we want to do more in between these copies if possible.
2634
+
2635
+ /* "Could have done more" - detect as:
2636
+ - A batch operation would still leave a "good chunk" of time in a run
2637
+ - "Good chunk" means that the time we took to add the batch is less than 50% of a run time
2638
+ - We can do that on any run after the first (when at least one ceiling on the maximum time is known)
2639
+ - But in the case where a max_execution_time is long (so that resumptions are never needed), and we're always on run 0, we will automatically increase chunk size if the batch took less than 6 seconds.
2640
+ */
2641
+
2642
+ // At one stage we had a strategy of not allowing check-ins to have more than 20s between them. However, once the zip file got to a certain size, PHP's habit of copying the entire zip file first meant that it *always* went over 18s, and thence a drop in the max size was inevitable - which was bad, because with the copy time being something that only grew, the outcome was less data being copied every time
2643
+
2644
+ // Gather the data. We try not to do this unless necessary (may be time-sensitive)
2645
+ if ($iwp_backup_core->current_resumption >= 1) {
2646
+ $time_passed = $iwp_backup_core->jobdata_get('run_times');
2647
+ if (!is_array($time_passed)) $time_passed = array();
2648
+ list($max_time, $timings_string, $run_times_known) = $iwp_backup_core->max_time_passed($time_passed, $iwp_backup_core->current_resumption-1, $this->first_run);
2649
+ } else {
2650
+ $run_times_known = 0;
2651
+ $max_time = -1;
2652
+ }
2653
+
2654
+ if ($normalised_time_since_began<6 || ($iwp_backup_core->current_resumption >=1 && $run_times_known >=1 && $time_since_began < 0.6*$max_time )) {
2655
+
2656
+ // How much can we increase it by?
2657
+ if ($normalised_time_since_began <6) {
2658
+ if ($run_times_known > 0 && $max_time >0) {
2659
+ $new_maxzipbatch = min(floor(max(
2660
+ $maxzipbatch*6/$normalised_time_since_began, $maxzipbatch*((0.6*$max_time)/$normalised_time_since_began))),
2661
+ 200*1024*1024
2662
+ );
2663
+ } else {
2664
+ # Maximum of 200MB in a batch
2665
+ $new_maxzipbatch = min( floor($maxzipbatch*6/$normalised_time_since_began),
2666
+ 200*1024*1024
2667
+ );
2668
+ }
2669
+ } else {
2670
+ // Use up to 60% of available time
2671
+ $new_maxzipbatch = min(
2672
+ floor($maxzipbatch*((0.6*$max_time)/$normalised_time_since_began)),
2673
+ 200*1024*1024
2674
+ );
2675
+ }
2676
+
2677
+ # Throttle increases - don't increase by more than 2x in one go - ???
2678
+ # $new_maxzipbatch = floor(min(2*$maxzipbatch, $new_maxzipbatch));
2679
+ # Also don't allow anything that is going to be more than 18 seconds - actually, that's harmful because of the basically fixed time taken to copy the file
2680
+ # $new_maxzipbatch = floor(min(18*$rate ,$new_maxzipbatch));
2681
+
2682
+ # Don't go above the split amount (though we expect that to be higher anyway, unless sending via email)
2683
+ $new_maxzipbatch = min($new_maxzipbatch, $this->zip_split_every);
2684
+
2685
+ # Don't raise it above a level that failed on a previous run
2686
+ $maxzipbatch_ceiling = $iwp_backup_core->jobdata_get('maxzipbatch_ceiling');
2687
+ if (is_numeric($maxzipbatch_ceiling) && $maxzipbatch_ceiling > 20*1024*1024 && $new_maxzipbatch > $maxzipbatch_ceiling) {
2688
+ $iwp_backup_core->log("Was going to raise maxzipbytes to $new_maxzipbatch, but this is too high: a previous failure led to the ceiling being set at $maxzipbatch_ceiling, which we will use instead");
2689
+ $new_maxzipbatch = $maxzipbatch_ceiling;
2690
+ }
2691
+
2692
+ // Final sanity check
2693
+ if ($new_maxzipbatch > 1024*1024) $iwp_backup_core->jobdata_set("maxzipbatch", $new_maxzipbatch);
2694
+
2695
+ if ($new_maxzipbatch <= 1024*1024) {
2696
+ $iwp_backup_core->log("Unexpected new_maxzipbatch value obtained (time=$time_since_began, normalised_time=$normalised_time_since_began, max_time=$max_time, data points known=$run_times_known, old_max_bytes=$maxzipbatch, new_max_bytes=$new_maxzipbatch)");
2697
+ } elseif ($new_maxzipbatch > $maxzipbatch) {
2698
+ $iwp_backup_core->log("Performance is good - will increase the amount of data we attempt to batch (time=$time_since_began, normalised_time=$normalised_time_since_began, max_time=$max_time, data points known=$run_times_known, old_max_bytes=$maxzipbatch, new_max_bytes=$new_maxzipbatch)");
2699
+ } elseif ($new_maxzipbatch < $maxzipbatch) {
2700
+ // Ironically, we thought we were speedy...
2701
+ $iwp_backup_core->log("Adjust: Reducing maximum amount of batched data (time=$time_since_began, normalised_time=$normalised_time_since_began, max_time=$max_time, data points known=$run_times_known, new_max_bytes=$new_maxzipbatch, old_max_bytes=$maxzipbatch)");
2702
+ } else {
2703
+ $iwp_backup_core->log("Performance is good - but we will not increase the amount of data we batch, as we are already at the present limit (time=$time_since_began, normalised_time=$normalised_time_since_began, max_time=$max_time, data points known=$run_times_known, max_bytes=$maxzipbatch)");
2704
+ }
2705
+
2706
+ if ($new_maxzipbatch > 1024*1024) $maxzipbatch = $new_maxzipbatch;
2707
+ }
2708
+
2709
+ // Detect excessive slowness
2710
+ // Don't do this until we're on at least resumption 7, as we want to allow some time for things to settle down and the maxiumum time to be accurately known (since reducing the batch size unnecessarily can itself cause extra slowness, due to PHP's usage of temporary zip files)
2711
+
2712
+ // We use a percentage-based system as much as possible, to avoid the various criteria being in conflict with each other (i.e. a run being both 'slow' and 'fast' at the same time, which is increasingly likely as max_time gets smaller).
2713
+
2714
+ if (!$iwp_backup_core->something_useful_happened && $iwp_backup_core->current_resumption >= 7) {
2715
+
2716
+ $iwp_backup_core->something_useful_happened();
2717
+
2718
+ if ($run_times_known >= 5 && ($time_since_began > 0.8 * $max_time || $time_since_began + 7 > $max_time)) {
2719
+
2720
+ $new_maxzipbatch = max(floor($maxzipbatch*0.8), 20971520);
2721
+ if ($new_maxzipbatch < $maxzipbatch) {
2722
+ $maxzipbatch = $new_maxzipbatch;
2723
+ $iwp_backup_core->jobdata_set("maxzipbatch", $new_maxzipbatch);
2724
+ $iwp_backup_core->log("We are within a small amount of the expected maximum amount of time available; the zip-writing thresholds will be reduced (time_passed=$time_since_began, normalised_time_passed=$normalised_time_since_began, max_time=$max_time, data points known=$run_times_known, old_max_bytes=$maxzipbatch, new_max_bytes=$new_maxzipbatch)");
2725
+ } else {
2726
+ $iwp_backup_core->log("We are within a small amount of the expected maximum amount of time available, but the zip-writing threshold is already at its lower limit (20MB), so will not be further reduced (max_time=$max_time, data points known=$run_times_known, max_bytes=$maxzipbatch)");
2727
+ }
2728
+ }
2729
+
2730
+ } else {
2731
+ $iwp_backup_core->something_useful_happened();
2732
+ }
2733
+ }
2734
+ $data_added_since_reopen = 0;
2735
+ } else {
2736
+ # ZipArchive::close() can take a very long time, which we want to know about
2737
+ $iwp_backup_core->record_still_alive();
2738
+ }
2739
+
2740
+ clearstatcache();
2741
+ $this->zipfiles_lastwritetime = time();
2742
+ }
2743
+ } elseif (0 == $this->zipfiles_added_thisrun) {
2744
+ // Update lastwritetime, because otherwise the 2.0-second-activity detection can fire prematurely (e.g. if it takes >2.0 seconds to process the previously-written files, then the detector fires after 1 file. This then can have the knock-on effect of having something_useful_happened() called, but then a subsequent attempt to write out a lot of meaningful data fails, and the maximum batch is not then reduced.
2745
+ // Testing shows that calling time() 1000 times takes negligible time
2746
+ $this->zipfiles_lastwritetime=time();
2747
+ }
2748
+
2749
+ $this->zipfiles_added++;
2750
+
2751
+ // Don't call something_useful_happened() here - nothing necessarily happens until close() is called
2752
+ if ($this->zipfiles_added % 100 == 0) $iwp_backup_core->log("Zip: ".basename($zipfile).": ".$this->zipfiles_added." files added (on-disk size: ".round(@filesize($zipfile)/1024,1)." KB)");
2753
+
2754
+ if ($bump_index) {
2755
+ $iwp_backup_core->log(sprintf("Zip size is at/near split limit (%s MB / %s MB) - bumping index (from: %d)", $bumped_at, round($this->zip_split_every/1048576, 1), $this->index));
2756
+ $bump_index = false;
2757
+ $this->bump_index();
2758
+ $zipfile = $this->zip_basename.($this->index+1).'.zip.tmp';
2759
+ }
2760
+
2761
+ if (empty($zip)) {
2762
+ $zip = new $this->use_zip_object;
2763
+
2764
+ if (file_exists($zipfile)) {
2765
+ $opencode = $zip->open($zipfile);
2766
+ $original_size = filesize($zipfile);
2767
+ clearstatcache();
2768
+ } else {
2769
+ $create_code = defined('ZIPARCHIVE::CREATE') ? ZIPARCHIVE::CREATE : 1;
2770
+ $opencode = $zip->open($zipfile, $create_code);
2771
+ $original_size = 0;
2772
+ }
2773
+
2774
+ if ($opencode !== true) return new WP_Error('no_open', sprintf(__('Failed to open the zip file (%s) - %s', 'InfiniteWP'), $zipfile, $zip->last_error));
2775
+ }
2776
+
2777
+ }
2778
+
2779
+ # Reset array
2780
+ $this->zipfiles_batched = array();
2781
+ $this->zipfiles_skipped_notaltered = array();
2782
+
2783
+ if (false == ($nret = $zip->close())) $this->record_zip_error($files_zipadded_since_open, $zip->last_error, $warn_on_failures);
2784
+
2785
+ do_action("IWP_makezip_addfiles_finished", $this, $this->whichone);
2786
+
2787
+ $this->zipfiles_lastwritetime = time();
2788
+ # May not exist if the last thing we did was bump
2789
+ if (file_exists($zipfile) && filesize($zipfile) > $original_size) $iwp_backup_core->something_useful_happened();
2790
+
2791
+ # Move on to next archive?
2792
+ if (file_exists($zipfile) && filesize($zipfile) > $this->zip_split_every) {
2793
+ $iwp_backup_core->log(sprintf("Zip size has gone over split limit (%s, %s) - bumping index (%d)", round(filesize($zipfile)/1048576,1), round($this->zip_split_every/1048576, 1), $this->index));
2794
+ $this->bump_index();
2795
+ }
2796
+
2797
+ clearstatcache();
2798
+
2799
+ return ($ret == false) ? false : $nret;
2800
+ }
2801
+
2802
+ private function record_zip_error($files_zipadded_since_open, $msg, $warn = true) {
2803
+ global $iwp_backup_core;
2804
+
2805
+ if (!empty($iwp_backup_core->cpanel_quota_readable)) {
2806
+ $hosting_bytes_free = $iwp_backup_core->get_hosting_disk_quota_free();
2807
+ if (is_array($hosting_bytes_free)) {
2808
+ $perc = round(100*$hosting_bytes_free[1]/(max($hosting_bytes_free[2], 1)), 1);
2809
+ $quota_free_msg = sprintf('Free disk space in account: %s (%s used)', round($hosting_bytes_free[3]/1048576, 1)." MB", "$perc %");
2810
+ $iwp_backup_core->log($quota_free_msg);
2811
+ if ($hosting_bytes_free[3] < 1048576*50) {
2812
+ $quota_low = true;
2813
+ $quota_free_mb = round($hosting_bytes_free[3]/1048576, 1);
2814
+ $iwp_backup_core->log(sprintf(__('Your free space in your hosting account is very low - only %s Mb remain', 'InfiniteWP'), $quota_free_mb), 'warning', 'lowaccountspace'.$quota_free_mb);
2815
+ }
2816
+ }
2817
+ }
2818
+
2819
+ // Always warn of this
2820
+ if (strpos($msg, 'File Size Limit Exceeded') !== false && 'IWP_MMB_BinZip' == $this->use_zip_object) {
2821
+ $iwp_backup_core->log(sprintf(__('The zip engine returned the message: %s.', 'InfiniteWP'), 'File Size Limit Exceeded'). __('Go here for more information.','InfiniteWP').' Please define this constant on your wp-config file define("IWP_NO_BINZIP", true);', 'warning', 'zipcloseerror-filesizelimit');
2822
+ } elseif ($warn) {
2823
+ $warn_msg = __('A zip error occurred', 'InfiniteWP').' - ';
2824
+ if (!empty($quota_low)) {
2825
+ $warn_msg = sprintf(__('your web hosting account appears to be full; please see: %s', 'InfiniteWP'), 'Increase your server space to backup. Make sure your web server space should be twice the site size');
2826
+ } else {
2827
+ $warn_msg .= __('check your log for more details.', 'InfiniteWP');
2828
+ }
2829
+ $iwp_backup_core->log($warn_msg, 'warning', 'zipcloseerror-'.$this->whichone);
2830
+ }
2831
+
2832
+ $iwp_backup_core->log("The attempt to close the zip file returned an error ($msg). List of files we were trying to add follows (check their permissions).");
2833
+
2834
+ foreach ($files_zipadded_since_open as $ffile) {
2835
+ $iwp_backup_core->log("File: ".$ffile['addas']." (exists: ".(int)@file_exists($ffile['file']).", is_readable: ".(int)@is_readable($ffile['file'])." size: ".@filesize($ffile['file']).')', 'notice', false, true);
2836
+ }
2837
+ }
2838
+
2839
+ private function bump_index() {
2840
+ global $iwp_backup_core;
2841
+ $youwhat = $this->whichone;
2842
+
2843
+ $timetaken = max(microtime(true)-$this->zip_microtime_start, 0.000001);
2844
+
2845
+ $itext = ($this->index == 0) ? '' : ($this->index+1);
2846
+ $full_path = $this->zip_basename.$itext.'.zip';
2847
+
2848
+ $checksums = $iwp_backup_core->which_checksums();
2849
+
2850
+ $checksum_description = '';
2851
+
2852
+ foreach ($checksums as $checksum) {
2853
+
2854
+ $cksum = hash_file($checksum, $full_path.'.tmp');
2855
+ $iwp_backup_core->jobdata_set($checksum.'-'.$youwhat.$this->index, $cksum);
2856
+ if ($checksum_description) $checksum_description .= ', ';
2857
+ $checksum_description .= "$checksum: $cksum";
2858
+
2859
+ }
2860
+
2861
+ $next_full_path = $this->zip_basename.($this->index+2).'.zip';
2862
+ # We touch the next zip before renaming the temporary file; this indicates that the backup for the entity is not *necessarily* finished
2863
+ touch($next_full_path.'.tmp');
2864
+
2865
+ if (file_exists($full_path.'.tmp') && filesize($full_path.'.tmp') > 0) {
2866
+ if (!rename($full_path.'.tmp', $full_path)) {
2867
+ $iwp_backup_core->log("Rename failed for $full_path.tmp");
2868
+ } else {
2869
+ $iwp_backup_core->something_useful_happened();
2870
+ }
2871
+ }
2872
+
2873
+ $kbsize = filesize($full_path)/1024;
2874
+ $rate = round($kbsize/$timetaken, 1);
2875
+ $iwp_backup_core->log("Created ".$this->whichone." zip (".$this->index.") - ".round($kbsize,1)." KB in ".round($timetaken,1)." s ($rate KB/s) (checksums: $checksum_description)");
2876
+ $this->zip_microtime_start = microtime(true);
2877
+
2878
+ # No need to add $itext here - we can just delete any temporary files for this zip
2879
+ $iwp_backup_core->clean_temporary_files('_'.$iwp_backup_core->nonce."-".$youwhat, 600);
2880
+
2881
+ $this->index++;
2882
+ $this->job_file_entities[$youwhat]['index'] = $this->index;
2883
+ $iwp_backup_core->jobdata_set('job_file_entities', $this->job_file_entities);
2884
+ }
2885
+
2886
+ /**
2887
+ * Returns the member of the array with key (int)0, as a new array. This function is used as a callback for array_map().
2888
+ *
2889
+ * @param Array $a - the array
2890
+ *
2891
+ * @return Array - with keys 'name' and 'type'
2892
+ */
2893
+ private function cb_get_name_base_type($a) {
2894
+ return array('name' => $a[0], 'type' => 'BASE TABLE');
2895
+ }
2896
+
2897
+ /**
2898
+ * Returns the members of the array with keys (int)0 and (int)1, as part of a new array.
2899
+ *
2900
+ * @param Array $a - the array
2901
+ *
2902
+ * @return Array - keys are 'name' and 'type'
2903
+ */
2904
+ private function cb_get_name_type($a) {
2905
+ return array('name' => $a[0], 'type' => $a[1]);
2906
+ }
2907
+
2908
+ /**
2909
+ * Returns the member of the array with key (string)'name'. This function is used as a callback for array_map().
2910
+ *
2911
+ * @param Array $a - the array
2912
+ *
2913
+ * @return Mixed - the value with key (string)'name'
2914
+ */
2915
+ private function cb_get_name($a) {
2916
+ return $a['name'];
2917
+ }
2918
+
2919
+ }
2920
+
2921
+ class IWP_MMB_WPDB_OtherDB extends wpdb {
2922
+ // This adjusted bail() does two things: 1) Never dies and 2) logs in the UD log
2923
+ public function bail( $message, $error_code = '500' ) {
2924
+ global $iwp_backup_core;
2925
+ 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.';
2926
+ $iwp_backup_core->log("WPDB_OtherDB error: $message ($error_code)");
2927
+ # Now do the things that would have been done anyway
2928
+ if ( class_exists( 'WP_Error' ) )
2929
+ $this->error = new WP_Error($error_code, $message);
2930
+ else
2931
+ $this->error = $message;
2932
+ return false;
2933
+ }
2934
+ }
2935
+
backup/backup.upload.php ADDED
@@ -0,0 +1,294 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if ( ! defined('ABSPATH') )
4
+ die();
5
+
6
+ abstract class IWP_MMB_UploadModule {
7
+
8
+ private $_options;
9
+
10
+ private $_instance_id;
11
+
12
+ /**
13
+ * Store options (within this class) for this remote storage module. There is also a parameter for saving to the permanent storage (i.e. database).
14
+ *
15
+ * @param array $options array of options to store
16
+ * @param boolean $save whether or not to also save the options to the database
17
+ * @param null|String $instance_id optionally set the instance ID for this instance at the same time. This is required if you have not already set an instance ID with set_instance_id()
18
+ * @return void|Boolean If saving to DB, then the result of the DB save operation is returned.
19
+ */
20
+ public function set_options($options, $save = false, $instance_id = null) {
21
+
22
+ $this->_options = $options;
23
+
24
+ if (null !== $instance_id) $this->set_instance_id($instance_id);
25
+
26
+ if ($save) return $this->save_options();
27
+
28
+ }
29
+
30
+ /**
31
+ * Saves the current options to the database. This is a private function; external callers should use set_options().
32
+ *
33
+ * @throws Exception if trying to save options without indicating an instance_id, or if the remote storage module does not have the multi-option capability
34
+ */
35
+ private function save_options() {
36
+
37
+ if (!$this->supports_feature('multi_options')) {
38
+ throw new Exception('set_options() can only be called on a storage method which supports multi_options (this module, '.$this->get_id().', does not)');
39
+ }
40
+
41
+ if (!$this->_instance_id) {
42
+ throw new Exception('set_options() requires an instance ID, but was called without setting one (either directly or via set_instance_id())');
43
+ }
44
+
45
+ global $iwp_backup_core;
46
+
47
+ $current_db_options = $iwp_backup_core->update_remote_storage_options_format($this->get_id());
48
+
49
+ if (is_wp_error($current_db_options)) {
50
+ throw new Exception('set_options(): options fetch/update failed ('.$current_db_options->get_error_code().': '.$current_db_options->get_error_message().')');
51
+ }
52
+
53
+ $current_db_options['settings'][$this->_instance_id] = $this->_options;
54
+
55
+ return IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_'.$this->get_id(), $current_db_options);
56
+
57
+ }
58
+
59
+ /**
60
+ * Retrieve default options for this remote storage module.
61
+ * This method would normally be over-ridden by the child.
62
+ *
63
+ * @return Array - an array of options
64
+ */
65
+ public function get_default_options() {
66
+ return array();
67
+ }
68
+
69
+ /**
70
+ * Retrieve a list of supported features for this storage method
71
+ * This method should be over-ridden by methods supporting new
72
+ * features.
73
+ *
74
+ * Keys are strings, and values are booleans.
75
+ *
76
+ * Currently known features:
77
+ *
78
+ * - multi_options : indicates that the remote storage module
79
+ * can handle its options being in the Feb-2017 multi-options
80
+ * format. N.B. This only indicates options handling, not any
81
+ * other multi-destination options.
82
+ *
83
+ * - multi_servers : not implemented yet: indicates that the
84
+ * remote storage module can handle multiple servers at backup
85
+ * time. This should not be specified without multi_options.
86
+ * multi_options without multi_servers is fine - it will just
87
+ * cause only the first entry in the options array to be used.
88
+ *
89
+ * @return Array - an array of supported features (any features not
90
+ * mentioned are assumed to not be supported)
91
+ */
92
+ public function get_supported_features() {
93
+ return array();
94
+ }
95
+
96
+ /**
97
+ * Over-ride this to allow methods to not use the hidden version field, if they do not output any settings (to prevent their saved settings being over-written by just this hidden field
98
+ *
99
+ * @return [boolean] - return true to output the version field or false to not output the field
100
+ */
101
+ public function print_shared_settings_fields() {
102
+ return true;
103
+ }
104
+
105
+ /**
106
+ * Prints out the configuration section for a particular module
107
+ */
108
+ abstract function config_print();
109
+
110
+ /**
111
+ * Supplies the list of keys for options to be saved in the backup job.
112
+ */
113
+ public function get_credentials() {
114
+ $keys = array('IWP_ssl_disableverify', 'IWP_ssl_nossl', 'IWP_ssl_useservercerts');
115
+ if (!$this->supports_feature('multi_servers')) $keys[] = 'IWP_'.$this->get_id();
116
+ return $keys;
117
+ }
118
+
119
+
120
+
121
+ /**
122
+ * Returns a space-separated list of CSS classes suitable for rows in the configuration section
123
+ *
124
+ * @returns String - the list of CSS classes
125
+ */
126
+ public function get_css_classes() {
127
+ $classes = 'IWPmethod '.$this->get_id();
128
+ if ('' != $this->_instance_id) $classes .= ' '.$this->get_id().'-'.$this->_instance_id;
129
+ return $classes;
130
+ }
131
+
132
+ /**
133
+ * Get the backup method identifier for this class
134
+ *
135
+ * @return String - the identifier
136
+ */
137
+ public function get_id() {
138
+ $class = get_class($this);
139
+ // IWP_MMB_UploadModule_
140
+ return substr($class, 21);
141
+ }
142
+
143
+ /**
144
+ * Sets the instance ID - for supporting multi_options
145
+ *
146
+ * @param String $instance_id - the instance ID
147
+ */
148
+ public function set_instance_id($instance_id) {
149
+ $this->_instance_id = $instance_id;
150
+ }
151
+
152
+ /**
153
+ * Sets the instance ID - for supporting multi_options
154
+ *
155
+ * @returns String the instance ID
156
+ */
157
+ public function get_instance_id() {
158
+ return $this->_instance_id;
159
+ }
160
+
161
+ /**
162
+ * Check whether this storage module supports a mentioned feature
163
+ *
164
+ * @param String $feature - the feature concerned
165
+ *
166
+ * @returns Boolean
167
+ */
168
+ public function supports_feature($feature) {
169
+ return in_array($feature, $this->get_supported_features());
170
+ }
171
+
172
+ /**
173
+ * Retrieve options for this remote storage module
174
+ *
175
+ * @uses get_default_options
176
+ *
177
+ * @return Array - array of options. This will include default values for any options not set.
178
+ */
179
+ public function get_options() {
180
+
181
+ global $iwp_backup_core;
182
+
183
+ $supports_multi_options = $this->supports_feature('multi_options');
184
+
185
+ if (is_array($this->_options)) {
186
+
187
+ // First, prioritise any options that were explicitly set. This is the eventual goal for all storage modules.
188
+ $options = $this->_options;
189
+
190
+ } elseif (is_callable(array($this, 'get_opts'))) {
191
+
192
+ // Next, get any options available via a legacy / over-ride method.
193
+
194
+ if ($supports_multi_options) {
195
+ // This is forbidden, because get_opts() is legacy and is for methods that do not support multi-options. Supporting multi-options leads to the array format being updated, which will then break get_opts().
196
+ die('Fatal error: method '.$this->get_id().' both supports multi_options and provides a get_opts method');
197
+ }
198
+
199
+ $options = $this->get_opts();
200
+
201
+ } else {
202
+
203
+ // Next, look for job options (which in turn, falls back to saved settings if no job options were set)
204
+
205
+ $options = $iwp_backup_core->get_job_option('IWP_'.$this->get_id());
206
+ if (!is_array($options)) $options = array();
207
+
208
+ if ($supports_multi_options) {
209
+
210
+ if (!isset($options['version'])) {
211
+ $options_full = $iwp_backup_core->update_remote_storage_options_format($this->get_id());
212
+
213
+ if (is_wp_error($options_full)) {
214
+ $iwp_backup_core->log("Options retrieval failure: ".$options_full->get_error_code().": ".$options_full->get_error_message()." (".json_encode($options_full->get_error_data()).")");
215
+ return array();
216
+ }
217
+
218
+ } else {
219
+ $options_full = $options;
220
+ }
221
+
222
+ // IWP_MMB_UploadModule::get_options() is for getting the current instance's options. So, this branch (going via the job option) is a legacy route, and hence we just give back the first one. The non-legacy route is to call the set_options() method externally.
223
+ $options = reset($options_full['settings']);
224
+
225
+ if (false === $options) {
226
+ $iwp_backup_core->log("Options retrieval failure (no options set)");
227
+ return array();
228
+ }
229
+ $instance_id = key($options_full['settings']);
230
+ $this->set_options($options, false, $instance_id);
231
+
232
+ }
233
+
234
+ }
235
+
236
+ $options = apply_filters(
237
+ 'IWP_backupmodule_get_options',
238
+ wp_parse_args($options, $this->get_default_options()),
239
+ $this
240
+ );
241
+
242
+ return $options;
243
+
244
+ }
245
+
246
+ public function jobdata_set($key, $value, $legacy_key = null) {
247
+
248
+ $instance_key = $this->get_id().'-'.($this->_instance_id ? $this->_instance_id : 'no_instance');
249
+
250
+ global $iwp_backup_core;
251
+
252
+ $instance_data = $iwp_backup_core->jobdata_get($instance_key);
253
+
254
+ if (!is_array($instance_data)) $instance_data = array();
255
+
256
+ $instance_data[$key] = $value;
257
+
258
+ $iwp_backup_core->jobdata_set($instance_key, $instance_data);
259
+
260
+ if (is_string($legacy_key)) $iwp_backup_core->jobdata_set($legacy_key, $value);
261
+
262
+ }
263
+
264
+ public function jobdata_get($key, $default = null, $legacy_key = null) {
265
+
266
+ $instance_key = $this->get_id().'-'.($this->_instance_id ? $this->_instance_id : 'no_instance');
267
+
268
+ global $iwp_backup_core;
269
+
270
+ $instance_data = $iwp_backup_core->jobdata_get($instance_key);
271
+
272
+ if (is_array($instance_data) && isset($instance_data[$key])) return $instance_data[$key];
273
+
274
+ return is_string($legacy_key) ? $iwp_backup_core->jobdata_get($legacy_key, $default) : $default;
275
+
276
+ }
277
+
278
+ public function jobdata_delete($key, $legacy_key = null) {
279
+
280
+ $instance_key = $this->get_id().'-'.($this->_instance_id ? $this->_instance_id : 'no_instance');
281
+
282
+ global $iwp_backup_core;
283
+
284
+ $instance_data = $iwp_backup_core->jobdata_get($instance_key);
285
+
286
+ if (is_array($instance_data) && isset($instance_data[$key])) {
287
+ unset($instance_data[$key]);
288
+ $iwp_backup_core->jobdata_set($instance_key, $instance_data);
289
+ }
290
+
291
+ if (is_string($legacy_key)) $iwp_backup_core->jobdata_delete($legacy_key);
292
+
293
+ }
294
+ }
backup/backup.zip.class.php ADDED
@@ -0,0 +1,342 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if ( ! defined('ABSPATH') )
4
+ die();
5
+
6
+ if (class_exists('ZipArchive')):
7
+ class IWP_MMB_ZipArchive extends ZipArchive {
8
+ public $last_error = 'Unknown: ZipArchive does not return error messages';
9
+ }
10
+ endif;
11
+
12
+ # A ZipArchive compatibility layer, with behaviour sufficient for our usage of ZipArchive
13
+ class IWP_MMB_PclZip {
14
+
15
+ protected $pclzip;
16
+ protected $path;
17
+ protected $addfiles;
18
+ protected $adddirs;
19
+ private $statindex;
20
+ private $include_mtime = false;
21
+ public $last_error;
22
+
23
+ public function __construct() {
24
+ $this->addfiles = array();
25
+ $this->adddirs = array();
26
+ // 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
27
+ global $iwp_backup_core;
28
+ if (!defined('PCLZIP_TEMPORARY_DIR')) define('PCLZIP_TEMPORARY_DIR', trailingslashit($iwp_backup_core->backups_dir_location()));
29
+ }
30
+
31
+ # Used to include mtime in statindex (by default, not done - to save memory; probably a bit paranoid)
32
+ public function ud_include_mtime() {
33
+ $this->include_mtime = true;
34
+ }
35
+
36
+ public function __get($name) {
37
+ if ($name == 'numFiles' || $name == 'numAll') {
38
+
39
+ if (empty($this->pclzip)) return false;
40
+
41
+ $statindex = $this->pclzip->listContent();
42
+
43
+ if (empty($statindex)) {
44
+ $this->statindex = array();
45
+ // We return a value that is == 0, but allowing a PclZip error to be detected (PclZip returns 0 in the case of an error).
46
+ if (0 === $statindex) $this->last_error = $this->pclzip->errorInfo(true);
47
+ return (0 === $statindex) ? false : 0;
48
+ }
49
+
50
+ if ($name == 'numFiles') {
51
+
52
+ $result = array();
53
+ foreach ($statindex as $i => $file) {
54
+ if (!isset($statindex[$i]['folder']) || 0 == $statindex[$i]['folder']) {
55
+ $result[] = $file;
56
+ }
57
+ unset($statindex[$i]);
58
+ }
59
+
60
+ $this->statindex=$result;
61
+
62
+ } else {
63
+ $this->statindex=$statindex;
64
+ }
65
+
66
+ return count($this->statindex);
67
+ }
68
+
69
+ return null;
70
+
71
+ }
72
+
73
+ public function statIndex($i) {
74
+ if (empty($this->statindex[$i])) return array('name' => null, 'size' => 0);
75
+ $v = array('name' => $this->statindex[$i]['filename'], 'size' => $this->statindex[$i]['size']);
76
+ if ($this->include_mtime) $v['mtime'] = $this->statindex[$i]['mtime'];
77
+ return $v;
78
+ }
79
+
80
+ public function open($path, $flags = 0) {
81
+
82
+ if(!class_exists('PclZip')) include_once(ABSPATH.'/wp-admin/includes/class-pclzip.php');
83
+ if(!class_exists('PclZip')) {
84
+ $this->last_error = "No PclZip class was found";
85
+ return false;
86
+ }
87
+
88
+ # Route around PHP bug (exact version with the problem not known)
89
+ $ziparchive_create_match = (version_compare(PHP_VERSION, '5.2.12', '>') && defined('ZIPARCHIVE::CREATE')) ? ZIPARCHIVE::CREATE : 1;
90
+
91
+ if ($flags == $ziparchive_create_match && file_exists($path)) @unlink($path);
92
+
93
+ $this->pclzip = new PclZip($path);
94
+
95
+ if (empty($this->pclzip)) {
96
+ $this->last_error = 'Could not get a PclZip object';
97
+ return false;
98
+ }
99
+
100
+ # Make the empty directory we need to implement addEmptyDir()
101
+ global $iwp_backup_core;
102
+ $iwp_backup_dir = $iwp_backup_core->backups_dir_location();
103
+ if (!is_dir($iwp_backup_dir.'/emptydir') && !mkdir($iwp_backup_dir.'/emptydir')) {
104
+ $this->last_error = "Could not create empty directory ($iwp_backup_dir/emptydir)";
105
+ return false;
106
+ }
107
+
108
+ $this->path = $path;
109
+
110
+ return true;
111
+
112
+ }
113
+
114
+ # Do the actual write-out - it is assumed that close() is where this is done. Needs to return true/false
115
+ public function close() {
116
+ if (empty($this->pclzip)) {
117
+ $this->last_error = 'Zip file was not opened';
118
+ return false;
119
+ }
120
+
121
+ global $iwp_backup_core;
122
+ $iwp_backup_dir = $iwp_backup_core->backups_dir_location();
123
+
124
+ $activity = false;
125
+
126
+ # Add the empty directories
127
+ foreach ($this->adddirs as $dir) {
128
+ if (false == $this->pclzip->add($iwp_backup_dir.'/emptydir', PCLZIP_OPT_REMOVE_PATH, $iwp_backup_dir.'/emptydir', PCLZIP_OPT_ADD_PATH, $dir)) {
129
+ $this->last_error = $this->pclzip->errorInfo(true);
130
+ return false;
131
+ }
132
+ $activity = true;
133
+ }
134
+
135
+ foreach ($this->addfiles as $rdirname => $adirnames) {
136
+ foreach ($adirnames as $adirname => $files) {
137
+ if (false == $this->pclzip->add($files, PCLZIP_OPT_REMOVE_PATH, $rdirname, PCLZIP_OPT_ADD_PATH, $adirname)) {
138
+ $this->last_error = $this->pclzip->errorInfo(true);
139
+ return false;
140
+ }
141
+ $activity = true;
142
+ }
143
+ unset($this->addfiles[$rdirname]);
144
+ }
145
+
146
+ $this->pclzip = false;
147
+ $this->addfiles = array();
148
+ $this->adddirs = array();
149
+
150
+ clearstatcache();
151
+ if ($activity && filesize($this->path) < 50) {
152
+ $this->last_error = "Write failed - unknown cause (check your file permissions)";
153
+ return false;
154
+ }
155
+
156
+ return true;
157
+ }
158
+
159
+ # 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.
160
+ public function addFile($file, $add_as) {
161
+ # 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).
162
+ $rdirname = dirname($file);
163
+ $adirname = dirname($add_as);
164
+ $this->addfiles[$rdirname][$adirname][] = $file;
165
+ }
166
+
167
+ # PclZip doesn't have a direct way to do this
168
+ public function addEmptyDir($dir) {
169
+ $this->adddirs[] = $dir;
170
+ }
171
+
172
+ public function extract($path_to_extract, $path) {
173
+ return $this->pclzip->extract(PCLZIP_OPT_PATH, $path_to_extract, PCLZIP_OPT_BY_NAME, $path);
174
+ }
175
+
176
+ }
177
+
178
+ class IWP_MMB_BinZip extends IWP_MMB_PclZip {
179
+
180
+ private $binzip;
181
+
182
+ public function __construct() {
183
+ global $IWP_backup;
184
+ $this->binzip = $IWP_backup->binzip;
185
+ if (!is_string($this->binzip)) {
186
+ $this->last_error = "No binary zip was found";
187
+ return false;
188
+ }
189
+ return parent::__construct();
190
+ }
191
+
192
+ public function addFile($file, $add_as) {
193
+
194
+ global $iwp_backup_core;
195
+ # Get the directory that $add_as is relative to
196
+ $base = $iwp_backup_core->str_lreplace($add_as, '', $file);
197
+
198
+ if ($file == $base) {
199
+ // Shouldn't happen; but see: https://bugs.php.net/bug.php?id=62119
200
+ $iwp_backup_core->log("File skipped due to unexpected name mismatch (locale: ".setlocale(LC_CTYPE, "0")."): file=$file add_as=$add_as", 'notice', false, true);
201
+ } else {
202
+ $rdirname = untrailingslashit($base);
203
+ # Note: $file equals $rdirname/$add_as
204
+ $this->addfiles[$rdirname][] = $add_as;
205
+ }
206
+
207
+ }
208
+
209
+ # The standard zip binary cannot list; so we use PclZip for that
210
+ # Do the actual write-out - it is assumed that close() is where this is done. Needs to return true/false
211
+ public function close() {
212
+
213
+ if (empty($this->pclzip)) {
214
+ $this->last_error = 'Zip file was not opened';
215
+ return false;
216
+ }
217
+
218
+ global $iwp_backup_core, $IWP_backup;
219
+ $iwp_backup_dir = $iwp_backup_core->backups_dir_location();
220
+
221
+ $activity = false;
222
+
223
+ # BinZip does not like zero-sized zip files
224
+ if (file_exists($this->path) && 0 == filesize($this->path)) @unlink($this->path);
225
+
226
+ $descriptorspec = array(
227
+ 0 => array('pipe', 'r'),
228
+ 1 => array('pipe', 'w'),
229
+ 2 => array('pipe', 'w')
230
+ );
231
+ $exec = $this->binzip;
232
+ if (defined('IWP_BINZIP_OPTS') && IWP_BINZIP_OPTS) $exec .= ' '.IWP_BINZIP_OPTS;
233
+ $exec .= " -v -@ ".escapeshellarg($this->path);
234
+
235
+ $last_recorded_alive = time();
236
+ $something_useful_happened = $iwp_backup_core->something_useful_happened;
237
+ $orig_size = file_exists($this->path) ? filesize($this->path) : 0;
238
+ $last_size = $orig_size;
239
+ clearstatcache();
240
+
241
+ $added_dirs_yet = false;
242
+
243
+ # If there are no files to add, but there are empty directories, then we need to make sure the directories actually get added
244
+ if (0 == count($this->addfiles) && 0 < count($this->adddirs)) {
245
+ $dir = realpath($IWP_backup->make_zipfile_source);
246
+ $this->addfiles[$dir] = '././.';
247
+ }
248
+ // Loop over each destination directory name
249
+ foreach ($this->addfiles as $rdirname => $files) {
250
+
251
+ $process = proc_open($exec, $descriptorspec, $pipes, $rdirname);
252
+
253
+ if (!is_resource($process)) {
254
+ $iwp_backup_core->log('BinZip error: proc_open failed');
255
+ $this->last_error = 'BinZip error: proc_open failed';
256
+ return false;
257
+ }
258
+
259
+ if (!$added_dirs_yet) {
260
+ # Add the directories - (in fact, with binzip, non-empty directories automatically have their entries added; but it doesn't hurt to add them explicitly)
261
+ foreach ($this->adddirs as $dir) {
262
+ fwrite($pipes[0], $dir."/\n");
263
+ }
264
+ $added_dirs_yet=true;
265
+ }
266
+
267
+ $read = array($pipes[1], $pipes[2]);
268
+ $except = null;
269
+
270
+ if (!is_array($files) || 0 == count($files)) {
271
+ fclose($pipes[0]);
272
+ $write = array();
273
+ } else {
274
+ $write = array($pipes[0]);
275
+ }
276
+
277
+ while ((!feof($pipes[1]) || !feof($pipes[2]) || (is_array($files) && count($files)>0)) && false !== ($changes = @stream_select($read, $write, $except, 0, 200000))) {
278
+
279
+ if (is_array($write) && in_array($pipes[0], $write) && is_array($files) && count($files)>0) {
280
+ $file = array_pop($files);
281
+ // Send the list of files on stdin
282
+ fwrite($pipes[0], $file."\n");
283
+ if (0 == count($files)) fclose($pipes[0]);
284
+ }
285
+
286
+ if (is_array($read) && in_array($pipes[1], $read)) {
287
+ $w = fgets($pipes[1]);
288
+ // Logging all this really slows things down; use debug to mitigate
289
+ if ($w && $IWP_backup->debug) $iwp_backup_core->log("Output from zip: ".trim($w), 'debug');
290
+ if (time() > $last_recorded_alive + 5) {
291
+ $iwp_backup_core->record_still_alive();
292
+ $last_recorded_alive = time();
293
+ }
294
+ if (file_exists($this->path)) {
295
+ $new_size = @filesize($this->path);
296
+ if (!$something_useful_happened && $new_size > $orig_size + 20) {
297
+ $iwp_backup_core->something_useful_happened();
298
+ $something_useful_happened = true;
299
+ }
300
+ clearstatcache();
301
+ # Log when 20% bigger or at least every 50MB
302
+ if ($new_size > $last_size*1.2 || $new_size > $last_size + 52428800) {
303
+ $iwp_backup_core->log(basename($this->path).sprintf(": size is now: %.2f MB", round($new_size/1048576,1)));
304
+ $last_size = $new_size;
305
+ }
306
+ }
307
+ }
308
+
309
+ if (is_array($read) && in_array($pipes[2], $read)) {
310
+ $last_error = fgets($pipes[2]);
311
+ if (!empty($last_error)) $this->last_error = rtrim($last_error);
312
+ }
313
+
314
+ // Re-set
315
+ $read = array($pipes[1], $pipes[2]);
316
+ $write = (is_array($files) && count($files) >0) ? array($pipes[0]) : array();
317
+ $except = null;
318
+
319
+ }
320
+
321
+ fclose($pipes[1]);
322
+ fclose($pipes[2]);
323
+
324
+ $ret = proc_close($process);
325
+
326
+ if ($ret != 0 && $ret != 12) {
327
+ if ($ret < 128) {
328
+ $iwp_backup_core->log("Binary zip: error (code: $ret - look it up in the Diagnostics section of the zip manual at http://www.info-zip.org/mans/zip.html for interpretation... and also check that your hosting account quota is not full)");
329
+ } else {
330
+ $iwp_backup_core->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)");
331
+ }
332
+ if (!empty($w) && !$IWP_backup->debug) $iwp_backup_core->log("Last output from zip: ".trim($w), 'debug');
333
+ return false;
334
+ }
335
+
336
+ unset($this->addfiles[$rdirname]);
337
+ }
338
+
339
+ return true;
340
+ }
341
+
342
+ }
backup/class.semaphore.php ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // Adapted from WP Social under the GPL - thanks to Alex King (https://github.com/crowdfavorite/wp-social)
3
+ /**
4
+ * Semaphore Lock Management
5
+ */
6
+
7
+ if ( ! defined('ABSPATH') )
8
+ die();
9
+
10
+ class IWP_MMB_Semaphore {
11
+
12
+ /**
13
+ * Initializes the semaphore object.
14
+ *
15
+ * @static
16
+ */
17
+ public static function factory() {
18
+ return new self;
19
+ }
20
+
21
+ /**
22
+ * @var bool
23
+ */
24
+ protected $lock_broke = false;
25
+
26
+ public $lock_name = 'lock';
27
+
28
+ /**
29
+ * Attempts to start the lock. If the rename works, the lock is started.
30
+ *
31
+ * @return bool
32
+ */
33
+ public function lock() {
34
+ global $wpdb, $iwp_backup_core;
35
+
36
+ // Attempt to set the lock
37
+ $affected = $wpdb->query("
38
+ UPDATE $wpdb->options
39
+ SET option_name = 'IWP_locked_".$this->lock_name."'
40
+ WHERE option_name = 'IWP_unlocked_".$this->lock_name."'
41
+ ");
42
+
43
+ if ($affected == '0' and !$this->stuck_check()) {
44
+ $iwp_backup_core->log('Semaphore lock ('.$this->lock_name.', '.$wpdb->options.') failed (line '.__LINE__.')');
45
+ return false;
46
+ }
47
+
48
+ // Check to see if all processes are complete
49
+ $affected = $wpdb->query("
50
+ UPDATE $wpdb->options
51
+ SET option_value = CAST(option_value AS UNSIGNED) + 1
52
+ WHERE option_name = 'IWP_semaphore_".$this->lock_name."'
53
+ AND option_value = '0'
54
+ ");
55
+ if ($affected != '1') {
56
+ if (!$this->stuck_check()) {
57
+ $iwp_backup_core->log('Semaphore lock ('.$this->lock_name.', '.$wpdb->options.') failed (line '.__LINE__.')');
58
+ return false;
59
+ }
60
+
61
+ // Reset the semaphore to 1
62
+ $wpdb->query("
63
+ UPDATE $wpdb->options
64
+ SET option_value = '1'
65
+ WHERE option_name = 'IWP_semaphore_".$this->lock_name."'
66
+ ");
67
+
68
+ $iwp_backup_core->log('Semaphore ('.$this->lock_name.', '.$wpdb->options.') reset to 1');
69
+ }
70
+
71
+ // Set the lock time
72
+ $wpdb->query($wpdb->prepare("
73
+ UPDATE $wpdb->options
74
+ SET option_value = %s
75
+ WHERE option_name = 'IWP_last_lock_time_".$this->lock_name."'
76
+ ", current_time('mysql', 1)));
77
+ $iwp_backup_core->log('Set semaphore last lock ('.$this->lock_name.') time to '.current_time('mysql', 1));
78
+
79
+ $iwp_backup_core->log('Semaphore lock ('.$this->lock_name.') complete');
80
+ update_option('IWP_backup_status', '1');
81
+ return true;
82
+ }
83
+
84
+ /**
85
+ * Increment the semaphore.
86
+ *
87
+ * @param array $filters
88
+ * @return Social_Semaphore
89
+ */
90
+ public function increment(array $filters = array()) {
91
+ global $wpdb;
92
+
93
+ if (count($filters)) {
94
+ // Loop through all of the filters and increment the semaphore
95
+ foreach ($filters as $priority) {
96
+ for ($i = 0, $j = count($priority); $i < $j; ++$i) {
97
+ $this->increment();
98
+ }
99
+ }
100
+ }
101
+ else {
102
+ $wpdb->query("
103
+ UPDATE $wpdb->options
104
+ SET option_value = CAST(option_value AS UNSIGNED) + 1
105
+ WHERE option_name = 'IWP_semaphore_".$this->lock_name."'
106
+ ");
107
+ $iwp_backup_core->log('Incremented the semaphore ('.$this->lock_name.') by 1');
108
+ }
109
+
110
+ return $this;
111
+ }
112
+
113
+ /**
114
+ * Decrements the semaphore.
115
+ *
116
+ * @return void
117
+ */
118
+ public function decrement() {
119
+ global $wpdb, $iwp_backup_core;
120
+
121
+ $wpdb->query("
122
+ UPDATE $wpdb->options
123
+ SET option_value = CAST(option_value AS UNSIGNED) - 1
124
+ WHERE option_name = 'IWP_semaphore_".$this->lock_name."'
125
+ AND CAST(option_value AS UNSIGNED) > 0
126
+ ");
127
+ $iwp_backup_core->log('Decremented the semaphore ('.$this->lock_name.') by 1');
128
+ }
129
+
130
+ /**
131
+ * Unlocks the process.
132
+ *
133
+ * @return bool
134
+ */
135
+ public function unlock() {
136
+ global $wpdb, $iwp_backup_core;
137
+
138
+ // Decrement for the master process.
139
+ $this->decrement();
140
+
141
+ $result = $wpdb->query("
142
+ UPDATE $wpdb->options
143
+ SET option_name = 'IWP_unlocked_".$this->lock_name."'
144
+ WHERE option_name = 'IWP_locked_".$this->lock_name."'
145
+ ");
146
+ update_option('IWP_backup_status', '0');
147
+ if ($result == '1') {
148
+ $iwp_backup_core->log('Semaphore ('.$this->lock_name.') unlocked');
149
+ return true;
150
+ }
151
+
152
+ $iwp_backup_core->log('Semaphore ('.$this->lock_name.', '.$wpdb->options.') still locked ('.$result.')');
153
+ return false;
154
+ }
155
+
156
+ /**
157
+ * Attempts to jiggle the stuck lock loose.
158
+ *
159
+ * @return bool
160
+ */
161
+ private function stuck_check() {
162
+ global $wpdb, $iwp_backup_core;
163
+
164
+ // Check to see if we already broke the lock.
165
+ if ($this->lock_broke) {
166
+ return true;
167
+ }
168
+
169
+ $current_time = current_time('mysql', 1);
170
+ $three_minutes_before = gmdate('Y-m-d H:i:s', time()-(defined('IWP_SEMAPHORE_LOCK_WAIT') ? IWP_SEMAPHORE_LOCK_WAIT : 180));
171
+
172
+ $affected = $wpdb->query($wpdb->prepare("
173
+ UPDATE $wpdb->options
174
+ SET option_value = %s
175
+ WHERE option_name = 'IWP_last_lock_time_".$this->lock_name."'
176
+ AND option_value <= %s
177
+ ", $current_time, $three_minutes_before));
178
+
179
+ if ('1' == $affected) {
180
+ $iwp_backup_core->log('Semaphore ('.$this->lock_name.', '.$wpdb->options.') was stuck, set lock time to '.$current_time);
181
+ $this->lock_broke = true;
182
+ return true;
183
+ }
184
+
185
+ return false;
186
+ }
187
+
188
+ }
backup/dropbox.php ADDED
@@ -0,0 +1,525 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // https://www.dropbox.com/developers/apply?cont=/developers/apps
4
+ if ( ! defined('ABSPATH') )
5
+ die();
6
+
7
+ if (!class_exists('IWP_MMB_UploadModule')) require_once($GLOBALS['iwp_mmb_plugin_dir'].'/backup/backup.upload.php');
8
+
9
+ # Fix a potential problem for users who had the short-lived 1.12.35-1.12.38 free versions (see: https://wordpress.org/support/topic/1-12-37-dropbox-auth-broken/page/2/#post-8981457)
10
+ # Can be removed after a few months
11
+ $potential_options = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_dropbox');
12
+ if (is_array($potential_options) && isset($potential_options['version']) && isset($potential_options['settings']) && array() === $potential_options['settings']) {
13
+ // Wipe it, which wil force its re-creation in proper format
14
+ IWP_MMB_Backup_Options::delete_iwp_backup_option('IWP_dropbox');
15
+ }
16
+
17
+
18
+ class IWP_MMB_UploadModule_dropbox extends IWP_MMB_UploadModule {
19
+
20
+ private $current_file_hash;
21
+ private $current_file_size;
22
+ private $dropbox_object;
23
+ private $uploaded_offset;
24
+ private $upload_tick;
25
+
26
+ public function chunked_callback($offset, $uploadid, $fullpath = false) {
27
+ global $iwp_backup_core;
28
+
29
+ // Update upload ID
30
+ $iwp_backup_core->jobdata_set('IWP_dbid_'.$this->current_file_hash, $uploadid);
31
+ $iwp_backup_core->jobdata_set('IWP_dbof_'.$this->current_file_hash, $offset);
32
+
33
+ $time_now = microtime(true);
34
+
35
+ $time_since_last_tick = $time_now - $this->upload_tick;
36
+ $data_since_last_tick = $offset - $this->uploaded_offset;
37
+
38
+ $this->upload_tick = $time_now;
39
+ $this->uploaded_offset = $offset;
40
+
41
+ $chunk_size = $iwp_backup_core->jobdata_get('dropbox_chunk_size', 1048576);
42
+ // Don't go beyond 10MB, or change the chunk size after the last segment
43
+ if ($chunk_size < 10485760 && $this->current_file_size > 0 && $offset < $this->current_file_size) {
44
+ $job_run_time = $time_now - $iwp_backup_core->job_time_ms;
45
+ if ($time_since_last_tick < 10) {
46
+ $upload_rate = $data_since_last_tick / max($time_since_last_tick, 1);
47
+ $upload_secs = min(floor($job_run_time), 10);
48
+ if ($job_run_time < 15) $upload_secs = max(6, $job_run_time*0.6);
49
+ $new_chunk = max(min($upload_secs * $upload_rate * 0.9, 10485760), 1048576);
50
+ $new_chunk = $new_chunk - ($new_chunk % 524288);
51
+ $chunk_size = (int)$new_chunk;
52
+ $this->dropbox_object->setChunkSize($chunk_size);
53
+ $iwp_backup_core->jobdata_set('dropbox_chunk_size', $chunk_size);
54
+ }
55
+ }
56
+
57
+ if ($this->current_file_size > 0) {
58
+ $percent = round(100*($offset/$this->current_file_size),1);
59
+ $iwp_backup_core->record_uploaded_chunk($percent, "$uploadid, $offset, ".round($chunk_size/1024, 1)." KB", $fullpath);
60
+ } else {
61
+ $iwp_backup_core->log("Dropbox: Chunked Upload: $offset bytes uploaded");
62
+ // This act is done by record_uploaded_chunk, and helps prevent overlapping runs
63
+ touch($fullpath);
64
+ }
65
+ }
66
+
67
+ public function get_supported_features() {
68
+ // This options format is handled via only accessing options via $this->get_options()
69
+ return array('multi_options');
70
+ }
71
+
72
+ public function get_default_options() {
73
+ return array(
74
+ 'appkey' => '',
75
+ 'secret' => '',
76
+ 'folder' => '',
77
+ 'tk_access_token' => '',
78
+ );
79
+ }
80
+
81
+ public function backup($backup_array) {
82
+
83
+ global $iwp_backup_core;
84
+
85
+ $opts = $this->get_options();
86
+
87
+ if (empty($opts['tk_access_token'])) {
88
+ $iwp_backup_core->log('You do not appear to be authenticated with Dropbox (1)');
89
+ $iwp_backup_core->log(__('You do not appear to be authenticated with Dropbox','InfiniteWP'), 'error');
90
+ return false;
91
+ }
92
+
93
+ // 28 June 2017
94
+ $use_api_ver = 2;
95
+
96
+ if (empty($opts['tk_request_token'])) {
97
+ $iwp_backup_core->log("Dropbox: begin cloud upload (using API version $use_api_ver with OAuth v2 token)");
98
+ } else {
99
+ $iwp_backup_core->log("Dropbox: begin cloud upload (using API version $use_api_ver with OAuth v1 token)");
100
+ }
101
+
102
+ $chunk_size = $iwp_backup_core->jobdata_get('dropbox_chunk_size', 1048576);
103
+
104
+ try {
105
+ $dropbox = $this->bootstrap();
106
+ if (false === $dropbox) throw new Exception(__('You do not appear to be authenticated with Dropbox', 'InfiniteWP'));
107
+ $iwp_backup_core->log("Dropbox: access gained; setting chunk size to: ".round($chunk_size/1024, 1)." KB");
108
+ $dropbox->setChunkSize($chunk_size);
109
+ } catch (Exception $e) {
110
+ $iwp_backup_core->log('Dropbox error when trying to gain access: '.$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
111
+ $iwp_backup_core->log(sprintf(__('Dropbox error: %s (see log file for more)','InfiniteWP'), $e->getMessage()), 'error');
112
+ return false;
113
+ }
114
+
115
+ $iwp_backup_dir = $iwp_backup_core->backups_dir_location();
116
+
117
+
118
+ foreach ($backup_array as $file) {
119
+
120
+ $available_quota = -1;
121
+
122
+ // If we experience any failures collecting account info, then carry on anyway
123
+ try {
124
+
125
+ /*
126
+ Quota information is no longer provided with account information a new call to quotaInfo must be made to get this information.
127
+ */
128
+ if (1 == $use_api_ver) {
129
+ $quotaInfo = $dropbox->accountInfo();
130
+ } else {
131
+ $quotaInfo = $dropbox->quotaInfo();
132
+ }
133
+
134
+ if ($quotaInfo['code'] != "200") {
135
+ $message = "Dropbox account/info did not return HTTP 200; returned: ". $quotaInfo['code'];
136
+ } elseif (!isset($quotaInfo['body'])) {
137
+ $message = "Dropbox account/info did not return the expected data";
138
+ } else {
139
+ $body = $quotaInfo['body'];
140
+ if (isset($body->quota_info)) {
141
+ $quota_info = $body->quota_info;
142
+ $total_quota = $quota_info->quota;
143
+ $normal_quota = $quota_info->normal;
144
+ $shared_quota = $quota_info->shared;
145
+ $available_quota = $total_quota - ($normal_quota + $shared_quota);
146
+ $message = "Dropbox quota usage: normal=".round($normal_quota/1048576,1)." MB, shared=".round($shared_quota/1048576,1)." MB, total=".round($total_quota/1048576,1)." MB, available=".round($available_quota/1048576,1)." MB";
147
+ } else {
148
+ $total_quota = max($body->allocation->allocated, 1);
149
+ $used = $body->used;
150
+ /* check here to see if the account is a team account and if so use the other used value
151
+ This will give us their total usage including their individual account and team account */
152
+ if (isset($body->allocation->used)) $used = $body->allocation->used;
153
+ $available_quota = $total_quota - $used;
154
+ $message = "Dropbox quota usage: used=".round($used/1048576,1)." MB, total=".round($total_quota/1048576,1)." MB, available=".round($available_quota/1048576,1)." MB";
155
+ }
156
+ }
157
+ $iwp_backup_core->log($message);
158
+ } catch (Exception $e) {
159
+ $iwp_backup_core->log("Dropbox error: exception (".get_class($e).") occurred whilst getting account info: ".$e->getMessage());
160
+ }
161
+
162
+ $file_success = 1;
163
+
164
+ $hash = md5($file);
165
+ $this->current_file_hash = $hash;
166
+
167
+ $filesize = filesize($iwp_backup_dir.'/'.$file);
168
+ $this->current_file_size = $filesize;
169
+
170
+ // Into KB
171
+ $filesize = $filesize/1024;
172
+ $microtime = microtime(true);
173
+
174
+ if ($upload_id = $iwp_backup_core->jobdata_get('IWP_dbid_'.$hash)) {
175
+ # Resume
176
+ $offset = $iwp_backup_core->jobdata_get('IWP_dbof_'.$hash);
177
+ $iwp_backup_core->log("This is a resumption: $offset bytes had already been uploaded");
178
+ } else {
179
+ $offset = 0;
180
+ $upload_id = null;
181
+ }
182
+
183
+ // We don't actually abort now - there's no harm in letting it try and then fail
184
+ if ($available_quota != -1 && $available_quota < ($filesize-$offset)) {
185
+ $iwp_backup_core->log("File upload expected to fail: file data remaining to upload ($file) size is ".($filesize-$offset)." b (overall file size; .".($filesize*1024)." b), whereas available quota is only $available_quota b");
186
+ }
187
+
188
+
189
+ $ufile = apply_filters('IWP_dropbox_modpath', $file, $this);
190
+
191
+ $iwp_backup_core->log("Dropbox: Attempt to upload: $file to: $ufile");
192
+
193
+ $this->upload_tick = microtime(true);
194
+ $this->uploaded_offset = $offset;
195
+
196
+ try {
197
+ $response = $dropbox->chunkedUpload($iwp_backup_dir.'/'.$file, '', $ufile, true, $offset, $upload_id, array($this, 'chunked_callback'));
198
+ if (empty($response['code']) || "200" != $response['code']) {
199
+ $iwp_backup_core->log('Unexpected HTTP code returned from Dropbox: '.$response['code']." (".serialize($response).")");
200
+ if ($response['code'] >= 400) {
201
+ $iwp_backup_core->log('Dropbox '.sprintf(__('error: failed to upload file to %s (see log file for more)','iwp_backup_core'), $file), 'error');
202
+ } else {
203
+ $iwp_backup_core->log(sprintf(__('%s did not return the expected response - check your log file for more details', 'iwp_backup_core'), 'Dropbox'), 'warning');
204
+ }
205
+ }
206
+ } catch (Exception $e) {
207
+ $iwp_backup_core->log("Dropbox chunked upload exception (".get_class($e)."): ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
208
+ if (preg_match("/Submitted input out of alignment: got \[(\d+)\] expected \[(\d+)\]/i", $e->getMessage(), $matches)) {
209
+ // Try the indicated offset
210
+ $we_tried = $matches[1];
211
+ $dropbox_wanted = (int)$matches[2];
212
+ $iwp_backup_core->log("Dropbox not yet aligned: tried=$we_tried, wanted=$dropbox_wanted; will attempt recovery");
213
+ $this->uploaded_offset = $dropbox_wanted;
214
+ try {
215
+ $dropbox->chunkedUpload($iwp_backup_dir.'/'.$file, '', $ufile, true, $dropbox_wanted, $upload_id, array($this, 'chunked_callback'));
216
+ } catch (Exception $e) {
217
+ $msg = $e->getMessage();
218
+ if (preg_match('/Upload with upload_id .* already completed/', $msg)) {
219
+ $iwp_backup_core->log('Dropbox returned an error, but apparently indicating previous success: '.$msg);
220
+ } else {
221
+ $iwp_backup_core->log('Dropbox error: '.$msg.' (line: '.$e->getLine().', file: '.$e->getFile().')');
222
+ $iwp_backup_core->log('Dropbox '.sprintf(__('error: failed to upload file to %s (see log file for more)','iwp_backup_core'), $ufile), 'error');
223
+ $file_success = 0;
224
+ if (strpos($msg, 'select/poll returned error') !== false && $this->upload_tick > 0 && time() - $this->upload_tick > 800) {
225
+ $iwp_backup_core->reschedule(60);
226
+ $iwp_backup_core->log("Select/poll returned after a long time: scheduling a resumption and terminating for now");
227
+ $iwp_backup_core->record_still_alive();
228
+ die;
229
+ }
230
+ }
231
+ }
232
+ } else {
233
+ $msg = $e->getMessage();
234
+ if (preg_match('/Upload with upload_id .* already completed/', $msg)) {
235
+ $iwp_backup_core->log('Dropbox returned an error, but apparently indicating previous success: '.$msg);
236
+ } else {
237
+ $iwp_backup_core->log('Dropbox error: '.$msg);
238
+ $iwp_backup_core->log('Dropbox '.sprintf(__('error: failed to upload file to %s (see log file for more)','iwp_backup_core'), $ufile), 'error');
239
+ $file_success = 0;
240
+ if (strpos($msg, 'select/poll returned error') !== false && $this->upload_tick > 0 && time() - $this->upload_tick > 800) {
241
+ $iwp_backup_core->reschedule(60);
242
+ $iwp_backup_core->log("Select/poll returned after a long time: scheduling a resumption and terminating for now");
243
+ $iwp_backup_core->record_still_alive();
244
+ die;
245
+ }
246
+ }
247
+ }
248
+ }
249
+ if ($file_success) {
250
+ $iwp_backup_core->uploaded_file($file);
251
+ $microtime_elapsed = microtime(true)-$microtime;
252
+ $speedps = $filesize/$microtime_elapsed;
253
+ $speed = sprintf("%.2d",$filesize)." KB in ".sprintf("%.2d",$microtime_elapsed)."s (".sprintf("%.2d", $speedps)." KB/s)";
254
+ $iwp_backup_core->log("Dropbox: File upload success (".$file."): $speed");
255
+ $iwp_backup_core->jobdata_delete('IWP_duido_'.$hash);
256
+ $iwp_backup_core->jobdata_delete('IWP_duidi_'.$hash);
257
+ }
258
+
259
+ }
260
+
261
+ return null;
262
+
263
+ }
264
+
265
+ # $match: a substring to require (tested via strpos() !== false)
266
+ public function listfiles($match = 'backup_') {
267
+
268
+ $opts = $this->get_options();
269
+
270
+ if (empty($opts['tk_access_token'])) return new WP_Error('no_settings', __('No settings were found', 'InfiniteWP').' (dropbox)');
271
+
272
+ global $iwp_backup_core;
273
+ try {
274
+ $dropbox = $this->bootstrap();
275
+ } catch (Exception $e) {
276
+ $iwp_backup_core->log('Dropbox access error: '.$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
277
+ return new WP_Error('access_error', $e->getMessage());
278
+ }
279
+
280
+ $searchpath = '/'.untrailingslashit(apply_filters('IWP_dropbox_modpath', '', $this));
281
+
282
+ try {
283
+ /* Some users could have a large amount of backups, the max search is 1000 entries we should continue to search until there are no more entries to bring back. */
284
+ $start = 0;
285
+ $matches = array();
286
+
287
+ while (true) {
288
+ $search = $dropbox->search($match, $searchpath, 1000, $start);
289
+ if (empty($search['code']) || 200 != $search['code']) return new WP_Error('response_error', sprintf(__('%s returned an unexpected HTTP response: %s', 'InfiniteWP'), 'Dropbox', $search['code']), $search['body']);
290
+
291
+ if (empty($search['body'])) return array();
292
+
293
+ if (isset($search['body']->matches) && is_array($search['body']->matches)) {
294
+ $matches = array_merge($matches, $search['body']->matches);
295
+ } elseif (is_array($search['body'])) {
296
+ $matches = $search['body'];
297
+ } else {
298
+ break;
299
+ }
300
+
301
+ if (isset($search['body']->more) && true == $search['body']->more && isset($search['body']->start)) {
302
+ $start = $search['body']->start;
303
+ } else {
304
+ break;
305
+ }
306
+ }
307
+
308
+ } catch (Exception $e) {
309
+ $iwp_backup_core->log('Dropbox error: '.$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
310
+ // The most likely cause of a search_error is specifying a non-existent path, which should just result in an empty result set.
311
+ // return new WP_Error('search_error', $e->getMessage());
312
+ return array();
313
+ }
314
+
315
+ $results = array();
316
+
317
+ foreach ($matches as $item) {
318
+
319
+ $item = $item->metadata;
320
+ if (!is_object($item)) continue;
321
+
322
+ if ((!isset($item->size) || $item->size > 0) && $item->{'.tag'} != 'folder' && !empty($item->path_display) && 0 === strpos($item->path_display, $searchpath)) {
323
+
324
+ $path = substr($item->path_display, strlen($searchpath));
325
+ if ('/' == substr($path, 0, 1)) $path=substr($path, 1);
326
+
327
+ # Ones in subfolders are not wanted
328
+ if (false !== strpos($path, '/')) continue;
329
+
330
+ $result = array('name' => $path);
331
+ if (!empty($item->size)) $result['size'] = $item->size;
332
+
333
+ $results[] = $result;
334
+ }
335
+ }
336
+
337
+ return $results;
338
+ }
339
+
340
+ public function defaults() {
341
+ return apply_filters('IWP_dropbox_defaults', array('Z3Q3ZmkwbnplNHA0Zzlx', 'bTY0bm9iNmY4eWhjODRt'));
342
+ }
343
+
344
+ public function delete($files, $data = null, $sizeinfo = array()) {
345
+
346
+ global $iwp_backup_core;
347
+ if (is_string($files)) $files=array($files);
348
+
349
+ $opts = $this->get_options();
350
+
351
+ if (empty($opts['tk_access_token'])) {
352
+ $iwp_backup_core->log('You do not appear to be authenticated with Dropbox (3)');
353
+ $iwp_backup_core->log(sprintf(__('You do not appear to be authenticated with %s (whilst deleting)', 'InfiniteWP'), 'Dropbox'), 'warning');
354
+ return false;
355
+ }
356
+
357
+ try {
358
+ $dropbox = $this->bootstrap();
359
+ } catch (Exception $e) {
360
+ $iwp_backup_core->log('Dropbox error: '.$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
361
+ $iwp_backup_core->log(sprintf(__('Failed to access %s when deleting (see log file for more)', 'InfiniteWP'), 'Dropbox'), 'warning');
362
+ return false;
363
+ }
364
+ if (false === $dropbox) return false;
365
+
366
+ foreach ($files as $file) {
367
+ $ufile = apply_filters('IWP_dropbox_modpath', $file, $this);
368
+ $iwp_backup_core->log("Dropbox: request deletion: $ufile");
369
+
370
+ try {
371
+ $dropbox->delete($ufile);
372
+ $file_success = 1;
373
+ } catch (Exception $e) {
374
+ $iwp_backup_core->log('Dropbox error: '.$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
375
+ }
376
+
377
+ if (isset($file_success)) {
378
+ $iwp_backup_core->log('Dropbox: delete succeeded');
379
+ } else {
380
+ return false;
381
+ }
382
+ }
383
+
384
+ }
385
+
386
+ public function download($file) {
387
+
388
+ global $iwp_backup_core;
389
+
390
+ $opts = $this->get_options();
391
+
392
+ if (empty($opts['tk_access_token'])) {
393
+ $iwp_backup_core->log('You do not appear to be authenticated with Dropbox (4)');
394
+ $iwp_backup_core->log(sprintf(__('You do not appear to be authenticated with %s','InfiniteWP'), 'Dropbox'), 'error');
395
+ return false;
396
+ }
397
+
398
+ try {
399
+ $dropbox = $this->bootstrap();
400
+ } catch (Exception $e) {
401
+ $iwp_backup_core->log('Dropbox error: '.$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
402
+ $iwp_backup_core->log('Dropbox error: '.$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')', 'error');
403
+ return false;
404
+ }
405
+ if (false === $dropbox) return false;
406
+
407
+ $iwp_backup_dir = $iwp_backup_core->backups_dir_location();
408
+ $microtime = microtime(true);
409
+
410
+ $try_the_other_one = false;
411
+
412
+ $ufile = apply_filters('IWP_dropbox_modpath', $file, $this);
413
+
414
+ try {
415
+ $get = $dropbox->getFile($ufile, $iwp_backup_dir.'/'.$file, null, true);
416
+ } catch (Exception $e) {
417
+ // TODO: Remove this October 2013 (we stored in the wrong place for a while...)
418
+ $try_the_other_one = true;
419
+ $possible_error = $e->getMessage();
420
+ $iwp_backup_core->log('Dropbox error: '.$e);
421
+ $get = false;
422
+ }
423
+
424
+ // TODO: Remove this October 2013 (we stored files in the wrong place for a while...)
425
+ if ($try_the_other_one) {
426
+ $dropbox_folder = trailingslashit($opts['folder']);
427
+ try {
428
+ $get = $dropbox->getFile($dropbox_folder.'/'.$file, $iwp_backup_dir.'/'.$file, null, true);
429
+ if (isset($get['response']['body'])) {
430
+ $iwp_backup_core->log("Dropbox: downloaded ".round(strlen($get['response']['body'])/1024,1).' KB');
431
+ }
432
+ } catch (Exception $e) {
433
+ $iwp_backup_core->log($possible_error, 'error');
434
+ $iwp_backup_core->log($e->getMessage(), 'error');
435
+ $get = false;
436
+ }
437
+ }
438
+
439
+ return $get;
440
+
441
+ }
442
+
443
+ public function config_print() {
444
+
445
+ $opts = $this->get_options();
446
+ $ownername = empty($opts['ownername']) ? '' : $opts['ownername'];
447
+ if (!empty($opts['appkey'])) {
448
+ $appkey = empty($opts['appkey']) ? '' : $opts['appkey'];
449
+ $secret = empty($opts['secret']) ? '' : $opts['secret'];
450
+ }
451
+ }
452
+
453
+ public function auth_token() {
454
+ $this->bootstrap();
455
+ $opts = $this->get_options();
456
+ if (!empty($opts['tk_access_token'])) {
457
+ add_action('all_admin_notices', array($this, 'show_authed_admin_warning') );
458
+ }
459
+ }
460
+
461
+ // Acquire single-use authorization code
462
+ public function auth_request() {
463
+ $this->bootstrap();
464
+ }
465
+
466
+ // This basically reproduces the relevant bits of bootstrap.php from the SDK
467
+ public function bootstrap($deauthenticate = false) {
468
+ if (!empty($this->dropbox_object) && !is_wp_error($this->dropbox_object)) return $this->dropbox_object;
469
+
470
+ /*
471
+ Use Old Dropbox API constant is used to force bootstrap to use the old API this is for users having problems. By default we will use the new Dropbox API v2 as the old version will be deprecated as of June 2017
472
+ */
473
+ $dropbox_api = 'Dropbox2';
474
+
475
+ require_once($GLOBALS['iwp_mmb_plugin_dir'].'/lib/'.$dropbox_api.'/API.php');
476
+ require_once($GLOBALS['iwp_mmb_plugin_dir'].'/lib/'.$dropbox_api.'/Exception.php');
477
+ require_once($GLOBALS['iwp_mmb_plugin_dir'].'/lib/'.$dropbox_api.'/OAuth/Consumer/ConsumerAbstract.php');
478
+ require_once($GLOBALS['iwp_mmb_plugin_dir'].'/lib/'.$dropbox_api.'/OAuth/Storage/StorageInterface.php');
479
+ require_once($GLOBALS['iwp_mmb_plugin_dir'].'/lib/'.$dropbox_api.'/OAuth/Storage/Encrypter.php');
480
+ require_once($GLOBALS['iwp_mmb_plugin_dir'].'/lib/'.$dropbox_api.'/OAuth/Storage/WordPress.php');
481
+ require_once($GLOBALS['iwp_mmb_plugin_dir'].'/lib/'.$dropbox_api.'/OAuth/Consumer/Curl.php');
482
+
483
+ $opts = $this->get_options();
484
+
485
+ $key = empty($opts['secret']) ? '' : $opts['secret'];
486
+ $sec = empty($opts['appkey']) ? '' : $opts['appkey'];
487
+
488
+ $oauth2_id = base64_decode('aXA3NGR2Zm1sOHFteTA5');
489
+
490
+
491
+ // Instantiate the Encrypter and storage objects
492
+ $encrypter = new Dropbox_Encrypter('ThisOneDoesNotMatterBeyondLength');
493
+
494
+ // Instantiate the storage
495
+ $storage = new Dropbox_WordPress($encrypter, "tk_", 'IWP_dropbox', $this);
496
+
497
+ // WordPress consumer does not yet work
498
+ // $OAuth = new Dropbox_ConsumerWordPress($sec, $key, $storage, $callback);
499
+
500
+ // Get the DropBox API access details
501
+ list($d2, $d1) = $this->defaults();
502
+ if (empty($sec)) { $sec = base64_decode($d1); }; if (empty($key)) { $key = base64_decode($d2); }
503
+ $root = 'sandbox';
504
+ if ('dropbox:' == substr($sec, 0, 8)) {
505
+ $sec = substr($sec, 8);
506
+ $root = 'dropbox';
507
+ }
508
+
509
+ try {
510
+ $OAuth = new Dropbox_Curl($sec, $oauth2_id, $key, $storage, null, null, $deauthenticate);
511
+ } catch (Exception $e) {
512
+ global $iwp_backup_core;
513
+ $iwp_backup_core->log("Dropbox Curl error: ".$e->getMessage());
514
+ $iwp_backup_core->log(sprintf(__("%s error: %s", 'iwp_backup_core'), "Dropbox/Curl", $e->getMessage().' ('.get_class($e).') (line: '.$e->getLine().', file: '.$e->getFile()).')', 'error');
515
+ return false;
516
+ }
517
+
518
+ if ($deauthenticate) return true;
519
+
520
+ $OAuth->setToken($opts['tk_access_token']);
521
+ $this->dropbox_object = new IWP_MMB_Dropbox_API($OAuth, $root);
522
+ return $this->dropbox_object;
523
+ }
524
+
525
+ }
backup/ftp.php ADDED
@@ -0,0 +1,277 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if ( ! defined('ABSPATH') )
4
+ die();
5
+
6
+ if (!class_exists('IWP_MMB_UploadModule')) require_once($GLOBALS['iwp_mmb_plugin_dir'].'/backup/backup.upload.php');
7
+
8
+ class IWP_MMB_UploadModule_ftp extends IWP_MMB_UploadModule {
9
+
10
+ // Get FTP object with parameters set
11
+ private function getFTP($server, $user, $pass, $disable_ssl = false, $disable_verify = true, $use_server_certs = false, $passive = true) {
12
+
13
+ if ('' == trim($server) || '' == trim($user) || '' == trim($pass)) return new WP_Error('no_settings', sprintf(__('No %s settings were found','InfiniteWP'), 'FTP'));
14
+
15
+ if( !class_exists('IWP_MMB_ftp_wrapper')) require_once($GLOBALS['iwp_mmb_plugin_dir'].'/lib/ftp.class.php');
16
+
17
+ $port = 21;
18
+ if (preg_match('/^(.*):(\d+)$/', $server, $matches)) {
19
+ $server = $matches[1];
20
+ $port = $matches[2];
21
+ }
22
+
23
+ $ftp = new IWP_MMB_ftp_wrapper($server, $user, $pass, $port);
24
+
25
+ if ($disable_ssl) $ftp->ssl = false;
26
+ $ftp->use_server_certs = $use_server_certs;
27
+ $ftp->disable_verify = $disable_verify;
28
+ $ftp->passive = ($passive) ? true : false;
29
+
30
+ return $ftp;
31
+
32
+ }
33
+
34
+ public function get_supported_features() {
35
+ // This options format is handled via only accessing options via $this->get_options()
36
+ return array('multi_options');
37
+ }
38
+
39
+ public function get_default_options() {
40
+ return array(
41
+ 'host' => '',
42
+ 'user' => '',
43
+ 'pass' => '',
44
+ 'path' => '',
45
+ 'passive' => true
46
+ );
47
+ }
48
+
49
+ public function backup($backup_array) {
50
+
51
+ global $iwp_backup_core;
52
+
53
+ $opts = $this->get_options();
54
+
55
+ $ftp = $this->getFTP(
56
+ $opts['host'],
57
+ $opts['user'],
58
+ $opts['pass'],
59
+ $iwp_backup_core->get_job_option('IWP_ssl_nossl'),
60
+ $iwp_backup_core->get_job_option('IWP_ssl_disableverify'),
61
+ $iwp_backup_core->get_job_option('IWP_ssl_useservercerts'),
62
+ $opts['passive']
63
+ );
64
+
65
+ if (is_wp_error($ftp) || !$ftp->connect()) {
66
+ if (is_wp_error($ftp)) {
67
+ $iwp_backup_core->log_wp_error($ftp);
68
+ } else {
69
+ $iwp_backup_core->log("FTP Failure: we did not successfully log in with those credentials.");
70
+ }
71
+ $iwp_backup_core->log(sprintf(__("%s login failure",'InfiniteWP'), 'FTP'), 'error');
72
+ return false;
73
+ }
74
+
75
+ //$ftp->make_dir(); we may need to recursively create dirs? TODO
76
+
77
+ $iwp_backup_dir = $iwp_backup_core->backups_dir_location().'/';
78
+
79
+ $ftp_remote_path = trailingslashit($opts['path']);
80
+ if (!empty($opts['ftp_site_folder'])) {
81
+ $site_name = iwp_getSiteName();
82
+ $ftp_remote_path.= trailingslashit($site_name);
83
+ }
84
+ foreach($backup_array as $file) {
85
+ $fullpath = $iwp_backup_dir.$file;
86
+ $iwp_backup_core->log("FTP upload attempt: $file -> ftp://".$opts['user']."@".$opts['host']."/${ftp_remote_path}${file}");
87
+ $timer_start = microtime(true);
88
+ $size_k = round(filesize($fullpath)/1024,1);
89
+ # Note :Setting $resume to true unnecessarily is not meant to be a problem. Only ever (Feb 2014) seen one weird FTP server where calling SIZE on a non-existent file did create a problem. So, this code just helps that case. (the check for non-empty upload_status[p] is being cautious.
90
+ $upload_status = $iwp_backup_core->jobdata_get('uploading_substatus');
91
+ if (0 == $iwp_backup_core->current_resumption || (is_array($upload_status) && !empty($upload_status['p']) && $upload_status['p'] == 0)) {
92
+ $resume = false;
93
+ } else {
94
+ $resume = true;
95
+ }
96
+
97
+ if ($ftp->put($fullpath, $ftp_remote_path.$file, FTP_BINARY, $resume, $iwp_backup_core, $ftp_remote_path)) {
98
+ $iwp_backup_core->log("FTP upload attempt successful (".$size_k."KB in ".(round(microtime(true)-$timer_start,2)).'s)');
99
+ $iwp_backup_core->uploaded_file($file);
100
+ } else {
101
+ $iwp_backup_core->log("ERROR: FTP upload failed" );
102
+ $iwp_backup_core->log(sprintf(__("%s upload failed",'InfiniteWP'), 'FTP'), 'error');
103
+ }
104
+ }
105
+
106
+ return array('ftp_object' => $ftp, 'ftp_remote_path' => $ftp_remote_path);
107
+ }
108
+
109
+ public function listfiles($match = 'backup_') {
110
+ global $iwp_backup_core;
111
+
112
+ $opts = $this->get_options();
113
+
114
+ $ftp = $this->getFTP(
115
+ $opts['host'],
116
+ $opts['user'],
117
+ $opts['pass'],
118
+ $iwp_backup_core->get_job_option('IWP_ssl_nossl'),
119
+ $iwp_backup_core->get_job_option('IWP_ssl_disableverify'),
120
+ $iwp_backup_core->get_job_option('IWP_ssl_useservercerts'),
121
+ $opts['passive']
122
+ );
123
+
124
+ if (is_wp_error($ftp)) return $ftp;
125
+
126
+ if (!$ftp->connect()) return new WP_Error('ftp_login_failed', sprintf(__("%s login failure",'InfiniteWP'), 'FTP'));
127
+
128
+ $ftp_remote_path = $opts['path'];
129
+ if ($ftp_remote_path) $ftp_remote_path = trailingslashit($ftp_remote_path);
130
+ if (!empty($opts['ftp_site_folder'])) {
131
+ $site_name = iwp_getSiteName();
132
+ $ftp_remote_path.= trailingslashit($site_name);
133
+ }
134
+
135
+ $dirlist = $ftp->dir_list($ftp_remote_path);
136
+ if (!is_array($dirlist)) return array();
137
+
138
+ $results = array();
139
+
140
+ foreach ($dirlist as $k => $path) {
141
+
142
+ if ($ftp_remote_path) {
143
+ // Feb 2015 - found a case where the directory path was not prefixed on
144
+ if (0 !== strpos($path, $ftp_remote_path) && (false !== strpos('/', $ftp_remote_path) && false !== strpos('\\', $ftp_remote_path))) continue;
145
+ if (0 === strpos($path, $ftp_remote_path)) $path = substr($path, strlen($ftp_remote_path));
146
+ // if (0 !== strpos($path, $ftp_remote_path)) continue;
147
+ // $path = substr($path, strlen($ftp_remote_path));
148
+ if (0 === strpos($path, $match)) $results[]['name'] = $path;
149
+ } else {
150
+ if ('/' == substr($path, 0, 1)) $path = substr($path, 1);
151
+ if (false !== strpos($path, '/')) continue;
152
+ if (0 === strpos($path, $match)) $results[]['name'] = $path;
153
+ }
154
+
155
+ unset($dirlist[$k]);
156
+ }
157
+
158
+ # ftp_nlist() doesn't return file sizes. rawlist() does, but is tricky to parse. So, we get the sizes manually.
159
+ foreach ($results as $ind => $name) {
160
+ $size = $ftp->size($ftp_remote_path.$name['name']);
161
+ if (0 === $size) {
162
+ unset($results[$ind]);
163
+ } elseif ($size>0) {
164
+ $results[$ind]['size'] = $size;
165
+ }
166
+ }
167
+
168
+ return $results;
169
+
170
+ }
171
+
172
+ public function delete($files, $ftparr = array(), $sizeinfo = array()) {
173
+
174
+ global $iwp_backup_core;
175
+ if (is_string($files)) $files=array($files);
176
+
177
+ $opts = $this->get_options();
178
+
179
+ if (is_array($ftparr) && isset($ftparr['ftp_object'])) {
180
+ $ftp = $ftparr['ftp_object'];
181
+ } else {
182
+ $ftp = $this->getFTP(
183
+ $opts['host'],
184
+ $opts['user'],
185
+ $opts['pass'],
186
+ $iwp_backup_core->get_job_option('IWP_ssl_nossl'),
187
+ $iwp_backup_core->get_job_option('IWP_ssl_disableverify'),
188
+ $iwp_backup_core->get_job_option('IWP_ssl_useservercerts'),
189
+ $opts['passive']
190
+ );
191
+
192
+ if (is_wp_error($ftp) || !$ftp->connect()) {
193
+ if (is_wp_error($ftp)) $iwp_backup_core->log_wp_error($ftp);
194
+ $iwp_backup_core->log("FTP Failure: we did not successfully log in with those credentials (host=".$opts['host'].").");
195
+ return false;
196
+ }
197
+
198
+ }
199
+
200
+ $ftp_remote_path = isset($ftparr['ftp_remote_path']) ? $ftparr['ftp_remote_path'] : trailingslashit($opts['path']);
201
+
202
+ if (!empty($opts['ftp_site_folder'])) {
203
+ $site_name = iwp_getSiteName();
204
+ $ftp_remote_path.= trailingslashit($site_name);
205
+ }
206
+
207
+ $ret = true;
208
+ foreach ($files as $file) {
209
+ if (@$ftp->delete($ftp_remote_path.$file)) {
210
+ $iwp_backup_core->log("FTP delete: succeeded (${ftp_remote_path}${file})");
211
+ } else {
212
+ $iwp_backup_core->log("FTP delete: failed (${ftp_remote_path}${file})");
213
+ $ret = false;
214
+ }
215
+ }
216
+ return $ret;
217
+
218
+ }
219
+
220
+ public function download($file) {
221
+
222
+ global $iwp_backup_core;
223
+
224
+ $opts = $this->get_options();
225
+
226
+ $ftp = $this->getFTP(
227
+ $opts['host'],
228
+ $opts['user'],
229
+ $opts['pass'],
230
+ $iwp_backup_core->get_job_option('IWP_ssl_nossl'),
231
+ $iwp_backup_core->get_job_option('IWP_ssl_disableverify'),
232
+ $iwp_backup_core->get_job_option('IWP_ssl_useservercerts'),
233
+ $opts['passive']
234
+ );
235
+ if (is_wp_error($ftp)) return $ftp;
236
+
237
+ if (!$ftp->connect()) {
238
+ $iwp_backup_core->log("FTP Failure: we did not successfully log in with those credentials.");
239
+ $iwp_backup_core->log(sprintf(__("%s login failure",'iwp_backup_core'), 'FTP'), 'error');
240
+ return false;
241
+ }
242
+
243
+ //$ftp->make_dir(); we may need to recursively create dirs? TODO
244
+
245
+ $ftp_remote_path = trailingslashit($opts['path']);
246
+ if (!empty($opts['ftp_site_folder'])) {
247
+ $site_name = iwp_getSiteName();
248
+ $ftp_remote_path.= trailingslashit($site_name);
249
+ }
250
+ $fullpath = $iwp_backup_core->backups_dir_location().'/'.$file;
251
+
252
+ $resume = false;
253
+ if (file_exists($fullpath)) {
254
+ $resume = true;
255
+ $iwp_backup_core->log("File already exists locally; will resume: size: ".filesize($fullpath));
256
+ }
257
+
258
+ return $ftp->get($fullpath, $ftp_remote_path.$file, FTP_BINARY, $resume, $iwp_backup_core);
259
+ }
260
+
261
+ private function ftp_possible() {
262
+ $funcs_disabled = array();
263
+ foreach (array('ftp_connect', 'ftp_login', 'ftp_nb_fput') as $func) {
264
+ if (!function_exists($func)) $funcs_disabled['ftp'][] = $func;
265
+ }
266
+ $funcs_disabled = apply_filters('IWP_ftp_possible', $funcs_disabled);
267
+ return (0 == count($funcs_disabled)) ? true : $funcs_disabled;
268
+ }
269
+
270
+ public function config_print() {
271
+ global $iwp_backup_core;
272
+ $possible = $this->ftp_possible();
273
+
274
+ $opts = $this->get_options();
275
+ }
276
+
277
+ }
backup/functions.php ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if ( ! defined('ABSPATH') )
4
+ die();
5
+
6
+ if( !function_exists ( 'iwp_mmb_define_constant' )) {
7
+ function iwp_mmb_define_constant(){
8
+ define('IWP_DEFAULT_OTHERS_EXCLUDE','upgrade,cache,updraft,backup*,*backups,mysql.sql,debug.log,managewp,infinity-cache,backupwordpress,old-cache,nfwlog,wflogs,wishlist-backup,w3tc,logs,widget_cache,updraftplus');
9
+
10
+ define('IWP_DEFAULT_INCLUDES','google,wp-config.php,.htaccess');
11
+
12
+ define('IWP_DEFAULT_UPLOADS_EXCLUDE','backup*,*backups,backwpup*,wp-clone,snapshots,db-backup,backupbuddy_backups,vcf,pb_backupbuddy,sucuri,aiowps_backups,mainwp,wp_system,wpcf7_captcha,wc-logs,siteorigin-widgets,wp-hummingbird-cache,wp-security-audit-log,backwpup-12b462-backups,backwpup-12b462-logs,backwpup-12b462-temp,Dropbox_Backup,cache');
13
+
14
+ if (!defined('IWP_DATA_OPTIONAL_TABLES')) define('IWP_DATA_OPTIONAL_TABLES', 'bwps_log,statpress,slim_stats,redirection_logs,Counterize,Counterize_Referers,Counterize_UserAgents,wbz404_logs,wbz404_redirects,tts_trafficstats,tts_referrer_stats,wponlinebackup_generations,svisitor_stat,simple_feed_stats,itsec_log,relevanssi_log,blc_instances,wysija_email_user_stat,woocommerce_sessions,et_bloom_stats,redirection_404,iwp_backup_status,iwp_file_list');
15
+
16
+ if (!defined('IWP_ZIP_EXECUTABLE')) define('IWP_ZIP_EXECUTABLE', "/usr/bin/zip,/bin/zip,/usr/local/bin/zip,/usr/sfw/bin/zip,/usr/xdg4/bin/zip,/opt/bin/zip");
17
+
18
+ if (!defined('IWP_MYSQLDUMP_EXECUTABLE')) define('IWP_MYSQLDUMP_EXECUTABLE', iwp_mmb_build_mysqldump_list());
19
+
20
+ if (!defined('IWP_WARN_FILE_SIZE')) define('IWP_WARN_FILE_SIZE', 1024*1024*250);
21
+
22
+ if (!defined('IWP_WARN_DB_ROWS')) define('IWP_WARN_DB_ROWS', 150000);
23
+
24
+ if (!defined('IWP_SPLIT_MIN')) define('IWP_SPLIT_MIN', 200);
25
+
26
+ if (!defined('IWP_MAXBATCHFILES')) define('IWP_MAXBATCHFILES', 200);
27
+
28
+ if (!defined('IWP_WARN_EMAIL_SIZE')) define('IWP_WARN_EMAIL_SIZE', 20*1048576);
29
+
30
+ if (!defined('IWP_ZIP_NOCOMPRESS')) define('IWP_ZIP_NOCOMPRESS', '.jpg,.jpeg,.png,.gif,.zip,.gz,.bz2,.xz,.rar,.mp3,.mp4,.mpeg,.avi,.mov');
31
+
32
+ if (!defined('IWP_SET_TIME_LIMIT')) define('IWP_SET_TIME_LIMIT', 900);
33
+ if (!defined('IWP_INITIAL_RESUME_INTERVAL')) define('IWP_INITIAL_RESUME_INTERVAL', 300);
34
+
35
+ if (!defined('IWP_BINZIP_OPTS')) {
36
+ $zip_nocompress = array_map('trim', explode(',', IWP_ZIP_NOCOMPRESS));
37
+ $zip_binzip_opts = '';
38
+ foreach ($zip_nocompress as $ext) {
39
+ if (empty($zip_binzip_opts)) {
40
+ $zip_binzip_opts = "-n $ext:".strtoupper($ext);
41
+ } else {
42
+ $zip_binzip_opts .= ':'.$ext.':'.strtoupper($ext);
43
+ }
44
+ }
45
+ define('IWP_BINZIP_OPTS', $zip_binzip_opts);
46
+ }
47
+
48
+ if(!defined('IWP_BACKUP_DIR')){
49
+ define('IWP_BACKUP_DIR', WP_CONTENT_DIR . '/infinitewp/backups');
50
+ }
51
+
52
+ if(!defined('IWP_DB_DIR')){
53
+ define('IWP_DB_DIR', IWP_BACKUP_DIR . '/iwp_db');
54
+ }
55
+
56
+ if(!defined('IWP_PCLZIP_TEMPORARY_DIR')){
57
+ define('IWP_PCLZIP_TEMPORARY_DIR', WP_CONTENT_DIR . '/infinitewp/temp/');
58
+ }
59
+
60
+
61
+ }
62
+ }
63
+
64
+ if (!function_exists('iwp_mmb_modify_cron_schedules')){
65
+ function iwp_mmb_modify_cron_schedules($schedules) {
66
+ $schedules['weekly'] = array('interval' => 604800, 'display' => 'Once Weekly');
67
+ $schedules['fortnightly'] = array('interval' => 1209600, 'display' => 'Once Each Fortnight');
68
+ $schedules['monthly'] = array('interval' => 2592000, 'display' => 'Once Monthly');
69
+ $schedules['every4hours'] = array('interval' => 14400, 'display' => sprintf(__('Every %s hours', 'InfiniteWP'), 4));
70
+ $schedules['every8hours'] = array('interval' => 28800, 'display' => sprintf(__('Every %s hours', 'InfiniteWP'), 8));
71
+ return $schedules;
72
+ }
73
+ }
74
+
75
+ if (!function_exists('iwp_mmb_build_mysqldump_list')) {
76
+ function iwp_mmb_build_mysqldump_list() {
77
+ if ('win' == strtolower(substr(PHP_OS, 0, 3)) && function_exists('glob')) {
78
+ $drives = array('C','D','E');
79
+
80
+ if (!empty($_SERVER['DOCUMENT_ROOT'])) {
81
+ //Get the drive that this is running on
82
+ $current_drive = strtoupper(substr($_SERVER['DOCUMENT_ROOT'], 0, 1));
83
+ if(!in_array($current_drive, $drives)) array_unshift($drives, $current_drive);
84
+ }
85
+
86
+ $directories = array();
87
+
88
+ foreach ($drives as $drive_letter) {
89
+ $dir = glob("$drive_letter:\\{Program Files\\MySQL\\{,MySQL*,etc}{,\\bin,\\?},mysqldump}\\mysqldump*", GLOB_BRACE);
90
+ if (is_array($dir)) $directories = array_merge($directories, $dir);
91
+ }
92
+
93
+ $drive_string = implode(',', $directories);
94
+ return $drive_string;
95
+
96
+ } else return "/usr/bin/mysqldump,/bin/mysqldump,/usr/local/bin/mysqldump,/usr/sfw/bin/mysqldump,/usr/xdg4/bin/mysqldump,/opt/bin/mysqldump";
97
+ }
98
+ }
99
+
100
+ if (!function_exists('gzopen') && function_exists('gzopen64')) {
101
+ function gzopen($filename, $mode, $use_include_path = 0) {
102
+ return gzopen64($filename, $mode, $use_include_path);
103
+ }
104
+ }
105
+
106
+ if (!function_exists('remove_http')) {
107
+ function remove_http($url = '')
108
+ {
109
+ if ($url == 'http://' OR $url == 'https://') {
110
+ return $url;
111
+ }
112
+ return preg_replace('/^(http|https)\:\/\/(www.)?/i', '', $url);
113
+
114
+ }
115
+ }
116
+
117
+ if (!function_exists('iwp_getSiteName')) {
118
+ function iwp_getSiteName(){
119
+ $site_name = str_replace(array(
120
+ "_",
121
+ "/",
122
+ "~"
123
+ ), array(
124
+ "",
125
+ "-",
126
+ "-"
127
+ ), rtrim(remove_http(get_bloginfo('url')), "/"));
128
+
129
+ return $site_name;
130
+ }
131
+ }
132
+
133
+ if (!function_exists('iwp_mmb_get_backup_ID_by_taskname')) {
134
+ function iwp_mmb_get_backup_ID_by_taskname($method, $taskName){
135
+ global $iwp_mmb_core, $iwp_backup_core;
136
+ $backup_keys = array();
137
+ if ($method == 'advanced') {
138
+ $backup_keys = $iwp_backup_core->get_timestamp_by_label($taskName);
139
+ }else{
140
+ require_once($GLOBALS['iwp_mmb_plugin_dir']."/backup.class.multicall.php");
141
+ $backup_instance = new IWP_MMB_Backup_Multicall();
142
+ $backup_keys = $backup_instance->get_timestamp_by_label($taskName);
143
+ }
144
+
145
+ return $backup_keys;
146
+ }
147
+ }
backup/googledrive.php ADDED
@@ -0,0 +1,787 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if ( ! defined('ABSPATH') )
4
+ die();
5
+
6
+ if (!class_exists('IWP_MMB_UploadModule')) require_once($GLOBALS['iwp_mmb_plugin_dir'].'/backup/backup.upload.php');
7
+
8
+ class IWP_MMB_UploadModule_googledrive extends IWP_MMB_UploadModule {
9
+
10
+ private $service;
11
+ private $client;
12
+ private $ids_from_paths;
13
+
14
+ public function get_supported_features() {
15
+ // This options format is handled via only accessing options via $this->get_options()
16
+ return array('multi_options');
17
+ }
18
+
19
+ public function get_default_options() {
20
+ # parentid is deprecated since April 2014; it should not be in the default options (its presence is used to detect an upgraded-from-previous-SDK situation). For the same reason, 'folder' is also unset; which enables us to know whether new-style settings have ever been set.
21
+ return array(
22
+ 'clientid' => '',
23
+ 'secret' => '',
24
+ 'token' => '',
25
+ );
26
+ }
27
+
28
+ private function root_id() {
29
+ if (empty($this->root_id)) $this->root_id = $this->service->about->get()->getRootFolderId();
30
+ return $this->root_id;
31
+ }
32
+
33
+ public function id_from_path($path, $retry = true) {
34
+ global $iwp_backup_core;
35
+
36
+ try {
37
+ while ('/' == substr($path, 0, 1)) { $path = substr($path, 1); }
38
+
39
+ $cache_key = (empty($path)) ? '/' : $path;
40
+ if (!empty($this->ids_from_paths) && isset($this->ids_from_paths[$cache_key])) return $this->ids_from_paths[$cache_key];
41
+
42
+ $current_parent = $this->root_id();
43
+ $current_path = '/';
44
+
45
+ if (!empty($path)) {
46
+ foreach (explode('/', $path) as $element) {
47
+ $found = false;
48
+ $sub_items = $this->get_subitems($current_parent, 'dir', $element);
49
+
50
+ foreach ($sub_items as $item) {
51
+ try {
52
+ if ($item->getTitle() == $element) {
53
+ $found = true;
54
+ $current_path .= $element.'/';
55
+ $current_parent = $item->getId();
56
+ break;
57
+ }
58
+ } catch (Exception $e) {
59
+ $iwp_backup_core->log("Google Drive id_from_path: exception: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
60
+ }
61
+ }
62
+
63
+ if (!$found) {
64
+ $ref = new Google_Service_Drive_ParentReference;
65
+ $ref->setId($current_parent);
66
+ $dir = new Google_Service_Drive_DriveFile();
67
+ $dir->setMimeType('application/vnd.google-apps.folder');
68
+ $dir->setParents(array($ref));
69
+ $dir->setTitle($element);
70
+ $iwp_backup_core->log("Google Drive: creating path: ".$current_path.$element);
71
+ $dir = $this->service->files->insert(
72
+ $dir,
73
+ array('mimeType' => 'application/vnd.google-apps.folder')
74
+ );
75
+ $current_path .= $element.'/';
76
+ $current_parent = $dir->getId();
77
+ }
78
+ }
79
+ }
80
+
81
+ if (empty($this->ids_from_paths)) $this->ids_from_paths = array();
82
+ $this->ids_from_paths[$cache_key] = $current_parent;
83
+
84
+ return $current_parent;
85
+
86
+ } catch (Exception $e) {
87
+ $msg = $e->getMessage();
88
+ $iwp_backup_core->log("Google Drive id_from_path failure: exception (".get_class($e)."): ".$msg.' (line: '.$e->getLine().', file: '.$e->getFile().')');
89
+ if (is_a($e, 'Google_Service_Exception') && false !== strpos($msg, 'Invalid json in service response') && function_exists('mb_strpos')) {
90
+ // Aug 2015: saw a case where the gzip-encoding was not removed from the result
91
+ // https://stackoverflow.com/questions/10975775/how-to-determine-if-a-string-was-compressed
92
+ $is_gzip = false !== mb_strpos($msg , "\x1f" . "\x8b" . "\x08");
93
+ if ($is_gzip) $iwp_backup_core->log("Error: Response appears to be gzip-encoded still; something is broken in the client HTTP stack, and you should define IWP_GOOGLEDRIVE_DISABLEGZIP as true in your wp-config.php to overcome this.");
94
+ }
95
+ # One retry
96
+ return ($retry) ? $this->id_from_path($path, false) : false;
97
+ }
98
+ }
99
+
100
+ private function get_parent_id($opts) {
101
+ $filtered = apply_filters('IWP_googledrive_parent_id', false, $opts, $this->service, $this);
102
+ if (!empty($filtered)) return $filtered;
103
+ if (isset($opts['parentid'])) {
104
+ if (empty($opts['parentid'])) {
105
+ return $this->root_id();
106
+ } else {
107
+ $parent = (is_array($opts['parentid'])) ? $opts['parentid']['id'] : $opts['parentid'];
108
+ }
109
+ } else {
110
+ $path = 'infinitewp';
111
+ if (!empty($opts['gdrive_site_folder'])) {
112
+ $site_name = iwp_getSiteName();
113
+ $path = trailingslashit($path);
114
+ $path.= $site_name;
115
+ }
116
+ $parent = $this->id_from_path($path);
117
+ }
118
+ return (empty($parent)) ? $this->root_id() : $parent;
119
+ }
120
+
121
+ public function listfiles($match = 'backup_') {
122
+
123
+ $opts = $this->get_options();
124
+
125
+ if (empty($opts['secret']) || empty($opts['clientid']) || empty($opts['clientid'])) return new WP_Error('no_settings', sprintf(__('No %s settings were found', 'InfiniteWP'), __('Google Drive','InfiniteWP')));
126
+
127
+ $service = $this->bootstrap();
128
+ if (is_wp_error($service) || false == $service) return $service;
129
+
130
+ global $iwp_backup_core;
131
+
132
+ try {
133
+ $parent_id = $this->get_parent_id($opts);
134
+ $sub_items = $this->get_subitems($parent_id, 'file');
135
+ } catch (Exception $e) {
136
+ return new WP_Error(__('Google Drive list files: failed to access parent folder', 'InfiniteWP').": ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
137
+ }
138
+
139
+ $results = array();
140
+
141
+ foreach ($sub_items as $item) {
142
+ $title = "(unknown)";
143
+ try {
144
+ $title = $item->getTitle();
145
+ if (0 === strpos($title, $match)) {
146
+ $results[] = array('name' => $title, 'size' => $item->getFileSize());
147
+ }
148
+ } catch (Exception $e) {
149
+ $iwp_backup_core->log("Google Drive delete: exception: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
150
+ $ret = false;
151
+ continue;
152
+ }
153
+ }
154
+
155
+ return $results;
156
+ }
157
+
158
+ // Get a Google account access token using the refresh token
159
+ private function access_token($refresh_token, $client_id, $client_secret) {
160
+
161
+ global $iwp_backup_core;
162
+ $iwp_backup_core->log("Google Drive: requesting access token: client_id=$client_id");
163
+
164
+ $query_body = array(
165
+ 'refresh_token' => $refresh_token,
166
+ 'client_id' => $client_id,
167
+ 'client_secret' => $client_secret,
168
+ 'grant_type' => 'refresh_token'
169
+ );
170
+
171
+ $result = wp_remote_post('https://accounts.google.com/o/oauth2/token',
172
+ array(
173
+ 'timeout' => '20',
174
+ 'method' => 'POST',
175
+ 'body' => $query_body
176
+ )
177
+ );
178
+
179
+ if (is_wp_error($result)) {
180
+ $iwp_backup_core->log("Google Drive error when requesting access token");
181
+ foreach ($result->get_error_messages() as $msg) $iwp_backup_core->log("Error message: $msg");
182
+ return false;
183
+ } else {
184
+ $json_values = json_decode(wp_remote_retrieve_body($result), true);
185
+ if ( isset( $json_values['access_token'] ) ) {
186
+ $iwp_backup_core->log("Google Drive: successfully obtained access token");
187
+ return $json_values['access_token'];
188
+ } else {
189
+ $response = json_decode($result['body'],true);
190
+ if (!empty($response['error']) && 'deleted_client' == $response['error']) {
191
+ $iwp_backup_core->log(__('The client has been deleted from the Google Drive API console. Please create a new Google Drive project and reconnect with iwp_backup_core.','iwp_backup_core'), 'error');
192
+ }
193
+ $error_code = empty($response['error']) ? 'no error code' : $response['error'];
194
+ $iwp_backup_core->log("Google Drive error ($error_code) when requesting access token: response does not contain access_token. Response: ".(is_string($result['body']) ? str_replace("\n", '', $result['body']) : json_encode($result['body'])));
195
+ return false;
196
+ }
197
+ }
198
+ }
199
+
200
+ private function redirect_uri() {
201
+ return '';
202
+ }
203
+
204
+ // Acquire single-use authorization code from Google OAuth 2.0
205
+ public function gdrive_auth_request() {
206
+ $opts = $this->get_options();
207
+ // First, revoke any existing token, since Google doesn't appear to like issuing new ones
208
+ if (!empty($opts['token'])) $this->gdrive_auth_revoke();
209
+
210
+ // We use 'force' here for the approval_prompt, not 'auto', as that deals better with messy situations where the user authenticated, then changed settings
211
+
212
+ # We require access to all Google Drive files (not just ones created by this app - scope https://www.googleapis.com/auth/drive.file) - because we need to be able to re-scan storage for backups uploaded by other installs
213
+ $params = array(
214
+ 'response_type' => 'code',
215
+ 'client_id' => $opts['clientid'],
216
+ 'redirect_uri' => $this->redirect_uri(),
217
+ 'scope' => 'https://www.googleapis.com/auth/drive',
218
+ 'state' => 'token',
219
+ 'access_type' => 'offline',
220
+ 'approval_prompt' => 'force'
221
+ );
222
+ if(headers_sent()) {
223
+ global $iwp_backup_core;
224
+ $iwp_backup_core->log(sprintf(__('The %s authentication could not go ahead, because something else on your site is breaking it. Try disabling your other plugins and switching to a default theme. (Specifically, you are looking for the component that sends output (most likely PHP warnings/errors) before the page begins. Turning off any debugging settings may also help).', ''), 'Google Drive'), 'error');
225
+ } else {
226
+ header('Location: https://accounts.google.com/o/oauth2/auth?'.http_build_query($params, null, '&'));
227
+ }
228
+ }
229
+
230
+
231
+ // This function just does the formalities, and off-loads the main work to upload_file
232
+ public function backup($backup_array) {
233
+
234
+ global $iwp_backup_core, $IWP_backup;
235
+
236
+ $service = $this->bootstrap();
237
+ if (false == $service || is_wp_error($service)) return $service;
238
+
239
+ $iwp_backup_dir = trailingslashit($iwp_backup_core->backups_dir_location());
240
+
241
+ $opts = $this->get_options();
242
+
243
+ try {
244
+ $parent_id = $this->get_parent_id($opts);
245
+ } catch (Exception $e) {
246
+ $iwp_backup_core->log("Google Drive upload: failed to access parent folder: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
247
+ $iwp_backup_core->log(sprintf(__('Failed to upload to %s','InfiniteWP'),__('Google Drive','InfiniteWP')).': '.__('failed to access parent folder', 'InfiniteWP').' ('.$e->getMessage().')', 'error');
248
+ return false;
249
+ }
250
+
251
+ foreach ($backup_array as $file) {
252
+
253
+ $available_quota = -1;
254
+
255
+ try {
256
+ $about = $service->about->get();
257
+ $quota_total = max($about->getQuotaBytesTotal(), 1);
258
+ $quota_used = $about->getQuotaBytesUsed();
259
+ $available_quota = $quota_total - $quota_used;
260
+ $message = "Google Drive quota usage: used=".round($quota_used/1048576,1)." MB, total=".round($quota_total/1048576,1)." MB, available=".round($available_quota/1048576,1)." MB";
261
+ $iwp_backup_core->log($message);
262
+ } catch (Exception $e) {
263
+ $iwp_backup_core->log("Google Drive quota usage: failed to obtain this information: ".$e->getMessage());
264
+ }
265
+
266
+ $file_path = $iwp_backup_dir.$file;
267
+ $file_name = basename($file_path);
268
+ $iwp_backup_core->log("$file_name: Attempting to upload to Google Drive (into folder id: $parent_id)");
269
+
270
+ $filesize = filesize($file_path);
271
+ $already_failed = false;
272
+ if ($available_quota != -1) {
273
+ if ($filesize > $available_quota) {
274
+ $already_failed = true;
275
+ $iwp_backup_core->log("File upload expected to fail: file ($file_name) size is $filesize b, whereas available quota is only $available_quota b");
276
+ $iwp_backup_core->log(sprintf(__("Account full: your %s account has only %d bytes left, but the file to be uploaded is %d bytes",'InfiniteWP'),__('Google Drive', 'InfiniteWP'), $available_quota, $filesize), +'error');
277
+ }
278
+ }
279
+
280
+ if (!$already_failed && $filesize > 10737418240) {
281
+ # 10GB
282
+ $iwp_backup_core->log("File upload expected to fail: file ($file_name) size is $filesize b (".round($filesize/1073741824, 4)." GB), whereas Google Drive's limit is 10GB (1073741824 bytes)");
283
+ $iwp_backup_core->log(sprintf(__("Upload expected to fail: the %s limit for any single file is %s, whereas this file is %s GB (%d bytes)",'InfiniteWP'),__('Google Drive', 'InfiniteWP'), '10GB (1073741824)', round($filesize/1073741824, 4), $filesize), 'warning');
284
+ }
285
+
286
+ try {
287
+ $timer_start = microtime(true);
288
+ if ($this->upload_file($file_path, $parent_id)) {
289
+ $iwp_backup_core->log('OK: Archive ' . $file_name . ' uploaded to Google Drive in ' . ( round(microtime(true) - $timer_start, 2) ) . ' seconds');
290
+ $iwp_backup_core->uploaded_file($file);
291
+ } else {
292
+ $iwp_backup_core->log("ERROR: $file_name: Failed to upload to Google Drive" );
293
+ $iwp_backup_core->log("$file_name: ".sprintf(__('Failed to upload to %s','InfiniteWP'),__('Google Drive','InfiniteWP')), 'error');
294
+ }
295
+ } catch (Exception $e) {
296
+ $msg = $e->getMessage();
297
+ $iwp_backup_core->log("ERROR: Google Drive upload error: ".$msg.' (line: '.$e->getLine().', file: '.$e->getFile().')');
298
+ if (false !== ($p = strpos($msg, 'The user has exceeded their Drive storage quota'))) {
299
+ $iwp_backup_core->log("$file_name: ".sprintf(__('Failed to upload to %s','InfiniteWP'),__('Google Drive','InfiniteWP')).': '.substr($msg, $p), 'error');
300
+ } else {
301
+ $iwp_backup_core->log("$file_name: ".sprintf(__('Failed to upload to %s','InfiniteWP'),__('Google Drive','InfiniteWP')), 'error');
302
+ }
303
+ $this->client->setDefer(false);
304
+ }
305
+ }
306
+
307
+ return null;
308
+ }
309
+
310
+ public function bootstrap($access_token = false) {
311
+
312
+ global $iwp_backup_core;
313
+
314
+ if (!empty($this->service) && is_object($this->service) && is_a($this->service, 'Google_Service_Drive')) return $this->service;
315
+
316
+ $opts = $this->get_options();
317
+
318
+ if (empty($access_token)) {
319
+ if (empty($opts['token']) || empty($opts['clientid']) || empty($opts['secret'])) {
320
+ $iwp_backup_core->log('Google Drive: this account is not authorised');
321
+ $iwp_backup_core->log('Google Drive: '.__('Account is not authorized.', 'InfiniteWP'), 'error', 'googledrivenotauthed');
322
+ return new WP_Error('not_authorized', __('Account is not authorized.', 'InfiniteWP'));
323
+ }
324
+ }
325
+
326
+ $spl = spl_autoload_functions();
327
+ if (is_array($spl)) {
328
+ if (in_array('wpbgdc_autoloader', $spl)) spl_autoload_unregister('wpbgdc_autoloader');
329
+ // http://www.wpdownloadmanager.com/download/google-drive-explorer/ - but also others, since this is the default function name used by the Google SDK
330
+ if (in_array('google_api_php_client_autoload', $spl)) spl_autoload_unregister('google_api_php_client_autoload');
331
+ }
332
+
333
+ if ((!class_exists('Google_Config') || !class_exists('Google_Client') || !class_exists('Google_Service_Drive') || !class_exists('Google_Http_Request')) && !function_exists('google_api_php_client_autoload_iwp')) {
334
+ require_once($GLOBALS['iwp_mmb_plugin_dir'].'/lib/Google2/autoload.php');
335
+ }
336
+
337
+ if (!class_exists('IWP_MMB_Google_Http_MediaFileUpload')) {
338
+ require_once($GLOBALS['iwp_mmb_plugin_dir'].'/lib/google-extensions.php');
339
+ }
340
+
341
+ $config = new Google_Config();
342
+ $config->setClassConfig('Google_IO_Abstract', 'request_timeout_seconds', 60);
343
+ # In our testing, $service->about->get() fails if gzip is not disabled when using the stream wrapper
344
+ if (!function_exists('curl_version') || !function_exists('curl_exec') || (defined('IWP_GOOGLEDRIVE_DISABLEGZIP') && IWP_GOOGLEDRIVE_DISABLEGZIP)) {
345
+ $config->setClassConfig('Google_Http_Request', 'disable_gzip', true);
346
+ }
347
+
348
+ $client = new Google_Client($config);
349
+ $client->setClientId($opts['clientid']);
350
+ $client->setClientSecret($opts['secret']);
351
+ // $client->setUseObjects(true);
352
+
353
+ if (empty($access_token)) {
354
+ $access_token = $this->access_token($opts['token'], $opts['clientid'], $opts['secret']);
355
+ }
356
+
357
+ // Do we have an access token?
358
+ if (empty($access_token) || is_wp_error($access_token)) {
359
+ $iwp_backup_core->log('ERROR: Have not yet obtained an access token from Google (has the user authorised?)');
360
+ $iwp_backup_core->log(__('Have not yet obtained an access token from Google - you need to authorise or re-authorise your connection to Google Drive.','InfiniteWP'), 'error');
361
+ return $access_token;
362
+ }
363
+
364
+ $client->setAccessToken(json_encode(array(
365
+ 'access_token' => $access_token,
366
+ 'refresh_token' => $opts['token']
367
+ )));
368
+
369
+ $io = $client->getIo();
370
+ $setopts = array();
371
+
372
+ if (is_a($io, 'Google_IO_Curl')) {
373
+ $setopts[CURLOPT_SSL_VERIFYPEER] = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_disableverify') ? false : true;
374
+ if (!IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_useservercerts')) $setopts[CURLOPT_CAINFO] = $GLOBALS['iwp_mmb_plugin_dir'].'/lib/cacert.pem';
375
+ // Raise the timeout from the default of 15
376
+ $setopts[CURLOPT_TIMEOUT] = 60;
377
+ $setopts[CURLOPT_CONNECTTIMEOUT] = 15;
378
+ if (defined('IWP_IPV4_ONLY') && IWP_IPV4_ONLY) $setopts[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4;
379
+ } elseif (is_a($io, 'Google_IO_Stream')) {
380
+ $setopts['timeout'] = 60;
381
+ # We had to modify the SDK to support this
382
+ # https://wiki.php.net/rfc/tls-peer-verification - before PHP 5.6, there is no default CA file
383
+ if (!IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_useservercerts') || (version_compare(PHP_VERSION, '5.6.0', '<'))) $setopts['cafile'] = $GLOBALS['iwp_mmb_plugin_dir'].'/lib/cacert.pem';
384
+ if (IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_disableverify')) $setopts['disable_verify_peer'] = true;
385
+ }
386
+
387
+ $io->setOptions($setopts);
388
+
389
+ $service = new Google_Service_Drive($client);
390
+ $this->client = $client;
391
+ $this->service = $service;
392
+
393
+ try {
394
+ # Get the folder name, if not previously known (this is for the legacy situation where an id, not a name, was stored)
395
+ if (!empty($opts['parentid']) && (!is_array($opts['parentid']) || empty($opts['parentid']['name']))) {
396
+ $rootid = $this->root_id();
397
+ $title = '';
398
+ $parentid = is_array($opts['parentid']) ? $opts['parentid']['id'] : $opts['parentid'];
399
+ while ((!empty($parentid) && $parentid != $rootid)) {
400
+ $resource = $service->files->get($parentid);
401
+ $title = ($title) ? $resource->getTitle().'/'.$title : $resource->getTitle();
402
+ $parents = $resource->getParents();
403
+ if (is_array($parents) && count($parents)>0) {
404
+ $parent = array_shift($parents);
405
+ $parentid = is_a($parent, 'Google_Service_Drive_ParentReference') ? $parent->getId() : false;
406
+ } else {
407
+ $parentid = false;
408
+ }
409
+ }
410
+ if (!empty($title)) {
411
+ $opts['parentid'] = array(
412
+ 'id' => (is_array($opts['parentid']) ? $opts['parentid']['id'] : $opts['parentid']),
413
+ 'name' => $title
414
+ );
415
+ $this->set_options($opts, true);
416
+ }
417
+ }
418
+ } catch (Exception $e) {
419
+ $iwp_backup_core->log("Google Drive: failed to obtain name of parent folder: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
420
+ }
421
+
422
+ return $this->service;
423
+
424
+ }
425
+
426
+ // Returns array of Google_Service_Drive_DriveFile objects
427
+ private function get_subitems($parent_id, $type = 'any', $match = 'backup_') {
428
+ $q = '"'.$parent_id.'" in parents and trashed = false';
429
+ if ('dir' == $type) {
430
+ $q .= ' and mimeType = "application/vnd.google-apps.folder"';
431
+ } elseif ('file' == $type) {
432
+ $q .= ' and mimeType != "application/vnd.google-apps.folder"';
433
+ }
434
+ # We used to use 'contains' in both cases, but this exposed some bug that might be in the SDK or at the Google end - a result that matched for = was not returned with contains
435
+ if (!empty($match)) {
436
+ if ('backup_' == $match) {
437
+ $q .= " and title contains '$match'";
438
+ } else {
439
+ $q .= " and title contains '$match'";
440
+ }
441
+ }
442
+
443
+ $result = array();
444
+ $pageToken = NULL;
445
+
446
+ do {
447
+ try {
448
+ // Default for maxResults is 100
449
+ $parameters = array('q' => $q, 'maxResults' => 200);
450
+ if ($pageToken) {
451
+ $parameters['pageToken'] = $pageToken;
452
+ }
453
+ $files = $this->service->files->listFiles($parameters);
454
+
455
+ $result = array_merge($result, $files->getItems());
456
+ $pageToken = $files->getNextPageToken();
457
+ } catch (Exception $e) {
458
+ global $iwp_backup_core;
459
+ $iwp_backup_core->log("Google Drive: get_subitems: An error occurred (will not fetch further): " . $e->getMessage());
460
+ $pageToken = NULL;
461
+ }
462
+ } while ($pageToken);
463
+
464
+ return $result;
465
+ }
466
+
467
+ public function delete($files, $data=null, $sizeinfo = array()) {
468
+
469
+ if (is_string($files)) $files=array($files);
470
+
471
+ $service = $this->bootstrap();
472
+ if (is_wp_error($service) || false == $service) return $service;
473
+
474
+ $opts = $this->get_options();
475
+
476
+ global $iwp_backup_core;
477
+
478
+ try {
479
+ $parent_id = $this->get_parent_id($opts);
480
+ $sub_items = $this->get_subitems($parent_id, 'file');
481
+ } catch (Exception $e) {
482
+ $iwp_backup_core->log("Google Drive delete: failed to access parent folder: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
483
+ return false;
484
+ }
485
+
486
+ $ret = true;
487
+
488
+ foreach ($sub_items as $item) {
489
+ $title = "(unknown)";
490
+ try {
491
+ $title = $item->getTitle();
492
+ if (in_array($title, $files)) {
493
+ $service->files->delete($item->getId());
494
+ $iwp_backup_core->log("$title: Deletion successful");
495
+ if(($key = array_search($title, $files)) !== false) {
496
+ unset($files[$key]);
497
+ }
498
+ }
499
+ } catch (Exception $e) {
500
+ $iwp_backup_core->log("Google Drive delete: exception: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
501
+ $ret = false;
502
+ continue;
503
+ }
504
+ }
505
+
506
+ foreach ($files as $file) {
507
+ $iwp_backup_core->log("$file: Deletion failed: file was not found");
508
+ }
509
+
510
+ return $ret;
511
+
512
+ }
513
+
514
+ private function upload_file($file, $parent_id, $try_again = true) {
515
+
516
+ global $iwp_backup_core;
517
+ $opts = $this->get_options();
518
+ $basename = basename($file);
519
+
520
+ $service = $this->service;
521
+ $client = $this->client;
522
+
523
+ # See: https://github.com/google/google-api-php-client/blob/master/examples/fileupload.php (at time of writing, only shows how to upload in chunks, not how to resume)
524
+
525
+ $client->setDefer(true);
526
+
527
+ $local_size = filesize($file);
528
+
529
+ $gdfile = new Google_Service_Drive_DriveFile();
530
+ $gdfile->title = $basename;
531
+
532
+ $ref = new Google_Service_Drive_ParentReference;
533
+ $ref->setId($parent_id);
534
+ $gdfile->setParents(array($ref));
535
+
536
+ $size = 0;
537
+ $request = $service->files->insert($gdfile);
538
+
539
+ $chunk_bytes = 1048576;
540
+
541
+ $hash = md5($file);
542
+ $transkey = 'gdresume_'.$hash;
543
+ // This is unset upon completion, so if it is set then we are resuming
544
+ $possible_location = $iwp_backup_core->jobdata_get($transkey);
545
+
546
+ if (is_array($possible_location)) {
547
+
548
+ $headers = array( 'content-range' => "bytes */".$local_size);
549
+
550
+ $httpRequest = new Google_Http_Request(
551
+ $possible_location[0],
552
+ 'PUT',
553
+ $headers,
554
+ ''
555
+ );
556
+ $response = $this->client->getIo()->makeRequest($httpRequest);
557
+ $can_resume = false;
558
+
559
+ $response_http_code = $response->getResponseHttpCode();
560
+
561
+ if ($response_http_code == 200 || $response_http_code == 201) {
562
+ $client->setDefer(false);
563
+ $iwp_backup_core->jobdata_delete($transkey);
564
+ $iwp_backup_core->log("$basename: upload appears to be already complete (HTTP code: $response_http_code)");
565
+ return true;
566
+ }
567
+
568
+ if (308 == $response_http_code) {
569
+ $range = $response->getResponseHeader('range');
570
+ if (!empty($range) && preg_match('/bytes=0-(\d+)$/', $range, $matches)) {
571
+ $can_resume = true;
572
+ $possible_location[1] = $matches[1]+1;
573
+ $iwp_backup_core->log("$basename: upload already began; attempting to resume from byte ".$matches[1]);
574
+ }
575
+ }
576
+ if (!$can_resume) {
577
+ $iwp_backup_core->log("$basename: upload already began; attempt to resume did not succeed (HTTP code: ".$response_http_code.")");
578
+ }
579
+ }
580
+
581
+ $media = new IWP_MMB_Google_Http_MediaFileUpload(
582
+ $client,
583
+ $request,
584
+ (('.zip' == substr($basename, -4, 4)) ? 'application/zip' : 'application/octet-stream'),
585
+ null,
586
+ true,
587
+ $chunk_bytes
588
+ );
589
+ $media->setFileSize($local_size);
590
+
591
+ if (!empty($possible_location)) {
592
+ // $media->resumeUri = $possible_location[0];
593
+ // $media->progress = $possible_location[1];
594
+ $media->IWP_setResumeUri($possible_location[0]);
595
+ $media->IWP_setProgress($possible_location[1]);
596
+ $size = $possible_location[1];
597
+ }
598
+ if ($size >= $local_size) return true;
599
+
600
+ $status = false;
601
+ if (false == ($handle = fopen($file, 'rb'))) {
602
+ $iwp_backup_core->log("Google Drive: failed to open file: $basename");
603
+ $iwp_backup_core->log("$basename: ".sprintf(__('%s Error: Failed to open local file', 'iwp_backup_core'),'Google Drive'), 'error');
604
+ return false;
605
+ }
606
+ if ($size > 0 && 0 != fseek($handle, $size)) {
607
+ $iwp_backup_core->log("Google Drive: failed to fseek file: $basename, $size");
608
+ $iwp_backup_core->log("$basename (fseek): ".sprintf(__('%s Error: Failed to open local file', 'InfiniteWP'), 'Google Drive'), 'error');
609
+ return false;
610
+ }
611
+
612
+ $pointer = $size;
613
+
614
+ try {
615
+ while (!$status && !feof($handle)) {
616
+ $chunk = fread($handle, $chunk_bytes);
617
+ # Error handling??
618
+ $pointer += strlen($chunk);
619
+ $status = $media->nextChunk($chunk);
620
+ $iwp_backup_core->jobdata_set($transkey, array($media->IWP_getResumeUri(), $media->getProgress()));
621
+ $iwp_backup_core->record_uploaded_chunk(round(100*$pointer/$local_size, 1), $media->getProgress(), $file);
622
+ }
623
+
624
+ } catch (Google_Service_Exception $e) {
625
+ $iwp_backup_core->log("ERROR: Google Drive upload error (".get_class($e)."): ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
626
+ $client->setDefer(false);
627
+ fclose($handle);
628
+ $iwp_backup_core->jobdata_delete($transkey);
629
+ if (false == $try_again) throw($e);
630
+ # Reset this counter to prevent the something_useful_happened condition's possibility being sent into the far future and potentially missed
631
+ if ($iwp_backup_core->current_resumption > 9) $iwp_backup_core->jobdata_set('uploaded_lastreset', $iwp_backup_core->current_resumption);
632
+ return $this->upload_file($file, $parent_id, false);
633
+ }
634
+
635
+ // The final value of $status will be the data from the API for the object
636
+ // that has been uploaded.
637
+ $result = false;
638
+ if ($status != false) $result = $status;
639
+
640
+ fclose($handle);
641
+ $client->setDefer(false);
642
+ $iwp_backup_core->jobdata_delete($transkey);
643
+
644
+ return true;
645
+
646
+ }
647
+
648
+ public function download($file) {
649
+
650
+ global $iwp_backup_core;
651
+
652
+ $service = $this->bootstrap();
653
+ if (false == $service || is_wp_error($service)) return false;
654
+
655
+ global $iwp_backup_core;
656
+ $opts = $this->get_options();
657
+
658
+ try {
659
+ $parent_id = $this->get_parent_id($opts);
660
+ #$gdparent = $service->files->get($parent_id);
661
+ $site_name = iwp_getSiteName();
662
+ $sub_items = $this->get_subitems($parent_id, 'file', $file);
663
+ } catch (Exception $e) {
664
+ $iwp_backup_core->log("Google Drive delete: failed to access parent folder: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
665
+ return false;
666
+ }
667
+ $found = false;
668
+ foreach ($sub_items as $item) {
669
+ if ($found) continue;
670
+ $title = "(unknown)";
671
+ try {
672
+ $title = $item->getTitle();
673
+ if ($title == $file) {
674
+ $gdfile = $item;
675
+ $found = $item->getId();
676
+ $size = $item->getFileSize();
677
+ }
678
+ } catch (Exception $e) {
679
+ $iwp_backup_core->log("Google Drive download: exception: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
680
+ }
681
+ }
682
+
683
+ if (false === $found) {
684
+ $iwp_backup_core->log("Google Drive download: failed: file not found");
685
+ $iwp_backup_core->log("$file: ".sprintf(__("%s Error",'InfiniteWP'), 'Google Drive').": ".__('File not found', 'InfiniteWP'), 'error');
686
+ return false;
687
+ }
688
+
689
+ $download_to = $iwp_backup_core->backups_dir_location().'/'.$file;
690
+
691
+ $existing_size = (file_exists($download_to)) ? filesize($download_to) : 0;
692
+
693
+ if ($existing_size >= $size) {
694
+ $iwp_backup_core->log('Google Drive download: was already downloaded ('.filesize($download_to)."/$size bytes)");
695
+ return true;
696
+ }
697
+
698
+ # Chunk in units of 2MB
699
+ $chunk_size = 2097152;
700
+
701
+ try {
702
+ while ($existing_size < $size) {
703
+
704
+ $end = min($existing_size + $chunk_size, $size);
705
+
706
+ if ($existing_size > 0) {
707
+ $put_flag = FILE_APPEND;
708
+ $headers = array('Range' => 'bytes='.$existing_size.'-'.$end);
709
+ } else {
710
+ $put_flag = null;
711
+ $headers = ($end < $size) ? array('Range' => 'bytes=0-'.$end) : array();
712
+ }
713
+
714
+ $pstart = round(100*$existing_size/$size,1);
715
+ $pend = round(100*$end/$size,1);
716
+ $iwp_backup_core->log("Requesting byte range: $existing_size - $end ($pstart - $pend %)");
717
+
718
+ $request = $this->client->getAuth()->sign(new Google_Http_Request($gdfile->getDownloadUrl(), 'GET', $headers, null));
719
+ $http_request = $this->client->getIo()->makeRequest($request);
720
+ $http_response = $http_request->getResponseHttpCode();
721
+ if (200 == $http_response || 206 == $http_response) {
722
+ file_put_contents($download_to, $http_request->getResponseBody(), $put_flag);
723
+ } else {
724
+ $iwp_backup_core->log("Google Drive download: failed: unexpected HTTP response code: ".$http_response);
725
+ $iwp_backup_core->log(sprintf(__("%s download: failed: file not found", 'iwp_backup_core'), 'Google Drive'), 'error');
726
+ return false;
727
+ }
728
+
729
+ clearstatcache();
730
+ $new_size = filesize($download_to);
731
+ if ($new_size > $existing_size) {
732
+ $existing_size = $new_size;
733
+ } else {
734
+ throw new Exception('Failed to obtain any new data at size: '.$existing_size);
735
+ }
736
+ }
737
+ } catch (Exception $e) {
738
+ $iwp_backup_core->log("Google Drive download: exception: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
739
+ }
740
+
741
+ return true;
742
+ }
743
+
744
+ public function config_print() {
745
+ $opts = $this->get_options();
746
+
747
+ if (isset($opts['parentid'])) {
748
+ $parentid = (is_array($opts['parentid'])) ? $opts['parentid']['id'] : $opts['parentid'];
749
+ $showparent = (is_array($opts['parentid']) && !empty($opts['parentid']['name'])) ? $opts['parentid']['name'] : $parentid;
750
+ }
751
+ }
752
+
753
+ public function get_backup_file_size($file){
754
+ global $iwp_backup_core;
755
+
756
+ $service = $this->bootstrap();
757
+ if (false == $service || is_wp_error($service)) return false;
758
+
759
+ global $iwp_backup_core;
760
+ $opts = $this->get_options();
761
+ try {
762
+ $parent_id = $this->get_parent_id($opts);
763
+ #$gdparent = $service->files->get($parent_id);
764
+ $site_name = iwp_getSiteName();
765
+ $sub_items = $this->get_subitems($parent_id, 'file', $file);
766
+ } catch (Exception $e) {
767
+ $iwp_backup_core->log("Google Drive delete: failed to access parent folder: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
768
+ return false;
769
+ }
770
+
771
+ foreach ($sub_items as $item) {
772
+ if ($found) continue;
773
+ $title = "(unknown)";
774
+ try {
775
+ $title = $item->getTitle();
776
+ if ($title == $file) {
777
+ $gdfile = $item;
778
+ $found = $item->getId();
779
+ $size = $item->getFileSize();
780
+ return $size;
781
+ }
782
+ } catch (Exception $e) {
783
+ $iwp_backup_core->log("Google Drive download: exception: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
784
+ }
785
+ }
786
+ }
787
+ }
backup/s3.php ADDED
@@ -0,0 +1,899 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+
4
+ if ( ! defined('ABSPATH') )
5
+ die();
6
+
7
+ class IWP_MMB_S3Exception extends Exception {
8
+ public function __construct($message, $file, $line, $code = 0) {
9
+ parent::__construct($message, $code);
10
+ $this->file = $file;
11
+ $this->line = $line;
12
+ }
13
+ }
14
+
15
+ if (!class_exists('IWP_MMB_UploadModule')) require_once($GLOBALS['iwp_mmb_plugin_dir'].'/backup/backup.upload.php');
16
+
17
+ class IWP_MMB_UploadModule_s3 extends IWP_MMB_UploadModule {
18
+
19
+ private $s3_object;
20
+
21
+ private $got_with;
22
+
23
+ protected $quota_used = null;
24
+
25
+ protected $s3_exception;
26
+
27
+ protected $download_chunk_size = 10485760;
28
+
29
+ /**
30
+ * Retrieve specific options for this remote storage module
31
+ *
32
+ * @return Array - an array of options
33
+ */
34
+ protected function get_config() {
35
+ $opts = $this->get_options();
36
+ $opts['whoweare'] = 'S3';
37
+ $opts['whoweare_long'] = 'Amazon S3';
38
+ $opts['key'] = 's3';
39
+ return $opts;
40
+ }
41
+
42
+ /**
43
+ * This method overrides the parent method and lists the supported features of this remote storage option.
44
+ *
45
+ * @return Array - an array of supported features (any features not mentioned are asuumed to not be supported)
46
+ */
47
+ public function get_supported_features() {
48
+ // This options format is handled via only accessing options via $this->get_options()
49
+ return array('multi_options');
50
+ }
51
+
52
+ /**
53
+ * Retrieve default options for this remote storage module.
54
+ *
55
+ * @return Array - an array of options
56
+ */
57
+ public function get_default_options() {
58
+ return array(
59
+ 'accesskey' => '',
60
+ 'secretkey' => '',
61
+ 'path' => '',
62
+ 'rrs' => '',
63
+ 'server_side_encryption' => '',
64
+ );
65
+ }
66
+
67
+ protected function indicate_s3_class() {
68
+ // N.B. : The classes must have different names, as if multiple remote storage options are chosen, then we could theoretically need both (if both Amazon and a compatible-S3 provider are used)
69
+ // Conditional logic, for new AWS SDK (N.B. 3.x branch requires PHP 5.5, so we're on 2.x - requires 5.3.3)
70
+
71
+ $opts = $this->get_config();
72
+ // IWP_MMB_S3 is used when not accessing Amazon Web Services
73
+ $class_to_use = 'IWP_MMB_S3';
74
+ if (version_compare(PHP_VERSION, '5.3.3', '>=') && !empty($opts['key']) && ('s3' == $opts['key'] || 'updraftvault' == $opts['key']) && (!defined('IWP_S3_OLDLIB') || !IWP_S3_OLDLIB)) {
75
+ $class_to_use = 'IWP_MMB_S3_Compat';
76
+ }
77
+
78
+ if ('IWP_MMB_S3_Compat' == $class_to_use) {
79
+ if (!class_exists($class_to_use)) include_once($GLOBALS['iwp_mmb_plugin_dir'].'/lib/S3compat.php');
80
+ } else {
81
+ if (!class_exists($class_to_use)) include_once($GLOBALS['iwp_mmb_plugin_dir'].'/lib/S3.php');
82
+ }
83
+ return $class_to_use;
84
+
85
+ }
86
+
87
+ /**
88
+ * Get an S3 object, after setting our options
89
+ *
90
+ * @param string $key S3 Key
91
+ * @param string $secret S3 secret
92
+ * @param boolean $useservercerts User server certificates
93
+ * @param boolean $disableverify Check if disableverify is enabled
94
+ * @param boolean $nossl Check if there is SSL or not
95
+ * @param string $endpoint S3 endpoint
96
+ * @param boolean $sse A flag to use server side encryption
97
+ * @return array
98
+ */
99
+ public function getS3($key, $secret, $useservercerts, $disableverify, $nossl, $endpoint = null, $sse = false) {
100
+
101
+ if (!empty($this->s3_object) && !is_wp_error($this->s3_object)) return $this->s3_object;
102
+
103
+ if (is_string($key)) $key = trim($key);
104
+ if (is_string($secret)) $secret = trim($secret);
105
+
106
+ // Saved in case the object needs recreating for the corner-case where there is no permission to look up the bucket location
107
+ $this->got_with = array(
108
+ 'key' => $key,
109
+ 'secret' => $secret,
110
+ 'useservercerts' => $useservercerts,
111
+ 'disableverify' => $disableverify,
112
+ 'nossl' => $nossl,
113
+ 'server_side_encryption' => $sse
114
+ );
115
+
116
+ if (is_wp_error($key)) return $key;
117
+
118
+ if ('' == $key || '' == $secret) {
119
+ return new WP_Error('no_settings', __('No settings were found - please go to the Settings tab and check your settings', 'InfiniteWP'));
120
+ }
121
+
122
+ global $iwp_backup_core;
123
+
124
+ $use_s3_class = $this->indicate_s3_class();
125
+
126
+ if (!class_exists('WP_HTTP_Proxy')) include_once(ABSPATH.WPINC.'/class-http.php');
127
+ $proxy = new WP_HTTP_Proxy();
128
+
129
+ $use_ssl = true;
130
+ $ssl_ca = true;
131
+ if (!$nossl) {
132
+ $curl_version = (function_exists('curl_version')) ? curl_version() : array('features' => null);
133
+ $curl_ssl_supported = ($curl_version['features'] & defined('CURL_VERSION_SSL') && CURL_VERSION_SSL);
134
+ if ($curl_ssl_supported) {
135
+ if ($disableverify) {
136
+ $ssl_ca = false;
137
+ $iwp_backup_core->log("S3: Disabling verification of SSL certificates");
138
+ } else {
139
+ if ($useservercerts) {
140
+ $iwp_backup_core->log("S3: Using the server's SSL certificates");
141
+ $ssl_ca = 'system';
142
+ } else {
143
+ $ssl_ca = file_exists($GLOBALS['iwp_mmb_plugin_dir'].'/lib/cacert.pem') ? $GLOBALS['iwp_mmb_plugin_dir'].'/lib/cacert.pem' : true;
144
+ }
145
+ }
146
+ } else {
147
+ $use_ssl = false;
148
+ $iwp_backup_core->log("S3: Curl/SSL is not available. Communications will not be encrypted.");
149
+ }
150
+ } else {
151
+ $use_ssl = false;
152
+ $iwp_backup_core->log("SSL was disabled via the user's preference. Communications will not be encrypted.");
153
+ }
154
+
155
+ try {
156
+ $s3 = new $use_s3_class($key, $secret, $use_ssl, $ssl_ca, $endpoint);
157
+ } catch (Exception $e) {
158
+
159
+ // Catch a specific PHP engine bug - see HS#6364
160
+ if ('IWP_MMB_S3_Compat' == $use_s3_class && is_a($e, 'InvalidArgumentException') && false !== strpos('Invalid signature type: s3', $e->getMessage())) {
161
+ include_once($GLOBALS['iwp_mmb_plugin_dir'].'/lib/S3.php');
162
+ $use_s3_class = 'IWP_MMB_S3';
163
+ $try_again = true;
164
+ } else {
165
+ $iwp_backup_core->log(sprintf(__('%s Error: Failed to initialise', 'iwp_backup_core'), 'S3').": ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
166
+ $iwp_backup_core->log(sprintf(__('%s Error: Failed to initialise', 'InfiniteWP'), $key), 'S3');
167
+ return new WP_Error('s3_init_failed', sprintf(__('%s Error: Failed to initialise', 'InfiniteWP'), 'S3').": ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
168
+ }
169
+ }
170
+
171
+ if (!empty($try_again)) {
172
+ try {
173
+ $s3 = new $use_s3_class($key, $secret, $use_ssl, $ssl_ca, $endpoint);
174
+ } catch (Exception $e) {
175
+ $iwp_backup_core->log(sprintf(__('%s Error: Failed to initialise', 'InfiniteWP'), 'S3').": ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
176
+ $iwp_backup_core->log(sprintf(__('%s Error: Failed to initialise', 'InfiniteWP'), $key), 'S3');
177
+ return new WP_Error('s3_init_failed', sprintf(__('%s Error: Failed to initialise', 'InfiniteWP'), 'S3').": ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
178
+ }
179
+ $iwp_backup_core->log("S3: Hit a PHP engine bug - had to switch to the older S3 library (which is incompatible with signatureV4, which may cause problems later on if using a region that requires it)");
180
+ }
181
+
182
+ if ($proxy->is_enabled()) {
183
+ // WP_HTTP_Proxy returns empty strings where we want nulls
184
+ $user = $proxy->username();
185
+ if (empty($user)) {
186
+ $user = null;
187
+ $pass = null;
188
+ } else {
189
+ $pass = $proxy->password();
190
+ if (empty($pass)) $pass = null;
191
+ }
192
+ $port = (int) $proxy->port();
193
+ if (empty($port)) $port = 8080;
194
+ $s3->setProxy($proxy->host(), $user, $pass, CURLPROXY_HTTP, $port);
195
+ }
196
+
197
+ if (method_exists($s3, 'setServerSideEncryption') ) $s3->setServerSideEncryption('AES256');
198
+
199
+ $this->s3_object = $s3;
200
+
201
+ return $this->s3_object;
202
+ }
203
+
204
+ protected function set_region($obj, $region, $bucket_name = '') {
205
+ global $iwp_backup_core;
206
+ switch ($region) {
207
+ case 'EU':
208
+ case 'eu-west-1':
209
+ $endpoint = 's3-eu-west-1.amazonaws.com';
210
+ break;
211
+ case 'us-east-1':
212
+ $endpoint = 's3.amazonaws.com';
213
+ break;
214
+ case 'us-west-1':
215
+ case 'us-east-2':
216
+ case 'us-west-2':
217
+ case 'eu-west-2':
218
+ case 'ap-southeast-1':
219
+ case 'ap-southeast-2':
220
+ case 'ap-northeast-1':
221
+ case 'ap-northeast-2':
222
+ case 'sa-east-1':
223
+ case 'eu-west-3':
224
+ case 'ca-central-1':
225
+ case 'us-gov-west-1':
226
+ case 'eu-central-1':
227
+ $endpoint = 's3-'.$region.'.amazonaws.com';
228
+ break;
229
+ case 'ap-south-1':
230
+ case 'cn-north-1':
231
+ $endpoint = 's3.'.$region.'.amazonaws.com.cn';
232
+ break;
233
+ default:
234
+ break;
235
+ }
236
+
237
+ if (isset($endpoint)) {
238
+ if (is_a($obj, 'IWP_MMB_S3_Compat')) {
239
+ $iwp_backup_core->log("Set region: $region");
240
+ $obj->setRegion($region);
241
+ return;
242
+ }
243
+
244
+ $iwp_backup_core->log("Set endpoint: $endpoint");
245
+
246
+ return $obj->setEndpoint($endpoint);
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Perform the upload of backup archives
252
+ *
253
+ * @param Array $backup_array - a list of file names (basenames) (within UD's directory) to be uploaded
254
+ *
255
+ * @return Mixed - return (boolean)false ot indicate failure, or anything else to have it passed back at the delete stage (most useful for a storage object).
256
+ */
257
+ public function backup($backup_array) {
258
+
259
+ global $iwp_backup_core;
260
+
261
+ $config = $this->get_config();
262
+
263
+ if (empty($config['accesskey']) && !empty($config['error_message'])) {
264
+ $err = new WP_Error('no_settings', $config['error_message']);
265
+ return $iwp_backup_core->log_wp_error($err, false, true);
266
+ }
267
+
268
+ $whoweare = $config['whoweare'];
269
+ $whoweare_key = $config['key'];
270
+ $whoweare_keys = substr($whoweare_key, 0, 3);
271
+ $sse = empty($config['server_side_encryption']) ? false : true;
272
+
273
+ $s3 = $this->getS3(
274
+ $config['accesskey'],
275
+ $config['secretkey'],
276
+ IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_useservercerts'),
277
+ IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_disableverify'),
278
+ IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_nossl'),
279
+ null,
280
+ $sse
281
+ );
282
+
283
+ if (is_wp_error($s3)) return $iwp_backup_core->log_wp_error($s3, false, true);
284
+
285
+ if (is_a($s3, 'IWP_MMB_S3_Compat') && !class_exists('XMLWriter')) {
286
+ $iwp_backup_core->log('The required XMLWriter PHP module is not installed');
287
+ $iwp_backup_core->log(sprintf(__('The required %s PHP module is not installed - ask your web hosting company to enable it', 'InfiniteWP'), 'XMLWriter'), 'error');
288
+ return false;
289
+ }
290
+
291
+ $bucket_name = untrailingslashit($config['path']);
292
+ if (!empty($config['as3_site_folder'])) {
293
+ $site_name = iwp_getSiteName();
294
+ $bucket_name.= '/'.untrailingslashit($site_name);
295
+ }
296
+ $bucket_path = "";
297
+ $orig_bucket_name = $bucket_name;
298
+
299
+ if (preg_match("#^([^/]+)/(.*)$#", $bucket_name, $bmatches)) {
300
+ $bucket_name = $bmatches[1];
301
+ $bucket_path = $bmatches[2]."/";
302
+ }
303
+
304
+ list($s3, $bucket_exists, $region) = $this->get_bucket_access($s3, $config, $bucket_name, $bucket_path);
305
+
306
+ // See if we can detect the region (which implies the bucket exists and is ours), or if not create it
307
+ if ($bucket_exists) {
308
+
309
+ $iwp_backup_dir = trailingslashit($iwp_backup_core->backups_dir_location());
310
+
311
+ foreach ($backup_array as $key => $file) {
312
+
313
+ // We upload in 5MB chunks to allow more efficient resuming and hence uploading of larger files
314
+ // N.B.: 5MB is Amazon's minimum. So don't go lower or you'll break it.
315
+ $fullpath = $iwp_backup_dir.$file;
316
+ $orig_file_size = filesize($fullpath);
317
+
318
+ if (!file_exists($fullpath)) {
319
+ $iwp_backup_core->log("File not found: $file: $whoweare: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile());
320
+ $iwp_backup_core->log("$file: ".sprintf(__('Error: %s', 'InfiniteWP'), __('File not found', 'InfiniteWP')), 'error');
321
+ continue;
322
+ }
323
+
324
+ if (isset($config['quota']) && method_exists($this, 's3_get_quota_info')) {
325
+ $quota_used = $this->s3_get_quota_info('numeric', $config['quota']);
326
+ if (false === $quota_used) {
327
+ $iwp_backup_core->log("Quota usage: count failed");
328
+ } else {
329
+ $this->quota_used = $quota_used;
330
+ if ($config['quota'] - $this->quota_used < $orig_file_size) {
331
+ if (method_exists($this, 's3_out_of_quota')) call_user_func(array($this, 's3_out_of_quota'), $config['quota'], $this->quota_used, $orig_file_size);
332
+ continue;
333
+ } else {
334
+ // We don't need to log this always - the s3_out_of_quota method will do its own logging
335
+ $iwp_backup_core->log("$whoweare: Quota is available: used=$quota_used (".round($quota_used/1048576, 1)." MB), total=".$config['quota']." (".round($config['quota']/1048576, 1)." MB), needed=$orig_file_size (".round($orig_file_size/1048576, 1)." MB)");
336
+ }
337
+ }
338
+ }
339
+
340
+ $chunks = floor($orig_file_size / 5242880);
341
+ // There will be a remnant unless the file size was exactly on a 5MB boundary
342
+ if ($orig_file_size % 5242880 > 0) $chunks++;
343
+ $hash = md5($file);
344
+
345
+ $iwp_backup_core->log("$whoweare upload ($region): $file (chunks: $chunks) -> $whoweare_key://$bucket_name/$bucket_path$file");
346
+
347
+ $filepath = $bucket_path.$file;
348
+
349
+ // This is extra code for the 1-chunk case, but less overhead (no bothering with job data)
350
+ if ($chunks < 2) {
351
+ $s3->setExceptions(true);
352
+ try {
353
+ if (!$s3->putObjectFile($fullpath, $bucket_name, $filepath, 'private', array(), array(), apply_filters('IWP_'.$whoweare_key.'_storageclass', 'STANDARD', $s3, $config))) {
354
+ $iwp_backup_core->log("$whoweare regular upload: failed ($fullpath)");
355
+ $iwp_backup_core->log("$file: ".sprintf(__('%s Error: Failed to upload', 'InfiniteWP'), $whoweare), 'error');
356
+ } else {
357
+ $this->quota_used += $orig_file_size;
358
+ if (method_exists($this, 's3_record_quota_info')) $this->s3_record_quota_info($this->quota_used, $config['quota']);
359
+ $extra_log = '';
360
+ if (method_exists($this, 's3_get_quota_info')) {
361
+ $extra_log = ', quota used now: '.round($this->quota_used / 1048576, 1).' MB';
362
+ }
363
+ $iwp_backup_core->log("$whoweare regular upload: success$extra_log");
364
+ $iwp_backup_core->uploaded_file($file);
365
+ }
366
+ } catch (Exception $e) {
367
+ $iwp_backup_core->log("$file: ".sprintf(__('%s Error: Failed to upload', 'InfiniteWP'), $whoweare).": ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile());
368
+ $iwp_backup_core->log("$file: ".sprintf(__('%s Error: Failed to upload', 'InfiniteWP'), $whoweare), 'error');
369
+ }
370
+ $s3->setExceptions(false);
371
+ } else {
372
+
373
+ // Retrieve the upload ID
374
+ $upload_id = $this->jobdata_get($hash.'_uid', null, "upd_${whoweare_keys}_${hash}_uid");
375
+ if (empty($upload_id)) {
376
+ $s3->setExceptions(true);
377
+ try {
378
+ $upload_id = $s3->initiateMultipartUpload($bucket_name, $filepath, 'private', array(), array(), apply_filters('IWP_'.$whoweare_key.'_storageclass', 'STANDARD', $s3, $config));
379
+ } catch (Exception $e) {
380
+ $iwp_backup_core->log("$whoweare error whilst trying initiateMultipartUpload: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
381
+ $upload_id = false;
382
+ }
383
+ $s3->setExceptions(false);
384
+
385
+ if (empty($upload_id)) {
386
+ $iwp_backup_core->log("$whoweare upload: failed: could not get uploadId for multipart upload ($filepath)");
387
+ $iwp_backup_core->log(sprintf(__("%s upload: getting uploadID for multipart upload failed - see log file for more details", 'InfiniteWP'), $whoweare), 'error');
388
+ continue;
389
+ } else {
390
+ $iwp_backup_core->log("$whoweare chunked upload: got multipart ID: $upload_id");
391
+ $this->jobdata_set($hash.'_uid', $upload_id, "upd_${whoweare_keys}_${hash}_uid");
392
+ }
393
+ } else {
394
+ $iwp_backup_core->log("$whoweare chunked upload: retrieved previously obtained multipart ID: $upload_id");
395
+ }
396
+
397
+ $successes = 0;
398
+ $etags = array();
399
+ for ($i = 1; $i <= $chunks; $i++) {
400
+ $etag = $this->jobdata_get($hash.'_etag_'.$i, null, "ud_${whoweare_keys}_${hash}_e$i");
401
+ if (strlen($etag) > 0) {
402
+ $iwp_backup_core->log("$whoweare chunk $i: was already completed (etag: $etag)");
403
+ $successes++;
404
+ array_push($etags, $etag);
405
+ } else {
406
+ // Sanity check: we've seen a case where an overlap was truncating the file from underneath us
407
+ if (filesize($fullpath) < $orig_file_size) {
408
+ $iwp_backup_core->log("$whoweare error: $key: chunk $i: file was truncated underneath us (orig_size=$orig_file_size, now_size=".filesize($fullpath).")");
409
+ $iwp_backup_core->log(sprintf(__('%s error: file %s was shortened unexpectedly', 'InfiniteWP'), $whoweare, $fullpath), 'error');
410
+ }
411
+ $etag = $s3->uploadPart($bucket_name, $filepath, $upload_id, $fullpath, $i);
412
+ if (false !== $etag && is_string($etag)) {
413
+ $iwp_backup_core->record_uploaded_chunk(round(100*$i/$chunks, 1), "$i, $etag", $fullpath);
414
+ array_push($etags, $etag);
415
+ $this->jobdata_set($hash.'_etag_'.$i, $etag, "ud_${whoweare_keys}_${hash}_e$i");
416
+ $successes++;
417
+ } else {
418
+ $iwp_backup_core->log("$whoweare chunk $i: upload failed");
419
+ $iwp_backup_core->log(sprintf(__("%s chunk %s: upload failed", 'InfiniteWP'), $whoweare, $i), 'error');
420
+ }
421
+ }
422
+ }
423
+ if ($successes >= $chunks) {
424
+ $iwp_backup_core->log("$whoweare upload: all chunks uploaded; will now instruct $whoweare to re-assemble");
425
+
426
+ $s3->setExceptions(true);
427
+ try {
428
+ if ($s3->completeMultipartUpload($bucket_name, $filepath, $upload_id, $etags)) {
429
+ $iwp_backup_core->log("$whoweare upload ($key): re-assembly succeeded");
430
+ $iwp_backup_core->uploaded_file($file);
431
+ $this->quota_used += $orig_file_size;
432
+ if (method_exists($this, 's3_record_quota_info')) $this->s3_record_quota_info($this->quota_used, $config['quota']);
433
+ } else {
434
+ $iwp_backup_core->log("$whoweare upload ($key): re-assembly failed ($file)");
435
+ $iwp_backup_core->log(sprintf(__('%s upload (%s): re-assembly failed (see log for more details)', 'InfiniteWP'), $whoweare, $key), 'error');
436
+ }
437
+ } catch (Exception $e) {
438
+ $iwp_backup_core->log("$whoweare re-assembly error ($key): ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
439
+ $iwp_backup_core->log($e->getMessage().": ".sprintf(__('%s re-assembly error (%s): (see log file for more)', 'InfiniteWP'), $whoweare, $e->getMessage()), 'error');
440
+ }
441
+ // Remember to unset, as the deletion code later reuses the object
442
+ $s3->setExceptions(false);
443
+ } else {
444
+ $iwp_backup_core->log("$whoweare upload: upload was not completely successful on this run");
445
+ }
446
+ }
447
+ }
448
+
449
+ // Allows counting of the final quota accurately
450
+ if (method_exists($this, 's3_prune_retained_backups_finished')) {
451
+ add_action('IWP_prune_retained_backups_finished', array($this, 's3_prune_retained_backups_finished'));
452
+ }
453
+
454
+ return array('s3_object' => $s3, 's3_orig_bucket_name' => $orig_bucket_name);
455
+ } else {
456
+
457
+ $extra_text = empty($this->s3_exception) ? '' : ' '.$this->s3_exception->getMessage().' (line: '.$this->s3_exception->getLine().', file: '.$this->s3_exception->getFile().')';
458
+ $extra_text_short = empty($this->s3_exception) ? '' : ' '.$this->s3_exception->getMessage();
459
+
460
+ $iwp_backup_core->log("$whoweare Error: Failed to access bucket $bucket_name.".$extra_text);
461
+ $iwp_backup_core->log(sprintf(__('%s Error: Failed to access bucket %s. Check your permissions and credentials.', 'InfiniteWP'), $whoweare, $bucket_name).$extra_text_short, 'error');
462
+ }
463
+ }
464
+
465
+ public function listfiles($match = 'backup_') {
466
+ $config = $this->get_config();
467
+ return $this->listfiles_with_path($config['path'], $match);
468
+ }
469
+
470
+ protected function possibly_wait_for_bucket_or_user($config, $s3) {
471
+ if (!empty($config['is_new_bucket'])) {
472
+ if (method_exists($s3, 'waitForBucket')) {
473
+ $s3->setExceptions(true);
474
+ try {
475
+ $s3->waitForBucket($bucket_name);
476
+ } catch (Exception $e) {
477
+ // This seems to often happen - we get a 403 on a newly created user/bucket pair, even though the bucket was already waited for by the creator
478
+ // We could just sleep() - a sleep(5) seems to do it. However, given that it's a new bucket, that's unnecessary.
479
+ $s3->setExceptions(false);
480
+ return array();
481
+ }
482
+ $s3->setExceptions(false);
483
+ } else {
484
+ sleep(4);
485
+ }
486
+ } elseif (!empty($config['is_new_user'])) {
487
+ // A crude waiter, because the AWS toolkit does not have one for IAM propagation - basically, loop around a few times whilst the access attempt still fails
488
+ $attempt_flag = 0;
489
+ while ($attempt_flag < 5) {
490
+
491
+ $attempt_flag++;
492
+ if (@$s3->getBucketLocation($bucket_name)) {
493
+ $attempt_flag = 100;
494
+ } else {
495
+
496
+ sleep($attempt_flag*1.5 + 1);
497
+
498
+ // Get the bucket object again... because, for some reason, the AWS PHP SDK (at least on the current version we're using, March 2016) calculates an incorrect signature on subsequent attempts
499
+ $this->s3_object = null;
500
+ $s3 = $this->getS3(
501
+ $config['accesskey'],
502
+ $config['secretkey'],
503
+ IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_useservercerts'),
504
+ IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_disableverify'),
505
+ IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_nossl'),
506
+ null,
507
+ $sse
508
+ );
509
+
510
+ if (is_wp_error($s3)) return $s3;
511
+ if (!is_a($s3, 'IWP_MMB_S3') && !is_a($s3, 'IWP_MMB_S3_Compat')) return new WP_Error('no_s3object', 'Failed to gain access to '.$config['whoweare']);
512
+
513
+ }
514
+ }
515
+ }
516
+
517
+ return $s3;
518
+ }
519
+
520
+ /**
521
+ * The purpose of splitting this into a separate method, is to also allow listing with a different path
522
+ *
523
+ * @param string $path Path to check
524
+ * @param string $match THe match for idetifying the bucket name
525
+ * @param boolean $include_subfolders Check if list file need to include sub folders
526
+ * @return array
527
+ */
528
+ public function listfiles_with_path($path, $match = 'backup_', $include_subfolders = false) {
529
+
530
+ $bucket_name = untrailingslashit($path);
531
+ $bucket_path = '';
532
+ $config = $this->get_config();
533
+ if (!empty($config['as3_site_folder'])) {
534
+ $site_name = iwp_getSiteName();
535
+ $bucket_name.= '/'.untrailingslashit($site_name);
536
+ }
537
+ if (preg_match("#^([^/]+)/(.*)$#", $bucket_name, $bmatches)) {
538
+ $bucket_name = $bmatches[1];
539
+ $bucket_path = trailingslashit($bmatches[2]);
540
+ }
541
+
542
+
543
+ global $iwp_backup_core;
544
+
545
+ $whoweare = $config['whoweare'];
546
+ $whoweare_key = $config['key'];
547
+ $sse = empty($config['server_side_encryption']) ? false : true;
548
+
549
+ $s3 = $this->getS3(
550
+ $config['accesskey'],
551
+ $config['secretkey'],
552
+ IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_useservercerts'),
553
+ IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_disableverify'),
554
+ IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_nossl'),
555
+ null,
556
+ $sse
557
+ );
558
+
559
+ if (is_wp_error($s3)) return $s3;
560
+ if (!is_a($s3, 'IWP_MMB_S3') && !is_a($s3, 'IWP_MMB_S3_Compat')) return new WP_Error('no_s3object', 'Failed to gain access to '.$config['whoweare']);
561
+
562
+ $s3 = $this->possibly_wait_for_bucket_or_user($config, $s3);
563
+ if (!is_a($s3, 'IWP_MMB_S3') && !is_a($s3, 'IWP_MMB_S3_Compat')) return $s3;
564
+
565
+ list($s3, $bucket_exists, $region) = $this->get_bucket_access($s3, $config, $bucket_name, $bucket_path);
566
+
567
+ $bucket = $s3->getBucket($bucket_name, $bucket_path.$match);
568
+
569
+ if (!is_array($bucket)) return array();
570
+
571
+ $results = array();
572
+
573
+ foreach ($bucket as $key => $object) {
574
+ if (!is_array($object) || empty($object['name'])) continue;
575
+ if (isset($object['size']) && 0 == $object['size']) continue;
576
+
577
+ if ($bucket_path) {
578
+ if (0 !== strpos($object['name'], $bucket_path)) continue;
579
+ $object['name'] = substr($object['name'], strlen($bucket_path));
580
+ } else {
581
+ if (!$include_subfolders && false !== strpos($object['name'], '/')) continue;
582
+ }
583
+
584
+ $result = array('name' => $object['name']);
585
+ if (isset($object['size'])) $result['size'] = $object['size'];
586
+ unset($bucket[$key]);
587
+ $results[] = $result;
588
+ }
589
+
590
+ return $results;
591
+
592
+ }
593
+
594
+ public function delete($files, $s3arr = false, $sizeinfo = array()) {
595
+
596
+ global $iwp_backup_core;
597
+ if (is_string($files)) $files = array($files);
598
+
599
+ $config = $this->get_config();
600
+ $sse = (empty($config['server_side_encryption'])) ? false : true;
601
+ $whoweare = $config['whoweare'];
602
+
603
+ if ($s3arr) {
604
+ $s3 = $s3arr['s3_object'];
605
+ $orig_bucket_name = $s3arr['s3_orig_bucket_name'];
606
+ } else {
607
+
608
+ $s3 = $this->getS3(
609
+ $config['accesskey'],
610
+ $config['secretkey'],
611
+ IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_useservercerts'),
612
+ IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_disableverify'),
613
+ IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_nossl'),
614
+ null,
615
+ $sse
616
+ );
617
+
618
+ if (is_wp_error($s3)) return $iwp_backup_core->log_wp_error($s3, false, false);
619
+
620
+ $bucket_name = untrailingslashit($config['path']);
621
+ if (!empty($config['as3_site_folder'])) {
622
+ $site_name = iwp_getSiteName();
623
+ $bucket_name.= '/'.untrailingslashit($site_name);
624
+ }
625
+ $orig_bucket_name = $bucket_name;
626
+ if (preg_match("#^([^/]+)/(.*)$#", $bucket_name, $bmatches)) {
627
+ $bucket_name = $bmatches[1];
628
+ $bucket_path = $bmatches[2]."/";
629
+ } else {
630
+ $bucket_path = '';
631
+ }
632
+
633
+ list($s3, $bucket_exists, $region) = $this->get_bucket_access($s3, $config, $bucket_name, $bucket_path);
634
+
635
+ if (!$bucket_exists) {
636
+ $iwp_backup_core->log("$whoweare Error: Failed to access bucket $bucket_name. Check your permissions and credentials.");
637
+ $iwp_backup_core->log(sprintf(__('%s Error: Failed to access bucket %s. Check your permissions and credentials.', 'iwp_backup_core'), $whoweare, $bucket_name), 'error');
638
+ return false;
639
+ }
640
+ }
641
+
642
+ $ret = true;
643
+
644
+ foreach ($files as $i => $file) {
645
+
646
+ if (preg_match("#^([^/]+)/(.*)$#", $orig_bucket_name, $bmatches)) {
647
+ $s3_bucket=$bmatches[1];
648
+ $s3_uri = $bmatches[2]."/".$file;
649
+ } else {
650
+ $s3_bucket = $orig_bucket_name;
651
+ $s3_uri = $file;
652
+ }
653
+ $iwp_backup_core->log("$whoweare: Delete remote: bucket=$s3_bucket, URI=$s3_uri");
654
+
655
+ $s3->setExceptions(true);
656
+ try {
657
+ if (!$s3->deleteObject($s3_bucket, $s3_uri)) {
658
+ $iwp_backup_core->log("$whoweare: Delete failed");
659
+ } elseif (null !== $this->quota_used && !empty($sizeinfo[$i]) && isset($config['quota']) && method_exists($this, 's3_record_quota_info')) {
660
+ $this->quota_used -= $sizeinfo[$i];
661
+ $this->s3_record_quota_info($this->quota_used, $config['quota']);
662
+ }
663
+ } catch (Exception $e) {
664
+ $iwp_backup_core->log("$whoweare delete failed: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
665
+ $s3->setExceptions(false);
666
+ $ret = false;
667
+ }
668
+ $s3->setExceptions(false);
669
+
670
+ }
671
+
672
+ return $ret;
673
+
674
+ }
675
+
676
+ public function download($file) {
677
+
678
+ global $iwp_backup_core;
679
+
680
+ $config = $this->get_config();
681
+ $whoweare = $config['whoweare'];
682
+ $sse = empty($config['server_side_encryption']) ? false : true;
683
+
684
+ $s3 = $this->getS3(
685
+ $config['accesskey'],
686
+ $config['secretkey'],
687
+ IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_useservercerts'),
688
+ IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_disableverify'),
689
+ IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_nossl'),
690
+ null,
691
+ $sse
692
+ );
693
+ if (is_wp_error($s3)) return $iwp_backup_core->log_wp_error($s3, false, true);
694
+
695
+ $bucket_name = untrailingslashit($config['path']);
696
+ $bucket_path = "";
697
+ if (!empty($config['as3_site_folder'])) {
698
+ $site_name = iwp_getSiteName();
699
+ $bucket_name.= '/'.untrailingslashit($site_name);
700
+ }
701
+ if (preg_match("#^([^/]+)/(.*)$#", $bucket_name, $bmatches)) {
702
+ $bucket_name = $bmatches[1];
703
+ $bucket_path = $bmatches[2]."/";
704
+ }
705
+
706
+
707
+ list($s3, $bucket_exists, $region) = $this->get_bucket_access($s3, $config, $bucket_name, $bucket_path);
708
+
709
+ if ($bucket_exists) {
710
+
711
+ $fullpath = $iwp_backup_core->backups_dir_location().'/'.$file;
712
+
713
+ $file_info = $this->listfiles($file);
714
+
715
+ if (is_array($file_info)) {
716
+ foreach ($file_info as $finfo) {
717
+ if ($finfo['name'] == $file) {
718
+ $file_size = $finfo['size'];
719
+ break;
720
+ }
721
+ }
722
+ }
723
+
724
+ if (!isset($file_size)) {
725
+ $iwp_backup_core->log("$whoweare Error: Failed to download $file. Check your permissions and credentials. Retrieved data: ".serialize($file_info));
726
+ $iwp_backup_core->log(sprintf(__('%s Error: Failed to download %s. Check your permissions and credentials.', 'iwp_backup_core'), $whoweare, $file), 'error');
727
+ return false;
728
+ }
729
+
730
+ return $iwp_backup_core->chunked_download($file, $this, $file_size, true, $s3, $this->download_chunk_size);
731
+
732
+
733
+ } else {
734
+ $iwp_backup_core->log("$whoweare Error: Failed to access bucket $bucket_name. Check your permissions and credentials.");
735
+ $iwp_backup_core->log(sprintf(__('%s Error: Failed to access bucket %s. Check your permissions and credentials.', 'InfiniteWP'), $whoweare, $bucket_name), 'error');
736
+ return false;
737
+ }
738
+ return true;
739
+
740
+ }
741
+
742
+ public function chunked_download($file, $headers, $s3, $fh) {
743
+
744
+ global $iwp_backup_core;
745
+
746
+ $resume = false;
747
+ $config = $this->get_config();
748
+ $whoweare = $config['whoweare'];
749
+
750
+ $bucket_name = untrailingslashit($config['path']);
751
+ if (!empty($config['as3_site_folder'])) {
752
+ $site_name = iwp_getSiteName();
753
+ $bucket_name.= '/'.untrailingslashit($site_name);
754
+ }
755
+ $bucket_path = "";
756
+
757
+ if (preg_match("#^([^/]+)/(.*)$#", $bucket_name, $bmatches)) {
758
+ $bucket_name = $bmatches[1];
759
+ $bucket_path = $bmatches[2]."/";
760
+ }
761
+
762
+ if (is_array($headers) && !empty($headers['Range']) && preg_match('/bytes=(\d+)-(\d+)$/', $headers['Range'], $matches)) {
763
+ $resume = $headers['Range'];
764
+ }
765
+
766
+ if (!$s3->getObject($bucket_name, $bucket_path.$file, $fh, $resume)) {
767
+ $iwp_backup_core->log("$whoweare Error: Failed to download $file. Check your permissions and credentials.");
768
+ $iwp_backup_core->log(sprintf(__('%s Error: Failed to download %s. Check your permissions and credentials.', 'InfiniteWP'), $whoweare, $file), 'error');
769
+ return false;
770
+ }
771
+
772
+ // This instructs the caller to look at the file pointer's position (i.e. ftell($fh)) to work out how many bytes were written.
773
+ return true;
774
+
775
+ }
776
+
777
+ public function config_print() {
778
+
779
+ // White: https://d36cz9buwru1tt.cloudfront.net/Powered-by-Amazon-Web-Services.jpg
780
+ $this->config_print_engine('s3', 'S3', 'Amazon S3', 'AWS', 'https://aws.amazon.com/console/', '<img src="//awsmedia.s3.amazonaws.com/AWS_logo_poweredby_black_127px.png" alt="Amazon Web Services">');
781
+
782
+ }
783
+
784
+ public function config_print_engine($key, $whoweare_short, $whoweare_long, $console_descrip, $console_url, $img_html = '', $include_endpoint_chooser = false) {
785
+
786
+ $opts = $this->get_config();
787
+
788
+ $use_s3_class = $this->indicate_s3_class();
789
+
790
+ if (!empty($include_endpoint_chooser)) {
791
+
792
+ if (is_array($include_endpoint_chooser)) {
793
+
794
+ $selected_endpoint = (!empty($opts['endpoint']) && in_array($opts['endpoint'], $include_endpoint_chooser)) ? $opts['endpoint'] : $include_endpoint_chooser[0];
795
+ }
796
+ }
797
+
798
+ }
799
+ /**
800
+ * This is not pretty, but is the simplest way to accomplish the task within the pre-existing structure (no need to re-invent the wheel of code with corner-cases debugged over years)
801
+ *
802
+ * @param object $s3 S3 Name
803
+ * @param string $bucket S3 Bucket
804
+ * @return boolean
805
+ */
806
+ public function use_dns_bucket_name($s3, $bucket) {
807
+ return is_a($s3, 'IWP_MMB_S3_Compat') ? true : $s3->useDNSBucketName(true, $bucket);
808
+ }
809
+
810
+ /**
811
+ * This method contains some repeated code. After getting an S3 object, it's time to see if we can access that bucket - either immediately, or via creating it, etc.
812
+ *
813
+ * @param object $s3 S3 name
814
+ * @param array $config array of config details
815
+ * @param string $bucket S3 Bucket
816
+ * @param string $path S3 Path
817
+ * @param boolean|string $endpoint S3 end point
818
+ * @return array
819
+ */
820
+ private function get_bucket_access($s3, $config, $bucket, $path, $endpoint = false) {
821
+
822
+ $bucket_exists = false;
823
+
824
+ if ('s3' == $config['key']) {
825
+
826
+ $s3->setExceptions(true);
827
+
828
+ if ('dreamobjects' == $config['key']) $this->set_region($s3, $endpoint);
829
+
830
+ try {
831
+ $region = @$s3->getBucketLocation($bucket);
832
+ // We want to distinguish between an empty region (null), and an exception or missing bucket (false)
833
+ if (empty($region) && false !== $region) $region = null;
834
+ } catch (Exception $e) {
835
+ $region = false;
836
+ }
837
+ $s3->setExceptions(false);
838
+ } else {
839
+ $region = 'n/a';
840
+ }
841
+
842
+ // See if we can detect the region (which implies the bucket exists and is ours), or if not create it
843
+ if (false === $region) {
844
+
845
+ $s3->setExceptions(true);
846
+ try {
847
+ if (@$s3->putBucket($bucket, 'private')) {
848
+ $bucket_exists = true;
849
+ }
850
+
851
+ } catch (Exception $e) {
852
+ $this->s3_exception = $e;
853
+ try {
854
+ if ('s3' == $config['key'] && $this->use_dns_bucket_name($s3, $bucket) && false !== @$s3->getBucket($bucket, $path, null, 1)) {
855
+ $bucket_exists = true;
856
+ }
857
+ } catch (Exception $e) {
858
+
859
+ // We don't put this in a separate catch block, since we need to be compatible with PHP 5.2 still
860
+ if (is_a($s3, 'IWP_MMB_S3_Compat') && is_a($e, 'Aws\S3\Exception\S3Exception')) {
861
+ $xml = $e->getResponse()->xml();
862
+
863
+ if (!empty($xml->Code) && 'AuthorizationHeaderMalformed' == $xml->Code && !empty($xml->Region)) {
864
+
865
+ $this->set_region($s3, $xml->Region);
866
+ $s3->setExceptions(false);
867
+
868
+ if (false !== @$s3->getBucket($bucket, $path, null, 1)) {
869
+ $bucket_exists = true;
870
+ }
871
+
872
+ } else {
873
+ $this->s3_exception = $e;
874
+ }
875
+ } else {
876
+ $this->s3_exception = $e;
877
+ }
878
+ }
879
+
880
+ }
881
+ $s3->setExceptions(false);
882
+
883
+ } else {
884
+ $bucket_exists = true;
885
+ }
886
+
887
+ if ($bucket_exists) {
888
+ if ('s3' != $config['key']) {
889
+ $this->set_region($s3, $endpoint, $bucket);
890
+ } elseif (!empty($region)) {
891
+ $this->set_region($s3, $region, $bucket);
892
+ }
893
+ }
894
+
895
+ return array($s3, $bucket_exists, $region);
896
+
897
+ }
898
+
899
+ }
backup/sftp.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if ( ! defined('ABSPATH') )
4
+ die();
5
+
6
+ if (!class_exists('IWP_MMB_RemoteStorage_sftp')) require_once($GLOBALS['iwp_mmb_plugin_dir'].'/lib/sftp.php');
7
+
8
+ if (class_exists('IWP_MMB_RemoteStorage_sftp')) {
9
+
10
+ // Migrate options to standard-style - April 2017. This then enables them to get picked up by the multi-options settings translation code
11
+ if (!is_array(IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_sftp')) && '' != IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_sftp_settings', '')) {
12
+ $opts = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_sftp_settings');
13
+ IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_sftp', $opts);
14
+ IWP_MMB_Backup_Options::delete_iwp_backup_option('IWP_sftp_settings');
15
+ }
16
+
17
+ class IWP_MMB_UploadModule_sftp extends IWP_MMB_RemoteStorage_sftp {
18
+ public function __construct() {
19
+ parent::__construct('sftp', 'SFTP/SCP');
20
+ }
21
+ }
22
+
23
+ }
core.class.php CHANGED
@@ -34,6 +34,7 @@ class IWP_MMB_Core extends IWP_MMB_Helper
34
  var $links_instance;
35
  var $user_instance;
36
  var $backup_instance;
 
37
  var $wordfence_instance;
38
  var $sucuri_instance;
39
  var $installer_instance;
@@ -43,8 +44,10 @@ class IWP_MMB_Core extends IWP_MMB_Helper
43
  var $ithemessec_instance;
44
  var $backup_repository_instance;
45
  var $optimize_instance;
 
46
 
47
  private $action_call;
 
48
  private $action_params;
49
  private $iwp_mmb_pre_init_actions;
50
  private $iwp_mmb_pre_init_filters;
@@ -146,10 +149,14 @@ class IWP_MMB_Core extends IWP_MMB_Helper
146
  'email_backup' => 'iwp_mmb_email_backup',
147
  'check_backup_compat' => 'iwp_mmb_check_backup_compat',
148
  'scheduled_backup' => 'iwp_mmb_scheduled_backup',
 
149
  'run_task' => 'iwp_mmb_run_task_now',
 
150
  'delete_schedule_task' => 'iwp_mmb_delete_task_now',
151
  'execute_php_code' => 'iwp_mmb_execute_php_code',
152
  'delete_backup' => 'iwp_mmb_delete_backup',
 
 
153
  'remote_backup_now' => 'iwp_mmb_remote_backup_now',
154
  'set_notifications' => 'iwp_mmb_set_notifications',
155
  'clean_orphan_backups' => 'iwp_mmb_clean_orphan_backups',
@@ -166,9 +173,11 @@ class IWP_MMB_Core extends IWP_MMB_Helper
166
  'maintenance' => 'iwp_mmb_maintenance_mode',
167
 
168
  'wp_optimize' => 'iwp_mmb_wp_optimize',
 
169
 
170
  'backup_repository' => 'iwp_mmb_backup_repository',
171
  'trigger_backup_multi' => 'iwp_mmb_trigger_check',
 
172
  'get_all_links' => 'iwp_mmb_get_all_links',
173
  'update_broken_link' => 'iwp_mmb_update_broken_link',
174
  'unlink_broken_link' => 'iwp_mmb_unlink_broken_link',
@@ -190,39 +199,59 @@ class IWP_MMB_Core extends IWP_MMB_Helper
190
  'save_seo_info' => 'iwp_mmb_yoast_save_seo_info',
191
  'fetch_activities_log' => 'iwp_mmb_fetch_activities_log',
192
  'sucuri_scan' => 'iwp_mmb_sucuri_scan',
193
- 'sucuri_change_alert' => 'iwp_mmb_sucuri_change_alert'
 
194
  );
195
 
196
  add_action('rightnow_end', array( &$this, 'add_right_now_info' ));
197
  add_action('admin_menu', array($this,'iwp_admin_menu_actions'), 999, 1);
198
  add_action('admin_init', array(&$this,'admin_actions'));
199
- add_action('init', array( &$this, 'iwp_mmb_remote_action'), 9999);
200
- add_action('setup_theme', 'iwp_mmb_parse_request');
 
 
201
  add_action('set_auth_cookie', array( &$this, 'iwp_mmb_set_auth_cookie'));
202
  add_action('set_logged_in_cookie', array( &$this, 'iwp_mmb_set_logged_in_cookie'));
203
 
204
  }
205
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  function iwp_mmb_remote_action(){
207
- if($this->action_call != null){
208
- $params = isset($this->action_params) && $this->action_params != null ? $this->action_params : array();
209
- call_user_func($this->action_call, $params);
210
- }
211
- }
212
-
213
- function register_action_params( $action = false, $params = array() ){
214
-
215
- if(isset($this->iwp_mmb_pre_init_actions[$action]) && function_exists($this->iwp_mmb_pre_init_actions[$action])){
216
- call_user_func($this->iwp_mmb_pre_init_actions[$action], $params);
217
- }
218
-
219
- if(isset($this->iwp_mmb_init_actions[$action]) && function_exists($this->iwp_mmb_init_actions[$action])){
220
- $this->action_call = $this->iwp_mmb_init_actions[$action];
221
- $this->action_params = $params;
222
-
223
  if( isset($this->iwp_mmb_pre_init_filters[$action]) && !empty($this->iwp_mmb_pre_init_filters[$action])){
224
  global $iwp_mmb_filters;
225
-
226
  foreach($this->iwp_mmb_pre_init_filters[$action] as $_name => $_functions){
227
  if(!empty($_functions)){
228
  $data = array();
@@ -240,6 +269,32 @@ class IWP_MMB_Core extends IWP_MMB_Helper
240
 
241
  }
242
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
  return true;
244
  }
245
  return false;
@@ -372,7 +427,16 @@ class IWP_MMB_Core extends IWP_MMB_Helper
372
  return $this->optimize_instance;
373
  }
374
 
375
-
 
 
 
 
 
 
 
 
 
376
  /**
377
  * Gets an instance of the WP_BrokenLinks class
378
  *
@@ -557,6 +621,26 @@ class IWP_MMB_Core extends IWP_MMB_Helper
557
  * Gets an instance of stats class
558
  *
559
  */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
560
  function get_backup_instance($mechanism='')
561
  {
562
  require_once($GLOBALS['iwp_mmb_plugin_dir']."/backup.class.singlecall.php");
@@ -785,7 +869,11 @@ class IWP_MMB_Core extends IWP_MMB_Helper
785
  @include_once ABSPATH . 'wp-admin/includes/template.php';
786
  @include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
787
  @include_once ABSPATH . 'wp-admin/includes/screen.php';
788
-
 
 
 
 
789
  if (!$this->is_server_writable()) {
790
  return array(
791
  'error' => 'Failed, please add FTP details', 'error_code' => 'automatic_upgrade_failed_add_ftp_details'
@@ -1036,6 +1124,10 @@ class IWP_MMB_Core extends IWP_MMB_Helper
1036
 
1037
  return $all_plugins;
1038
  }
 
 
 
 
1039
 
1040
 
1041
  }
34
  var $links_instance;
35
  var $user_instance;
36
  var $backup_instance;
37
+ var $backup_new_instance;
38
  var $wordfence_instance;
39
  var $sucuri_instance;
40
  var $installer_instance;
44
  var $ithemessec_instance;
45
  var $backup_repository_instance;
46
  var $optimize_instance;
47
+ var $wp_purge_cache_instance;
48
 
49
  private $action_call;
50
+ public $request_params;
51
  private $action_params;
52
  private $iwp_mmb_pre_init_actions;
53
  private $iwp_mmb_pre_init_filters;
149
  'email_backup' => 'iwp_mmb_email_backup',
150
  'check_backup_compat' => 'iwp_mmb_check_backup_compat',
151
  'scheduled_backup' => 'iwp_mmb_scheduled_backup',
152
+ 'new_scheduled_backup' => 'iwp_mmb_new_scheduled_backup',
153
  'run_task' => 'iwp_mmb_run_task_now',
154
+ 'new_run_task' => 'iwp_mmb_new_run_task_now',
155
  'delete_schedule_task' => 'iwp_mmb_delete_task_now',
156
  'execute_php_code' => 'iwp_mmb_execute_php_code',
157
  'delete_backup' => 'iwp_mmb_delete_backup',
158
+ 'delete_backup_new' => 'iwp_mmb_delete_backup_new',
159
+ 'kill_new_backup' => 'iwp_mmb_kill_new_backup',
160
  'remote_backup_now' => 'iwp_mmb_remote_backup_now',
161
  'set_notifications' => 'iwp_mmb_set_notifications',
162
  'clean_orphan_backups' => 'iwp_mmb_clean_orphan_backups',
173
  'maintenance' => 'iwp_mmb_maintenance_mode',
174
 
175
  'wp_optimize' => 'iwp_mmb_wp_optimize',
176
+ 'wp_purge_cache' => 'iwp_mmb_wp_purge_cache',
177
 
178
  'backup_repository' => 'iwp_mmb_backup_repository',
179
  'trigger_backup_multi' => 'iwp_mmb_trigger_check',
180
+ 'trigger_backup_multi_new' => 'iwp_mmb_trigger_check_new',
181
  'get_all_links' => 'iwp_mmb_get_all_links',
182
  'update_broken_link' => 'iwp_mmb_update_broken_link',
183
  'unlink_broken_link' => 'iwp_mmb_unlink_broken_link',
199
  'save_seo_info' => 'iwp_mmb_yoast_save_seo_info',
200
  'fetch_activities_log' => 'iwp_mmb_fetch_activities_log',
201
  'sucuri_scan' => 'iwp_mmb_sucuri_scan',
202
+ 'sucuri_change_alert' => 'iwp_mmb_sucuri_change_alert',
203
+ 'backup_downlaod' => 'iwp_mmb_backup_downlaod'
204
  );
205
 
206
  add_action('rightnow_end', array( &$this, 'add_right_now_info' ));
207
  add_action('admin_menu', array($this,'iwp_admin_menu_actions'), 999, 1);
208
  add_action('admin_init', array(&$this,'admin_actions'));
209
+ add_filter('deprecated_function_trigger_error', '__return_false');
210
+ // add_action('wp_loaded', array( &$this, 'iwp_mmb_remote_action'), 2147483650);
211
+ add_action('plugins_loaded', 'iwp_mmb_add_readd_request');
212
+ add_action('setup_theme', 'iwp_mmb_set_request');
213
  add_action('set_auth_cookie', array( &$this, 'iwp_mmb_set_auth_cookie'));
214
  add_action('set_logged_in_cookie', array( &$this, 'iwp_mmb_set_logged_in_cookie'));
215
 
216
  }
217
 
218
+ function admin_wp_loaded_iwp(){
219
+ if (!defined('WP_ADMIN')) {
220
+ define('WP_ADMIN', true);
221
+ }
222
+ if (is_multisite() && !defined('WP_NETWORK_ADMIN')) {
223
+ define('WP_NETWORK_ADMIN', true);
224
+ }
225
+ define('WP_BLOG_ADMIN', true);
226
+ require_once ABSPATH.'wp-admin/includes/admin.php';
227
+ // define('DOING_AJAX', true);
228
+ do_action('admin_init');
229
+ if (function_exists('wp_clean_update_cache')) {
230
+ /** @handled function */
231
+ wp_clean_update_cache();
232
+ }
233
+
234
+ /** @handled function */
235
+ wp_update_plugins();
236
+
237
+ /** @handled function */
238
+ set_current_screen();
239
+ do_action('load-update-core.php');
240
+
241
+ /** @handled function */
242
+ wp_version_check();
243
+
244
+ /** @handled function */
245
+ wp_version_check(array(), true);
246
+ }
247
+
248
  function iwp_mmb_remote_action(){
249
+ global $iwp_mmb_core;
250
+ if (!empty($iwp_mmb_core->request_params)) {
251
+ $params = $iwp_mmb_core->request_params;
252
+ $action = $iwp_mmb_core->request_params['iwp_action'];
 
 
 
 
 
 
 
 
 
 
 
 
253
  if( isset($this->iwp_mmb_pre_init_filters[$action]) && !empty($this->iwp_mmb_pre_init_filters[$action])){
254
  global $iwp_mmb_filters;
 
255
  foreach($this->iwp_mmb_pre_init_filters[$action] as $_name => $_functions){
256
  if(!empty($_functions)){
257
  $data = array();
269
 
270
  }
271
  }
272
+ }
273
+ if($this->action_call != null){
274
+ $params = isset($this->action_params) && $this->action_params != null ? $this->action_params : array();
275
+ call_user_func($this->action_call, $params);
276
+ }
277
+ }
278
+
279
+ function register_action_params( $action = false, $params = array() ){
280
+ if ($action == 'get_stats' || $action == 'do_upgrade') {
281
+ add_action('wp_loaded', array( &$this, 'iwp_mmb_remote_action'), 2147483650);
282
+ add_action('wp_loaded', array( &$this, 'admin_wp_loaded_iwp'), 2147483649);
283
+ }elseif ($action == 'install_addon') {
284
+ add_action('wp_loaded', array( &$this, 'iwp_mmb_remote_action'));
285
+ }elseif ($action == 'new_run_task' || $action == 'new_scheduled_backup') {
286
+ add_action('after_setup_theme', array( &$this, 'iwp_mmb_remote_action'), 9999);
287
+ }else{
288
+ add_action('init', array( &$this, 'iwp_mmb_remote_action'), 9999);
289
+ }
290
+
291
+ if(isset($this->iwp_mmb_pre_init_actions[$action]) && function_exists($this->iwp_mmb_pre_init_actions[$action])){
292
+ call_user_func($this->iwp_mmb_pre_init_actions[$action], $params);
293
+ }
294
+
295
+ if(isset($this->iwp_mmb_init_actions[$action]) && function_exists($this->iwp_mmb_init_actions[$action])){
296
+ $this->action_call = $this->iwp_mmb_init_actions[$action];
297
+ $this->action_params = $params;
298
  return true;
299
  }
300
  return false;
427
  return $this->optimize_instance;
428
  }
429
 
430
+ function wp_purge_cache_instance()
431
+ {
432
+ global $iwp_mmb_plugin_dir;
433
+ require_once("$iwp_mmb_plugin_dir/addons/wp_optimize/purge-plugins-cache-class.php");
434
+ if (!isset($this->wp_purge_cache_instance)) {
435
+ $this->wp_purge_cache_instance = new IWP_MMB_PURGE_CACHE();
436
+ }
437
+
438
+ return $this->wp_purge_cache_instance;
439
+ }
440
  /**
441
  * Gets an instance of the WP_BrokenLinks class
442
  *
621
  * Gets an instance of stats class
622
  *
623
  */
624
+ function get_new_backup_instance($params = array())
625
+ {
626
+ if ((isset($iwp_backup_core) && is_object($iwp_backup_core) && is_a($iwp_backup_core, 'IWP_MMB_Backup_Core'))) return $iwp_backup_core;
627
+
628
+ require_once($GLOBALS['iwp_mmb_plugin_dir'].'/backup/backup.core.class.php');
629
+ iwp_mmb_define_constant();
630
+ $iwp_backup_core = new IWP_MMB_Backup_Core();
631
+ $GLOBALS['iwp_backup_core'] = $iwp_backup_core;
632
+ $this->backup_new_instance = $iwp_backup_core;
633
+ if (!$iwp_backup_core->memory_check(192)) {
634
+ if (!$iwp_backup_core->memory_check($iwp_backup_core->memory_check_current(WP_MAX_MEMORY_LIMIT))) {
635
+ $new = absint($iwp_backup_core->memory_check_current(WP_MAX_MEMORY_LIMIT));
636
+ if ($new>32 && $new<100000) {
637
+ @ini_set('memory_limit', $new.'M');
638
+ }
639
+ }
640
+ }
641
+ return $this->backup_new_instance;
642
+ }
643
+
644
  function get_backup_instance($mechanism='')
645
  {
646
  require_once($GLOBALS['iwp_mmb_plugin_dir']."/backup.class.singlecall.php");
869
  @include_once ABSPATH . 'wp-admin/includes/template.php';
870
  @include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
871
  @include_once ABSPATH . 'wp-admin/includes/screen.php';
872
+ if (!$this->define_ftp_constants($params)) {
873
+ return array(
874
+ 'error' => 'FTP constant define failed', 'error_code' => 'ftp constant define failed'
875
+ );
876
+ }
877
  if (!$this->is_server_writable()) {
878
  return array(
879
  'error' => 'Failed, please add FTP details', 'error_code' => 'automatic_upgrade_failed_add_ftp_details'
1124
 
1125
  return $all_plugins;
1126
  }
1127
+
1128
+ function add_login_action(){
1129
+ add_action('plugins_loaded', array( &$this, 'automatic_login'), 10);
1130
+ }
1131
 
1132
 
1133
  }
helper.class.php CHANGED
@@ -99,7 +99,7 @@ class IWP_MMB_Helper
99
  if (version_compare($wp_version, '3.2.2', '<=')){
100
  return get_userdatabylogin( $user_info );
101
  } else {
102
- return get_user_by( $info, $user_info );
103
  }
104
  }
105
 
@@ -190,25 +190,23 @@ class IWP_MMB_Helper
190
  }
191
  function iwp_mmb_get_transient($option_name)
192
  {
 
 
193
  if (trim($option_name) == '') {
194
- return FALSE;
195
  }
196
- if($this->iwp_mmb_multisite)
197
- return $this->iwp_mmb_get_sitemeta_transient($option_name);
198
-
199
- global $wp_version;
200
-
201
- $transient = array();
202
-
203
- if (version_compare($wp_version, '2.7.9', '<=')) {
204
- return get_option($option_name);
205
- } else if (version_compare($wp_version, '2.9.9', '<=')) {
206
- $transient = get_option('_transient_' . $option_name);
207
- return apply_filters("transient_".$option_name, $transient);
208
- } else {
209
- $transient = get_option('_site_transient_' . $option_name);
210
- return apply_filters("site_transient_".$option_name, $transient);
211
  }
 
 
 
 
 
 
 
 
212
  }
213
 
214
  function iwp_mmb_delete_transient($option_name)
@@ -426,12 +424,12 @@ class IWP_MMB_Helper
426
  if ($username) {
427
  if( !function_exists('username_exists') )
428
  include_once(ABSPATH . WPINC . '/registration.php');
429
-
430
- //include_once(ABSPATH . 'wp-includes/pluggable.php');
431
 
432
- if (username_exists($username) == null) {
433
- return false;
434
- }
435
 
436
  $user = (array) $this->iwp_mmb_get_user_info( $username );
437
  if ((isset($user[$wpdb->base_prefix . 'user_level']) && $user[$wpdb->base_prefix . 'user_level'] == 10) || isset($user[$wpdb->base_prefix . 'capabilities']['administrator']) ||
@@ -487,6 +485,36 @@ class IWP_MMB_Helper
487
  else
488
  return true;
489
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
490
 
491
  function iwp_mmb_download_url($url, $file_name)
492
  {
99
  if (version_compare($wp_version, '3.2.2', '<=')){
100
  return get_userdatabylogin( $user_info );
101
  } else {
102
+ return iwp_mmb_get_user_by( $info, $user_info );
103
  }
104
  }
105
 
190
  }
191
  function iwp_mmb_get_transient($option_name)
192
  {
193
+ global $wp_version;
194
+
195
  if (trim($option_name) == '') {
196
+ return false;
197
  }
198
+
199
+ if (version_compare($wp_version, '3.4', '>')) {
200
+ return get_site_transient($option_name);
 
 
 
 
 
 
 
 
 
 
 
 
201
  }
202
+
203
+ if (!empty($this->iwp_mmb_multisite)) {
204
+ return $this->iwp_mmb_get_sitemeta_transient($option_name);
205
+ }
206
+
207
+ $transient = get_option('_site_transient_'.$option_name);
208
+
209
+ return apply_filters("site_transient_".$option_name, $transient);
210
  }
211
 
212
  function iwp_mmb_delete_transient($option_name)
424
  if ($username) {
425
  if( !function_exists('username_exists') )
426
  include_once(ABSPATH . WPINC . '/registration.php');
427
+ // if( !function_exists('get_user_by') )
428
+ // include_once(ABSPATH . 'wp-includes/pluggable.php');
429
 
430
+ // if (username_exists($username) == null) {
431
+ // return false;
432
+ // }
433
 
434
  $user = (array) $this->iwp_mmb_get_user_info( $username );
435
  if ((isset($user[$wpdb->base_prefix . 'user_level']) && $user[$wpdb->base_prefix . 'user_level'] == 10) || isset($user[$wpdb->base_prefix . 'capabilities']['administrator']) ||
485
  else
486
  return true;
487
  }
488
+
489
+
490
+ function define_ftp_constants($params){
491
+
492
+ if (!$this->is_server_writable()) {
493
+ $ftp_details = unserialize($params['account_info']);
494
+ if (empty($ftp_details)) {
495
+ return true;
496
+ }
497
+ if (!defined('FS_METHOD')) {
498
+ define( 'FS_METHOD', 'ftpext' );
499
+ }
500
+ if (!defined('FTP_BASE')) {
501
+ define( 'FTP_BASE', $ftp_details['remoteFolder'] );
502
+ }
503
+ if (!defined('FTP_USER')) {
504
+ define( 'FTP_USER', $ftp_details['hostUserName'] );
505
+ }
506
+ if (!defined('FTP_PASS')) {
507
+ define( 'FTP_PASS', $ftp_details['hostPassword'] );
508
+ }
509
+ if (!defined('FTP_HOST')) {
510
+ define( 'FTP_HOST', $ftp_details['hostName'] );
511
+ }
512
+ if (!defined('FTP_SSL')) {
513
+ define( 'FTP_SSL', $ftp_details['hostSSL'] );
514
+ }
515
+ }
516
+ return true;
517
+ }
518
 
519
  function iwp_mmb_download_url($url, $file_name)
520
  {
init.php CHANGED
@@ -4,7 +4,7 @@ Plugin Name: InfiniteWP - Client
4
  Plugin URI: http://infinitewp.com/
5
  Description: This is the client plugin of InfiniteWP that communicates with the InfiniteWP Admin panel.
6
  Author: Revmakx
7
- Version: 1.6.6.3
8
  Author URI: http://www.revmakx.com
9
  */
10
  /************************************************************
@@ -28,8 +28,8 @@ if(basename($_SERVER['SCRIPT_FILENAME']) == "init.php"):
28
  exit;
29
  endif;
30
  if(!defined('IWP_MMB_CLIENT_VERSION'))
31
- define('IWP_MMB_CLIENT_VERSION', '1.6.6.3');
32
-
33
 
34
 
35
  if ( !defined('IWP_MMB_XFRAME_COOKIE')){
@@ -46,6 +46,8 @@ $iwp_mmb_plugin_dir = WP_PLUGIN_DIR . '/' . basename(dirname(__FILE__));
46
  $iwp_mmb_plugin_url = WP_PLUGIN_URL . '/' . basename(dirname(__FILE__));
47
 
48
  require_once("$iwp_mmb_plugin_dir/helper.class.php");
 
 
49
  require_once("$iwp_mmb_plugin_dir/core.class.php");
50
  require_once("$iwp_mmb_plugin_dir/activities_log.class.php");
51
  require_once("$iwp_mmb_plugin_dir/stats.class.php");
@@ -63,7 +65,8 @@ require_once("$iwp_mmb_plugin_dir/addons/post_links/post.class.php");
63
 
64
  require_once("$iwp_mmb_plugin_dir/addons/wp_optimize/optimize.class.php");
65
 
66
- require_once("$iwp_mmb_plugin_dir/api.php");
 
67
  require_once("$iwp_mmb_plugin_dir/plugins/search/search.php");
68
  require_once("$iwp_mmb_plugin_dir/plugins/cleanup/cleanup.php");
69
 
@@ -100,7 +103,7 @@ if( !function_exists ('iwp_mmb_parse_request')) {
100
  }
101
  }
102
 
103
- ob_start();
104
 
105
  global $current_user, $iwp_mmb_core, $new_actions, $wp_db_version, $wpmu_version, $_wp_using_ext_object_cache;
106
  if (strrpos($HTTP_RAW_POST_DATA_LOCAL, '_IWP_JSON_PREFIX_') !== false) {
@@ -139,105 +142,51 @@ if( !function_exists ('iwp_mmb_parse_request')) {
139
  }
140
  $GLOBALS['activities_log_datetime'] = $request_data['activities_log_datetime'];
141
  }
142
-
143
- if (isset($iwp_action)) {
144
-
145
  if(!defined('IWP_AUTHORISED_CALL')) define('IWP_AUTHORISED_CALL', 1);
146
  if(function_exists('register_shutdown_function')){ register_shutdown_function("iwp_mmb_shutdown"); }
147
  $GLOBALS['IWP_MMB_PROFILING']['ACTION_START'] = microtime(1);
148
 
149
- error_reporting(E_ALL ^ E_NOTICE);
150
- @ini_set("display_errors", 1);
151
 
152
  iwp_mmb_backup_db_changes();
153
 
154
  run_hash_change_process();
155
-
156
  $action = $iwp_action;
157
  $_wp_using_ext_object_cache = false;
158
- @set_time_limit(600);
159
 
160
  if (!$iwp_mmb_core->check_if_user_exists($params['username']))
161
  iwp_mmb_response(array('error' => 'Username <b>' . $params['username'] . '</b> does not have administrative access. Enter the correct username in the site options.', 'error_code' => 'username_does_not_have_administrative_access'), false);
162
 
163
  if ($action == 'add_site') {
164
- $params['is_save_activity_log'] = $is_save_activity_log;
165
- iwp_mmb_add_site($params);
166
- iwp_mmb_response(array('error' => 'You should never see this.', 'error_code' => 'you_should_never_see_this'), false);
167
- }
168
- if ($action == 'readd_site') {
169
- $params['id'] = $id;
170
- $params['signature'] = $signature;
171
- $params['is_save_activity_log'] = $is_save_activity_log;
172
- iwp_mmb_readd_site($params);
173
- iwp_mmb_response(array('error' => 'You should never see this.', 'error_code' => 'you_should_never_see_this'), false);
174
  }
175
 
176
  $auth = $iwp_mmb_core->authenticate_message($action . $id, $signature, $id);
177
  if ($auth === true) {
178
- if ($action == 'maintain_site') {
179
- iwp_mmb_maintain_site($params);
180
- iwp_mmb_response(array('error' => 'You should never see this.', 'error_code' => 'you_should_never_see_this'), false);
181
- }
182
- @ignore_user_abort(true);
183
- $GLOBALS['IWP_CLIENT_HISTORY_ID'] = $id;
184
-
185
- if(isset($params['username']) && !is_user_logged_in()){
186
- $user = function_exists('get_user_by') ? get_user_by('login', $params['username']) : get_userdatabylogin( $params['username'] );
187
- wp_set_current_user($user->ID);
188
- //For WPE or Reload Data
189
- //if(@getenv('IS_WPE') || $iwp_action == 'get_stats')
190
- $SET_14_DAYS_VALIDITY = true;
191
- wp_set_auth_cookie($user->ID, $SET_14_DAYS_VALIDITY);
192
- }
193
- if ($action == 'get_cookie') {
194
- iwp_mmb_response(true, true);
195
- }
196
- /* in case database upgrade required, do database backup and perform upgrade ( wordpress wp_upgrade() function ) */
197
- if( strlen(trim($wp_db_version)) && !defined('ACX_PLUGIN_DIR') ){
198
- if ( get_option('db_version') != $wp_db_version ) {
199
- /* in multisite network, please update database manualy */
200
- if (empty($wpmu_version) || (function_exists('is_multisite') && !is_multisite())){
201
- if( ! function_exists('wp_upgrade'))
202
- include_once(ABSPATH.'wp-admin/includes/upgrade.php');
203
-
204
- ob_clean();
205
- @wp_upgrade();
206
- @do_action('after_db_upgrade');
207
- ob_end_clean();
208
- }
209
- }
210
  }
211
-
212
- if(isset($params['secure'])){
213
- if (isset($GLOBALS['IWP_JSON_COMMUNICATION']) && $GLOBALS['IWP_JSON_COMMUNICATION']) {
214
- $params['secure'] = iwp_mmb_safe_unserialize(base64_decode($params['secure']));
215
- }
216
- if($decrypted = $iwp_mmb_core->_secure_data($params['secure'])){
217
- if (is_serialized($decrypted)) {
218
- $decrypted = iwp_mmb_safe_unserialize($decrypted);
219
- }
220
- if(is_array($decrypted)){
221
-
222
- foreach($decrypted as $key => $val){
223
- if(!is_numeric($key))
224
- $params[$key] = $val;
225
-
226
- }
227
- unset($params['secure']);
228
- } else $params['secure'] = $decrypted;
229
- }
230
- elseif(isset($params['secure']['account_info'])){
231
- $params['account_info'] = $params['secure']['account_info'];
232
- }
233
- }
234
-
235
- if( !$iwp_mmb_core->register_action_params( $action, $params ) ){
236
- global $_iwp_mmb_plugin_actions;
237
- $_iwp_mmb_plugin_actions[$action] = $params;
238
  }
239
- $iwp_mmb_activities_log->iwp_mmb_update_is_save_activity_log($is_save_activity_log);
240
- $iwp_mmb_activities_log->iwp_mmb_save_options_for_activity_log('parse_request');
 
 
241
  } else {
242
  iwp_mmb_response($auth, false);
243
  }
@@ -246,7 +195,111 @@ if( !function_exists ('iwp_mmb_parse_request')) {
246
  // $GLOBALS['HTTP_RAW_POST_DATA'] = $HTTP_RAW_POST_DATA_LOCAL;
247
  $HTTP_RAW_POST_DATA = $HTTP_RAW_POST_DATA_LOCAL;
248
  }
249
- ob_end_clean();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
  }
251
  }
252
 
@@ -320,7 +373,23 @@ if( !function_exists ( 'iwp_mmb_response' )) {
320
  $GLOBALS['IWP_RESPONSE_SENT'] = true;
321
  $response_data = base64_encode(serialize($return));
322
  }
323
- exit("<IWPHEADER>" .$response_data."<ENDIWPHEADER>");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  }
325
  }
326
 
@@ -538,6 +607,21 @@ if( !function_exists ( 'iwp_mmb_pre_init_stats' )) {
538
  }
539
  }
540
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
541
  if( !function_exists ( 'iwp_mmb_trigger_check' )) {
542
  //backup multi call trigger and status check.
543
  function iwp_mmb_trigger_check($params)
@@ -572,6 +656,55 @@ if( !function_exists ( 'iwp_mmb_backup_now' )) {
572
  }
573
  }
574
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
575
  if( !function_exists ( 'iwp_mmb_run_task_now' )) {
576
  function iwp_mmb_run_task_now($params)
577
  {
@@ -641,6 +774,74 @@ if( !function_exists ( 'iwp_mmb_scheduled_backup' )) {
641
  }
642
  }
643
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
644
 
645
  if( !function_exists ( 'iwp_mmb_delete_backup' )) {
646
  function iwp_mmb_delete_backup($params)
@@ -652,6 +853,21 @@ if( !function_exists ( 'iwp_mmb_delete_backup' )) {
652
  }
653
  }
654
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
655
  if( !function_exists ( 'iwp_mmb_optimize_tables' )) {
656
  function iwp_mmb_optimize_tables($params)
657
  {
@@ -1222,7 +1438,19 @@ if( !function_exists('iwp_mmb_wp_optimize')){
1222
  }
1223
 
1224
  //WP-Optimize_end
1225
-
 
 
 
 
 
 
 
 
 
 
 
 
1226
  /*
1227
  *WordFence Addon Start
1228
  */
@@ -1359,6 +1587,24 @@ if(!function_exists('iwp_mmb_ithemes_security_check')) {
1359
  }
1360
  }
1361
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1362
  /*
1363
  * iTheams Security Addon End here
1364
  */
@@ -2004,47 +2250,6 @@ if( !function_exists('iwp_mmb_backup_test_site')){
2004
 
2005
  //register_activation_hook( __FILE__, 'iwp_mmb_create_backup_table' );
2006
 
2007
- $iwp_mmb_core = new IWP_MMB_Core();
2008
- $GLOBALS['iwp_mmb_activities_log'] = new IWP_MMB_Activities_log();
2009
- $mmb_core = 1;
2010
- $GLOBALS['iwp_activities_log_post_type'] = 'iwp_log';
2011
-
2012
- if(isset($_GET['auto_login']))
2013
- $iwp_mmb_core->automatic_login();
2014
-
2015
- if (function_exists('register_activation_hook'))
2016
- register_activation_hook( __FILE__ , array( $iwp_mmb_core, 'install' ));
2017
-
2018
- if (function_exists('register_deactivation_hook'))
2019
- register_deactivation_hook(__FILE__, array( $iwp_mmb_core, 'uninstall' ));
2020
-
2021
- if (function_exists('add_action'))
2022
- add_action('init', 'iwp_mmb_plugin_actions', 99999);
2023
-
2024
- if (function_exists('add_action'))
2025
- add_action('template_redirect', 'iwp_mmb_check_maintenance', 99999);
2026
-
2027
- if (function_exists('add_action'))
2028
- add_action('template_redirect', 'iwp_mmb_check_redirects', 99999);
2029
-
2030
- if (function_exists('add_filter'))
2031
- add_filter('install_plugin_complete_actions','iwp_mmb_iframe_plugins_fix');
2032
-
2033
- if( isset($_COOKIE[IWP_MMB_XFRAME_COOKIE]) ){
2034
- remove_action( 'admin_init', 'send_frame_options_header');
2035
- remove_action( 'login_init', 'send_frame_options_header');
2036
- }
2037
-
2038
- //added for jQuery compatibility
2039
- if(!function_exists('iwp_mmb_register_ext_scripts')){
2040
- function iwp_mmb_register_ext_scripts(){
2041
- wp_register_script( 'iwp-clipboard', plugins_url( 'clipboard.min.js', __FILE__ ) );
2042
- }
2043
- }
2044
-
2045
- add_action( 'admin_init', 'iwp_mmb_register_ext_scripts' );
2046
-
2047
-
2048
  if(!function_exists('iwp_mmb_add_clipboard_scripts')){
2049
  function iwp_mmb_add_clipboard_scripts(){
2050
  if (!wp_script_is( 'iwp-clipboard', 'enqueued' )) {
@@ -2591,4 +2796,169 @@ function iwp_mmb_safe_unserialize( $str )
2591
  return $out;
2592
  }
2593
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2594
  ?>
4
  Plugin URI: http://infinitewp.com/
5
  Description: This is the client plugin of InfiniteWP that communicates with the InfiniteWP Admin panel.
6
  Author: Revmakx
7
+ Version: 1.8.1
8
  Author URI: http://www.revmakx.com
9
  */
10
  /************************************************************
28
  exit;
29
  endif;
30
  if(!defined('IWP_MMB_CLIENT_VERSION'))
31
+ define('IWP_MMB_CLIENT_VERSION', '1.8.1');
32
+
33
 
34
 
35
  if ( !defined('IWP_MMB_XFRAME_COOKIE')){
46
  $iwp_mmb_plugin_url = WP_PLUGIN_URL . '/' . basename(dirname(__FILE__));
47
 
48
  require_once("$iwp_mmb_plugin_dir/helper.class.php");
49
+ require_once("$iwp_mmb_plugin_dir/backup/backup.options.php");
50
+ require_once("$iwp_mmb_plugin_dir/backup/functions.php");
51
  require_once("$iwp_mmb_plugin_dir/core.class.php");
52
  require_once("$iwp_mmb_plugin_dir/activities_log.class.php");
53
  require_once("$iwp_mmb_plugin_dir/stats.class.php");
65
 
66
  require_once("$iwp_mmb_plugin_dir/addons/wp_optimize/optimize.class.php");
67
 
68
+ require_once("$iwp_mmb_plugin_dir/addons.api.php");
69
+ require_once("$iwp_mmb_plugin_dir/plugin.compatibility.class.php");
70
  require_once("$iwp_mmb_plugin_dir/plugins/search/search.php");
71
  require_once("$iwp_mmb_plugin_dir/plugins/cleanup/cleanup.php");
72
 
103
  }
104
  }
105
 
106
+
107
 
108
  global $current_user, $iwp_mmb_core, $new_actions, $wp_db_version, $wpmu_version, $_wp_using_ext_object_cache;
109
  if (strrpos($HTTP_RAW_POST_DATA_LOCAL, '_IWP_JSON_PREFIX_') !== false) {
142
  }
143
  $GLOBALS['activities_log_datetime'] = $request_data['activities_log_datetime'];
144
  }
145
+ if (isset($iwp_action) && $iwp_action != 'restoreNew') {
 
 
146
  if(!defined('IWP_AUTHORISED_CALL')) define('IWP_AUTHORISED_CALL', 1);
147
  if(function_exists('register_shutdown_function')){ register_shutdown_function("iwp_mmb_shutdown"); }
148
  $GLOBALS['IWP_MMB_PROFILING']['ACTION_START'] = microtime(1);
149
 
150
+ error_reporting(0);
151
+ @ini_set("display_errors", 0);
152
 
153
  iwp_mmb_backup_db_changes();
154
 
155
  run_hash_change_process();
156
+ iwp_plugin_compatibility_fix();
157
  $action = $iwp_action;
158
  $_wp_using_ext_object_cache = false;
159
+ // @set_time_limit(600);
160
 
161
  if (!$iwp_mmb_core->check_if_user_exists($params['username']))
162
  iwp_mmb_response(array('error' => 'Username <b>' . $params['username'] . '</b> does not have administrative access. Enter the correct username in the site options.', 'error_code' => 'username_does_not_have_administrative_access'), false);
163
 
164
  if ($action == 'add_site') {
165
+ $params['iwp_action'] = $action;
166
+ $iwp_mmb_core->request_params = $params;
167
+ return;
168
+ }elseif ($action == 'readd_site') {
169
+ $params['id'] = $id;
170
+ $params['iwp_action'] = $action;
171
+ $params['signature'] = $signature;
172
+ $iwp_mmb_core->request_params = $params;
173
+ return;
 
174
  }
175
 
176
  $auth = $iwp_mmb_core->authenticate_message($action . $id, $signature, $id);
177
  if ($auth === true) {
178
+ if (!defined('WP_ADMIN') && $action == 'get_stats' || $action == 'do_upgrade' || $action == 'install_addon' || $action == 'edit_plugins_themes') {
179
+ define('WP_ADMIN', true);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  }
181
+ if (is_multisite()) {
182
+ define('WP_NETWORK_ADMIN', true);
183
+ }else{
184
+ define('WP_NETWORK_ADMIN', false);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  }
186
+ $params['id'] = $id;
187
+ $params['iwp_action'] = $action;
188
+ $params['is_save_activity_log'] = $is_save_activity_log;
189
+ $iwp_mmb_core->request_params = $params;
190
  } else {
191
  iwp_mmb_response($auth, false);
192
  }
195
  // $GLOBALS['HTTP_RAW_POST_DATA'] = $HTTP_RAW_POST_DATA_LOCAL;
196
  $HTTP_RAW_POST_DATA = $HTTP_RAW_POST_DATA_LOCAL;
197
  }
198
+
199
+ }
200
+ }
201
+ if (!function_exists ('iwp_mmb_add_readd_request')) {
202
+ function iwp_mmb_add_readd_request(){
203
+ global $current_user, $iwp_mmb_core, $new_actions, $wp_db_version, $wpmu_version, $_wp_using_ext_object_cache, $iwp_mmb_activities_log;
204
+ if (empty($iwp_mmb_core->request_params)) {
205
+ return false;
206
+ }
207
+ $params = $iwp_mmb_core->request_params;
208
+ $action = $iwp_mmb_core->request_params['iwp_action'];
209
+
210
+ if ($action == 'add_site') {
211
+ $params['is_save_activity_log'] = $is_save_activity_log;
212
+ iwp_mmb_add_site($params);
213
+ iwp_mmb_response(array('error' => 'You should never see this.', 'error_code' => 'you_should_never_see_this'), false);
214
+ }
215
+ if ($action == 'readd_site') {
216
+ $params['id'] = $params['id'];
217
+ $params['signature'] = $params['signature'];
218
+ $params['is_save_activity_log'] = $is_save_activity_log;
219
+ iwp_mmb_readd_site($params);
220
+ iwp_mmb_response(array('error' => 'You should never see this.', 'error_code' => 'you_should_never_see_this'), false);
221
+ }
222
+ }
223
+ }
224
+ if (!function_exists ('iwp_mmb_set_request')) {
225
+ function iwp_mmb_set_request(){
226
+ global $current_user, $iwp_mmb_core, $new_actions, $wp_db_version, $wpmu_version, $_wp_using_ext_object_cache, $iwp_mmb_activities_log;
227
+ if (empty($iwp_mmb_core->request_params)) {
228
+ return false;
229
+ }
230
+ $params = $iwp_mmb_core->request_params;
231
+ $action = $iwp_mmb_core->request_params['iwp_action'];
232
+ $is_save_activity_log = $iwp_mmb_core->request_params['is_save_activity_log'];
233
+ if ($action == 'maintain_site') {
234
+ iwp_mmb_maintain_site($params);
235
+ iwp_mmb_response(array('error' => 'You should never see this.', 'error_code' => 'you_should_never_see_this'), false);
236
+ }
237
+ @ignore_user_abort(true);
238
+ $GLOBALS['IWP_CLIENT_HISTORY_ID'] = $iwp_mmb_core->request_params['id'];
239
+
240
+ if(isset($params['username']) && !is_user_logged_in()){
241
+ $user = function_exists('get_user_by') ? get_user_by('login', $params['username']) : iwp_mmb_get_user_by( 'login', $params['username'] );
242
+ if (isset($user) && isset($user->ID)) {
243
+ wp_set_current_user($user->ID);
244
+ // Compatibility with All In One Security
245
+ update_user_meta($user->ID, 'last_login_time', current_time('mysql'));
246
+ }
247
+ $isHTTPS = (bool)is_ssl();
248
+ if($isHTTPS){
249
+ wp_set_auth_cookie($user->ID);
250
+ }else{
251
+ wp_set_auth_cookie($user->ID, false, false);
252
+ wp_set_auth_cookie($user->ID, false, true);
253
+ }
254
+ }
255
+ if ($action == 'get_cookie') {
256
+ iwp_mmb_response(true, true);
257
+ }
258
+ /* in case database upgrade required, do database backup and perform upgrade ( wordpress wp_upgrade() function ) */
259
+ if( strlen(trim($wp_db_version)) && !defined('ACX_PLUGIN_DIR') ){
260
+ if ( get_option('db_version') != $wp_db_version ) {
261
+ /* in multisite network, please update database manualy */
262
+ if (empty($wpmu_version) || (function_exists('is_multisite') && !is_multisite())){
263
+ if( ! function_exists('wp_upgrade'))
264
+ include_once(ABSPATH.'wp-admin/includes/upgrade.php');
265
+
266
+ ob_clean();
267
+ @wp_upgrade();
268
+ @do_action('after_db_upgrade');
269
+ ob_end_clean();
270
+ }
271
+ }
272
+ }
273
+
274
+ if(isset($params['secure'])){
275
+ if (isset($GLOBALS['IWP_JSON_COMMUNICATION']) && $GLOBALS['IWP_JSON_COMMUNICATION']) {
276
+ $params['secure'] = iwp_mmb_safe_unserialize(base64_decode($params['secure']));
277
+ }
278
+ if($decrypted = $iwp_mmb_core->_secure_data($params['secure'])){
279
+ if (is_serialized($decrypted)) {
280
+ $decrypted = iwp_mmb_safe_unserialize($decrypted);
281
+ }
282
+ if(is_array($decrypted)){
283
+
284
+ foreach($decrypted as $key => $val){
285
+ if(!is_numeric($key))
286
+ $params[$key] = $val;
287
+
288
+ }
289
+ unset($params['secure']);
290
+ } else $params['secure'] = $decrypted;
291
+ }
292
+ elseif(isset($params['secure']['account_info'])){
293
+ $params['account_info'] = $params['secure']['account_info'];
294
+ }
295
+ }
296
+
297
+ if( !$iwp_mmb_core->register_action_params( $action, $params ) ){
298
+ global $_iwp_mmb_plugin_actions;
299
+ $_iwp_mmb_plugin_actions[$action] = $params;
300
+ }
301
+ $iwp_mmb_activities_log->iwp_mmb_update_is_save_activity_log($is_save_activity_log);
302
+ $iwp_mmb_activities_log->iwp_mmb_save_options_for_activity_log('parse_request');
303
  }
304
  }
305
 
373
  $GLOBALS['IWP_RESPONSE_SENT'] = true;
374
  $response_data = base64_encode(serialize($return));
375
  }
376
+ $txt= "<IWPHEADER>" .$response_data."<ENDIWPHEADER>";
377
+ if (defined('IWP_RESPONSE_HEADER_CLOSE') && IWP_RESPONSE_HEADER_CLOSE) {
378
+ ignore_user_abort(true);
379
+ ob_end_clean();
380
+ ob_start();
381
+ echo ($txt);
382
+ $size = ob_get_length();
383
+ header("Connection: close\r\n");
384
+ header("Content-Encoding: none\r\n");
385
+ header("Content-Length: $size");
386
+ @ob_flush();
387
+ flush();
388
+ ob_end_flush();
389
+ exit(1);
390
+ }else{
391
+ exit($txt);
392
+ }
393
  }
394
  }
395
 
607
  }
608
  }
609
 
610
+ if( !function_exists ( 'iwp_mmb_trigger_check_new' )) {
611
+ //backup multi call trigger and status check.
612
+ function iwp_mmb_trigger_check_new($params)
613
+ {
614
+ global $iwp_backup_core;
615
+ $return = $iwp_backup_core->getRunningBackupStatus($params);
616
+
617
+ if (is_array($return) && array_key_exists('error', $return))
618
+ iwp_mmb_response($return, false);
619
+ else {
620
+ iwp_mmb_response($return, true);
621
+ }
622
+ }
623
+ }
624
+
625
  if( !function_exists ( 'iwp_mmb_trigger_check' )) {
626
  //backup multi call trigger and status check.
627
  function iwp_mmb_trigger_check($params)
656
  }
657
  }
658
 
659
+ if( !function_exists ( 'iwp_mmb_new_scheduled_backup' )) {
660
+ function iwp_mmb_new_scheduled_backup($params)
661
+ {
662
+ global $iwp_backup_core;
663
+
664
+ if (!empty($params['backup_nounce'])) {
665
+ $backupId = $params['backup_nounce'];
666
+ }else{
667
+ $backupId = $iwp_backup_core->backup_time_nonce();
668
+ }
669
+
670
+ $msg = array(
671
+ 'backup_id' => $backupId,
672
+ 'parentHID' => $params['args']['parentHID'],
673
+ 'success' => 'Backup started',
674
+ 'wp_content_url' => content_url(),
675
+ );
676
+ // Close browser connection not working for some servers so we suggest IWP_PHOENIX_BACKUP_CRON
677
+ // iwp_closeBrowserConnection( $msg );
678
+ if (is_array($msg) && array_key_exists('error', $msg))
679
+ iwp_closeBrowserConnection($msg, false);
680
+ else {
681
+ iwp_closeBrowserConnection($msg, true);
682
+ }
683
+ $iwp_backup_core->set_backup_task_option($params);
684
+ if (!empty($params['args']['exclude'])) {
685
+ $params['restrict_files_to_override']= explode(',', $params['args']['exclude']);
686
+ }
687
+ // return true;
688
+ if (defined('IWP_PHOENIX_BACKUP_CRON_START') && IWP_PHOENIX_BACKUP_CRON_START) {
689
+ $params['cron_start'] = 1;
690
+ }
691
+ $params['use_nonce'] = $backupId;
692
+ $params['label'] = $params['task_name'];
693
+ $params['backup_name'] = $params['args']['backup_name'];
694
+ if ($params['args']['what'] == 'db') {
695
+ // $return = $iwp_mmb_core->backup_new_instance->backupnow_database($params);
696
+ do_action( 'IWP_backupnow_backup_database', $params );
697
+ } elseif ($params['args']['what'] == 'files') {
698
+ // $return = $iwp_mmb_core->backup_new_instance->backupnow_files($params);
699
+ do_action( 'IWP_backupnow_backup', $params );
700
+ } else {
701
+ // $return = $iwp_mmb_core->backup_new_instance->backup_all($params);
702
+ do_action( 'IWP_backupnow_backup_all', $params );
703
+ }
704
+
705
+ }
706
+ }
707
+
708
  if( !function_exists ( 'iwp_mmb_run_task_now' )) {
709
  function iwp_mmb_run_task_now($params)
710
  {
774
  }
775
  }
776
 
777
+ if( !function_exists ( 'iwp_mmb_new_run_task_now' )) {
778
+ function iwp_mmb_new_run_task_now($params)
779
+ {
780
+ global $iwp_backup_core;
781
+
782
+ if (!empty($params['backup_nounce'])) {
783
+ $backupId = $params['backup_nounce'];
784
+ }else{
785
+ $backupId = $iwp_backup_core->backup_time_nonce();
786
+ }
787
+
788
+ $msg = array(
789
+ 'backup_id' => $backupId,
790
+ 'parentHID' => $params['args']['parentHID'],
791
+ 'success' => 'Backup started',
792
+ 'wp_content_url' => content_url(),
793
+ );
794
+ // Close browser connection not working for some servers so we suggest IWP_PHOENIX_BACKUP_CRON
795
+ // iwp_closeBrowserConnection( $msg );
796
+ if (is_array($msg) && array_key_exists('error', $msg))
797
+ iwp_closeBrowserConnection($msg, false);
798
+ else {
799
+ iwp_closeBrowserConnection($msg, true);
800
+ }
801
+ $iwp_backup_core->set_backup_task_option($params);
802
+ if (!empty($params['args']['exclude'])) {
803
+ $params['restrict_files_to_override']= explode(',', $params['args']['exclude']);
804
+ }
805
+ // return true;
806
+ if (defined('IWP_PHOENIX_BACKUP_CRON_START') && IWP_PHOENIX_BACKUP_CRON_START) {
807
+ $params['cron_start'] = 1;
808
+ }
809
+ $params['use_nonce'] = $backupId;
810
+ $params['label'] = $params['task_name'];
811
+ $params['backup_name'] = $params['args']['backup_name'];
812
+ if ($params['args']['what'] == 'db') {
813
+ // $return = $iwp_mmb_core->backup_new_instance->backupnow_database($params);
814
+ do_action( 'IWP_backupnow_backup_database', $params );
815
+ } elseif ($params['args']['what'] == 'files') {
816
+ // $return = $iwp_mmb_core->backup_new_instance->backupnow_files($params);
817
+ do_action( 'IWP_backupnow_backup', $params );
818
+ } else {
819
+ // $return = $iwp_mmb_core->backup_new_instance->backup_all($params);
820
+ do_action( 'IWP_backupnow_backup_all', $params );
821
+ }
822
+
823
+ }
824
+ }
825
+
826
+ if( !function_exists ( 'iwp_mmb_delete_backup_new' )) {
827
+ function iwp_mmb_delete_backup_new($params)
828
+ {
829
+ global $iwp_mmb_core;
830
+ $iwp_mmb_core->get_new_backup_instance($params);
831
+ $return = $iwp_mmb_core->backup_new_instance->delete_backup($params);
832
+ iwp_mmb_response($return, $return);
833
+ }
834
+ }
835
+
836
+ if( !function_exists ( 'iwp_mmb_kill_new_backup' )) {
837
+ function iwp_mmb_kill_new_backup($params)
838
+ {
839
+ global $iwp_mmb_core;
840
+ $iwp_mmb_core->get_new_backup_instance($params);
841
+ $return = $iwp_mmb_core->backup_new_instance->kill_new_backup($params);
842
+ iwp_mmb_response($return, $return);
843
+ }
844
+ }
845
 
846
  if( !function_exists ( 'iwp_mmb_delete_backup' )) {
847
  function iwp_mmb_delete_backup($params)
853
  }
854
  }
855
 
856
+ if( !function_exists ( 'iwp_mmb_backup_downlaod' )) {
857
+ function iwp_mmb_backup_downlaod($params)
858
+ {
859
+ global $iwp_mmb_core;
860
+ $iwp_mmb_core->get_new_backup_instance();
861
+ //$return = $iwp_mmb_core->backup_instance->task_now(); //set_backup_task($params)
862
+ $return = $iwp_mmb_core->backup_new_instance->do_iwp_download_backup($params);
863
+ if (is_array($return) && array_key_exists('error', $return))
864
+ iwp_mmb_response($return, false);
865
+ else {
866
+ iwp_mmb_response($return, true);
867
+ }
868
+ }
869
+ }
870
+
871
  if( !function_exists ( 'iwp_mmb_optimize_tables' )) {
872
  function iwp_mmb_optimize_tables($params)
873
  {
1438
  }
1439
 
1440
  //WP-Optimize_end
1441
+ if( !function_exists('iwp_mmb_wp_purge_cache')){
1442
+ function iwp_mmb_wp_purge_cache($params){
1443
+ global $iwp_mmb_core;
1444
+ $iwp_mmb_core->wp_purge_cache_instance();
1445
+
1446
+ $return = $iwp_mmb_core->wp_purge_cache_instance->purgeAllCache($params['type']);
1447
+ if (is_array($return) && array_key_exists('error', $return))
1448
+ iwp_mmb_response($return, false);
1449
+ else {
1450
+ iwp_mmb_response($return, true);
1451
+ }
1452
+ }
1453
+ }
1454
  /*
1455
  *WordFence Addon Start
1456
  */
1587
  }
1588
  }
1589
 
1590
+ if(!function_exists('iwp_mmb_is_wordfence')) {
1591
+ function iwp_mmb_is_wordfence() {
1592
+ include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
1593
+ if ( is_plugin_active( 'wordfence/wordfence.php' ) ) {
1594
+ @include_once(WP_PLUGIN_DIR . '/wordfence/wordfence.php');
1595
+ if (class_exists('wordfence')) {
1596
+ return true;
1597
+ } else {
1598
+ return false;
1599
+ }
1600
+ } else {
1601
+ return false;
1602
+ }
1603
+
1604
+
1605
+
1606
+ }
1607
+ }
1608
  /*
1609
  * iTheams Security Addon End here
1610
  */
2250
 
2251
  //register_activation_hook( __FILE__, 'iwp_mmb_create_backup_table' );
2252
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2253
  if(!function_exists('iwp_mmb_add_clipboard_scripts')){
2254
  function iwp_mmb_add_clipboard_scripts(){
2255
  if (!wp_script_is( 'iwp-clipboard', 'enqueued' )) {
2796
  return $out;
2797
  }
2798
 
2799
+ function iwp_mmb_get_hosting_disk_quota_free() {
2800
+ if (!@is_dir('/usr/local/cpanel') || !function_exists('popen') || (!@is_executable('/usr/local/bin/perl') && !@is_executable('/usr/local/cpanel/3rdparty/bin/perl')) ) return false;
2801
+
2802
+ $perl = (@is_executable('/usr/local/cpanel/3rdparty/bin/perl')) ? '/usr/local/cpanel/3rdparty/bin/perl' : '/usr/local/bin/perl';
2803
+
2804
+ $exec = "IWPKEY=IWP $perl ".WP_PLUGIN_DIR . '/' . basename(dirname(__FILE__))."/lib/cpanel-quota-usage.pl";
2805
+ $handle = popen($exec, 'r');
2806
+ if (!is_resource($handle)) return false;
2807
+
2808
+ $found = false;
2809
+ $lines = 0;
2810
+ while (false === $found && !feof($handle) && $lines<100) {
2811
+ $lines++;
2812
+ $w = fgets($handle);
2813
+ # Used, limit, remain
2814
+ if (preg_match('/RESULT: (\d+) (\d+) (\d+) /', $w, $matches)) { $found = true; }
2815
+ }
2816
+ $ret = pclose($handle);
2817
+ if (false === $found ||$ret != 0) return false;
2818
+
2819
+ if ((int)$matches[2]<100 || ($matches[1] + $matches[3] != $matches[2])) return false;
2820
+
2821
+ return $matches;
2822
+ }
2823
+
2824
+ function iwp_mmb_check_disk_space(){
2825
+ $hosting_bytes_free = iwp_mmb_get_hosting_disk_quota_free();
2826
+ if (is_array($hosting_bytes_free)) {
2827
+ $perc = round(100*$hosting_bytes_free[1]/(max($hosting_bytes_free[2], 1)), 1);
2828
+ $quota_free = round($hosting_bytes_free[3]/1048576, 1);
2829
+ if ($hosting_bytes_free[3] < 1048576*50) {
2830
+ $quota_free_mb = round($hosting_bytes_free[3]/1048576, 1);
2831
+ return $quota_free_mb;
2832
+ }
2833
+ }
2834
+
2835
+ $disk_free_space = @disk_free_space(dirname(__FILE__));
2836
+ # == rather than === here is deliberate; support experience shows that a result of (int)0 is not reliable. i.e. 0 can be returned when the real result should be false.
2837
+ if ($disk_free_space == false) {
2838
+ return false;
2839
+ } else {
2840
+
2841
+ $disk_free_mb = round($disk_free_space/1048576, 1);
2842
+ if ($disk_free_space < 50*1048576) return $disk_free_mb;
2843
+ }
2844
+ return false;
2845
+ }
2846
+
2847
+ function iwp_closeBrowserConnection($response = false, $success = true){
2848
+ $response = iwp_mmb_convert_wperror_obj_to_arr($response,'initial');
2849
+
2850
+ if ((is_array($response) && empty($response)) || (!is_array($response) && strlen($response) == 0)){
2851
+ $return['error'] = 'Empty response.';
2852
+ $return['error_code'] = 'empty_response';
2853
+ }
2854
+ elseif ($success){
2855
+ $return['success'] = $response;
2856
+ }
2857
+ else{
2858
+ $return['error'] = $response['error'];
2859
+ $return['error_code'] = $response['error_code'];
2860
+ }
2861
+
2862
+ $txt = '<IWPHEADER>_IWP_JSON_PREFIX_' . base64_encode( iwp_mmb_json_encode( $return ) ) . '<ENDIWPHEADER>';
2863
+ ignore_user_abort(true);
2864
+ ob_end_clean();
2865
+ ob_start();
2866
+ echo ($txt);
2867
+ $size = ob_get_length();
2868
+ header("Connection: close\r\n");
2869
+ header("Content-Encoding: none\r\n");
2870
+ header("Content-Length: $size");
2871
+ @ob_flush();
2872
+ flush();
2873
+ ob_end_flush();
2874
+ }
2875
+
2876
+ function iwp_mmb_set_plugin_priority()
2877
+ {
2878
+ $pluginBasename = 'iwp-client/init.php';
2879
+ $activePlugins = get_option('active_plugins');
2880
+
2881
+ if (reset($activePlugins) === $pluginBasename) {
2882
+ return;
2883
+ }
2884
+
2885
+ $iwpKey = array_search($pluginBasename, $activePlugins);
2886
+
2887
+ if ($iwpKey === false) {
2888
+ return;
2889
+ }
2890
+
2891
+ unset($activePlugins[$iwpKey]);
2892
+ array_unshift($activePlugins, $pluginBasename);
2893
+ update_option('active_plugins', array_values($activePlugins));
2894
+ }
2895
+
2896
+ function iwp_mmb_get_user_by( $field, $value ) {
2897
+ $userdata = WP_User::get_data_by( $field, $value );
2898
+
2899
+ if ( !$userdata )
2900
+ return false;
2901
+
2902
+ $user = new WP_User;
2903
+ $user->init( $userdata );
2904
+
2905
+ return $user;
2906
+ }
2907
+
2908
+ function iwp_plugin_compatibility_fix(){
2909
+ include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
2910
+ $iwp_plugin_fix = new IWP_MMB_FixCompatibility();
2911
+ $iwp_plugin_fix->fixAllInOneSecurity();
2912
+ $iwp_plugin_fix->fixWpSimpleFirewall();
2913
+ $iwp_plugin_fix->fixDuoFactor();
2914
+ $iwp_plugin_fix->fixShieldUserManagementICWP();
2915
+ $iwp_plugin_fix->fixSidekickPlugin();
2916
+ $iwp_plugin_fix->fixSpamShield();
2917
+ $iwp_plugin_fix->fixWpSpamShieldBan();
2918
+ $iwp_plugin_fix->fixPantheonGlobals();
2919
+
2920
+ }
2921
+
2922
+ iwp_mmb_set_plugin_priority();
2923
+ $iwp_mmb_core = new IWP_MMB_Core();
2924
+ $GLOBALS['iwp_mmb_activities_log'] = new IWP_MMB_Activities_log();
2925
+ $mmb_core = 1;
2926
+ $GLOBALS['iwp_activities_log_post_type'] = 'iwp_log';
2927
+
2928
+ if(isset($_GET['auto_login']))
2929
+ $iwp_mmb_core->add_login_action();
2930
+ if (function_exists('register_activation_hook'))
2931
+ register_activation_hook( __FILE__ , array( $iwp_mmb_core, 'install' ));
2932
+
2933
+ if (function_exists('register_deactivation_hook'))
2934
+ register_deactivation_hook(__FILE__, array( $iwp_mmb_core, 'uninstall' ));
2935
+
2936
+ if (function_exists('add_action'))
2937
+ add_action('init', 'iwp_mmb_plugin_actions', 99999);
2938
+
2939
+ if (function_exists('add_action'))
2940
+ add_action('template_redirect', 'iwp_mmb_check_maintenance', 99999);
2941
+
2942
+ if (function_exists('add_action'))
2943
+ add_action('template_redirect', 'iwp_mmb_check_redirects', 99999);
2944
+
2945
+ if (function_exists('add_filter'))
2946
+ add_filter('install_plugin_complete_actions','iwp_mmb_iframe_plugins_fix');
2947
+
2948
+ if( isset($_COOKIE[IWP_MMB_XFRAME_COOKIE]) ){
2949
+ remove_action( 'admin_init', 'send_frame_options_header');
2950
+ remove_action( 'login_init', 'send_frame_options_header');
2951
+ }
2952
+
2953
+ //added for jQuery compatibility
2954
+ if(!function_exists('iwp_mmb_register_ext_scripts')){
2955
+ function iwp_mmb_register_ext_scripts(){
2956
+ wp_register_script( 'iwp-clipboard', plugins_url( 'clipboard.min.js', __FILE__ ) );
2957
+ }
2958
+ }
2959
+
2960
+ add_action( 'admin_init', 'iwp_mmb_register_ext_scripts' );
2961
+ $iwp_mmb_core->get_new_backup_instance();
2962
+ iwp_mmb_parse_request();
2963
+
2964
  ?>
installer.class.php CHANGED
@@ -70,7 +70,12 @@ class IWP_MMB_Installer extends IWP_MMB_Core
70
  return array(
71
  'error' => '<p>No files received. Internal error.</p>', 'error_code' => 'no_files_receive_internal_error'
72
  );
73
-
 
 
 
 
 
74
  if (!$this->is_server_writable()) {
75
  return array(
76
  'error' => 'Failed, please add FTP details', 'error_code' => 'failed_please_add_ftp_install_remote_file'
@@ -104,6 +109,31 @@ class IWP_MMB_Installer extends IWP_MMB_Core
104
  'hook_extra' => array()
105
  ));
106
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
  if ($activate) {
109
  if ($type == 'plugins') {
@@ -168,7 +198,11 @@ class IWP_MMB_Installer extends IWP_MMB_Core
168
  return array(
169
  'error' => 'No upgrades passed.', 'error_code' => 'no_upgrades_passed'
170
  );
171
-
 
 
 
 
172
  if (!$this->is_server_writable()) {
173
  return array(
174
  'error' => 'Failed, please add FTP details', 'error_code' => 'failed_please_add_ftp_do_upgrade'
@@ -208,6 +242,7 @@ class IWP_MMB_Installer extends IWP_MMB_Core
208
  }
209
  if (!empty($upgrade_plugins)) {
210
  $plugin_files = $plugin_details = $premium_plugin_details = array();
 
211
  foreach ($upgrade_plugins as $plugin) {
212
  $file_path = $plugin['file'];
213
  $plugin_name = $plugin['name'];
@@ -222,6 +257,7 @@ class IWP_MMB_Installer extends IWP_MMB_Core
222
  if (!empty($plugin_files)) {
223
  $upgrades['plugins'] = $this->upgrade_plugins($plugin_files,$plugin_details,$userid);
224
  }
 
225
  }
226
 
227
  if (!empty($upgrade_themes)) {
@@ -815,28 +851,20 @@ class IWP_MMB_Installer extends IWP_MMB_Core
815
  $current = $this->iwp_mmb_get_transient('update_themes');
816
  if (!empty($current->response)) {
817
  foreach ((array) $all_themes as $theme_template => $theme_data) {
 
 
 
 
818
 
819
- if (isset($theme_data->{'Parent Theme'}) && !empty($theme_data->{'Parent Theme'})) {
820
- continue;
821
- }
822
-
823
- if (isset($theme_data->Name) && in_array($theme_data->Name, $filter)) {
824
- continue;
825
- }
826
-
827
- if (method_exists($theme_data,'parent') && !$theme_data->parent()) {
828
- foreach ($current->response as $current_themes => $theme) {
829
- if ($theme_data->Template == $current_themes) {
830
- if (strlen($theme_data->Name) > 0 && strlen($theme_data->Version) > 0) {
831
 
832
- $current->response[$current_themes]['name'] = $theme_data->Name;
833
- $current->response[$current_themes]['old_version'] = $theme_data->Version;
834
- $current->response[$current_themes]['theme_tmp'] = $theme_data->Template;
835
- $upgrade_themes[] = $current->response[$current_themes];
836
 
837
- }
838
- }
839
- }
840
  }
841
  }
842
  }
@@ -994,7 +1022,7 @@ class IWP_MMB_Installer extends IWP_MMB_Core
994
 
995
  }
996
 
997
- if ($search) {
998
  foreach ($themes['active'] as $k => $theme) {
999
  if (!stristr($theme['name'], $search)) {
1000
  unset($themes['active'][$k]);
@@ -1063,6 +1091,11 @@ class IWP_MMB_Installer extends IWP_MMB_Core
1063
  {
1064
  extract($args);
1065
  $return = array();
 
 
 
 
 
1066
  if ($type == 'plugins') {
1067
  $return['plugins'] = $this->edit_plugins($args);
1068
  } elseif ($type == 'themes') {
@@ -1144,5 +1177,26 @@ class IWP_MMB_Installer extends IWP_MMB_Core
1144
  return $return;
1145
 
1146
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1147
  }
1148
  ?>
70
  return array(
71
  'error' => '<p>No files received. Internal error.</p>', 'error_code' => 'no_files_receive_internal_error'
72
  );
73
+
74
+ if (!$this->define_ftp_constants($params)) {
75
+ return array(
76
+ 'error' => 'FTP constant define failed', 'error_code' => 'ftp constant define failed'
77
+ );
78
+ }
79
  if (!$this->is_server_writable()) {
80
  return array(
81
  'error' => 'Failed, please add FTP details', 'error_code' => 'failed_please_add_ftp_install_remote_file'
109
  'hook_extra' => array()
110
  ));
111
  }
112
+
113
+ // if (defined('WP_ADMIN') && WP_ADMIN) {
114
+ // global $wp_current_filter;
115
+ // $wp_current_filter[] = 'load-update-core.php';
116
+
117
+ // if (function_exists('wp_clean_update_cache')) {
118
+ // /** @handled function */
119
+ // wp_clean_update_cache();
120
+ // }
121
+
122
+ // /** @handled function */
123
+ // wp_update_plugins();
124
+
125
+ // array_pop($wp_current_filter);
126
+
127
+ // /** @handled function */
128
+ // set_current_screen();
129
+ // do_action('load-update-core.php');
130
+
131
+ // /** @handled function */
132
+ // wp_version_check();
133
+
134
+ // /** @handled function */
135
+ // wp_version_check(array(), true);
136
+ // }
137
 
138
  if ($activate) {
139
  if ($type == 'plugins') {
198
  return array(
199
  'error' => 'No upgrades passed.', 'error_code' => 'no_upgrades_passed'
200
  );
201
+ if (!$this->define_ftp_constants($params)) {
202
+ return array(
203
+ 'error' => 'FTP constant define failed', 'error_code' => 'ftp constant define failed'
204
+ );
205
+ }
206
  if (!$this->is_server_writable()) {
207
  return array(
208
  'error' => 'Failed, please add FTP details', 'error_code' => 'failed_please_add_ftp_do_upgrade'
242
  }
243
  if (!empty($upgrade_plugins)) {
244
  $plugin_files = $plugin_details = $premium_plugin_details = array();
245
+ $this->IWP_ithemes_updater_compatiblity();
246
  foreach ($upgrade_plugins as $plugin) {
247
  $file_path = $plugin['file'];
248
  $plugin_name = $plugin['name'];
257
  if (!empty($plugin_files)) {
258
  $upgrades['plugins'] = $this->upgrade_plugins($plugin_files,$plugin_details,$userid);
259
  }
260
+ $this->IWP_ithemes_updater_compatiblity();
261
  }
262
 
263
  if (!empty($upgrade_themes)) {
851
  $current = $this->iwp_mmb_get_transient('update_themes');
852
  if (!empty($current->response)) {
853
  foreach ((array) $all_themes as $theme_template => $theme_data) {
854
+ foreach ($current->response as $current_themes => $theme) {
855
+ if ($theme_data->Stylesheet !== $current_themes) {
856
+ continue;
857
+ }
858
 
859
+ if (strlen($theme_data->Name) === 0 || strlen($theme_data->Version) === 0) {
860
+ continue;
861
+ }
 
 
 
 
 
 
 
 
 
862
 
863
+ $current->response[$current_themes]['name'] = $theme_data->Name;
864
+ $current->response[$current_themes]['old_version'] = $theme_data->Version;
865
+ $current->response[$current_themes]['theme_tmp'] = $theme_data->Stylesheet;
 
866
 
867
+ $upgrade_themes[] = $current->response[$current_themes];
 
 
868
  }
869
  }
870
  }
1022
 
1023
  }
1024
 
1025
+ if (!empty($search)) {
1026
  foreach ($themes['active'] as $k => $theme) {
1027
  if (!stristr($theme['name'], $search)) {
1028
  unset($themes['active'][$k]);
1091
  {
1092
  extract($args);
1093
  $return = array();
1094
+ if (!$this->define_ftp_constants($args)) {
1095
+ return array(
1096
+ 'error' => 'FTP constant define failed', 'error_code' => 'ftp constant define failed'
1097
+ );
1098
+ }
1099
  if ($type == 'plugins') {
1100
  $return['plugins'] = $this->edit_plugins($args);
1101
  } elseif ($type == 'themes') {
1177
  return $return;
1178
 
1179
  }
1180
+
1181
+ function IWP_ithemes_updater_compatiblity()
1182
+ {
1183
+ // Check for the iThemes updater class
1184
+ if (empty($GLOBALS['ithemes_updater_path']) ||
1185
+ !file_exists($GLOBALS['ithemes_updater_path'].'/settings.php')
1186
+ ) {
1187
+ return;
1188
+ }
1189
+
1190
+ // Include iThemes updater
1191
+ require_once $GLOBALS['ithemes_updater_path'].'/settings.php';
1192
+
1193
+ // Check if the updater is instantiated
1194
+ if (empty($GLOBALS['ithemes-updater-settings'])) {
1195
+ return;
1196
+ }
1197
+
1198
+ // Update the download link
1199
+ $GLOBALS['ithemes-updater-settings']->flush('forced');
1200
+ }
1201
  }
1202
  ?>
lib/Dropbox/API.php ADDED
@@ -0,0 +1,802 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Dropbox API base class
5
+ * @author Ben Tadiar <ben@handcraftedbyben.co.uk>
6
+ * @link https://github.com/benthedesigner/dropbox
7
+ * @link https://www.dropbox.com/developers
8
+ * @link https://status.dropbox.com Dropbox status
9
+ * @package Dropbox
10
+ */
11
+
12
+ class IWP_Dropbox_API {
13
+ // API Endpoints
14
+ const API_URL = 'https://api.dropbox.com/1/';
15
+ const API_URL_V2 = 'https://api.dropboxapi.com/';
16
+ const CONTENT_URL = 'https://api-content.dropbox.com/1/';
17
+ const CONTENT_URL_V2 = 'https://content.dropboxapi.com/2/';
18
+
19
+ /**
20
+ * OAuth consumer object
21
+ * @var null|OAuth\Consumer
22
+ */
23
+ private $OAuth;
24
+
25
+ /**
26
+ * The root level for file paths
27
+ * Either `dropbox` or `sandbox` (preferred)
28
+ * @var null|string
29
+ */
30
+ private $root;
31
+
32
+ /**
33
+ * Chunk size used for chunked uploads
34
+ * @see \Dropbox_API::chunkedUpload()
35
+ */
36
+ private $chunkSize = 4194304;
37
+
38
+ private $responseFormat = 'php';
39
+
40
+ private $callback = 'dropboxCallback';
41
+ /**
42
+ * Object to track uploads
43
+ */
44
+ private $tracker;
45
+
46
+ private $base;
47
+
48
+ /**
49
+ * Set the OAuth consumer object
50
+ * See 'General Notes' at the link below for information on access type
51
+ * @link https://www.dropbox.com/developers/reference/api
52
+ * @param OAuth\Consumer\ConsumerAbstract $OAuth
53
+ * @param string $root Dropbox app access type
54
+ */
55
+ public function __construct($OAuth, $root = 'dropbox') {
56
+ $this->OAuth = $OAuth;
57
+ $this->setRoot($root);
58
+ }
59
+
60
+ /**
61
+ * Set the root level
62
+ * @param mixed $root
63
+ * @throws Exception
64
+ * @return void
65
+ */
66
+ public function setRoot($root) {
67
+ if ($root !== 'sandbox' && $root !== 'dropbox') {
68
+ throw new Exception("Expected a root of either 'dropbox' or 'sandbox', got '$root'");
69
+ } else {
70
+ $this->root = $root;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Set the tracker
76
+ * @param Tracker $tracker
77
+ */
78
+ public function setTracker($tracker) {
79
+ $this->tracker = $tracker;
80
+ }
81
+
82
+ /**
83
+ * Retrieves information about the user's account
84
+ * @return object stdClass
85
+ */
86
+ public function accountInfo() {
87
+ //API V1
88
+ // return $this->fetch('POST', self::API_URL, 'account/info');
89
+
90
+ $call = '2/users/get_current_account';
91
+ $params = array('api_v2' => true);
92
+ $response = $this->fetch('POST', self::API_URL_V2, $call, $params);
93
+ return $response;
94
+ }
95
+
96
+ /**
97
+ * Retrieves information about the user's quota
98
+ * @return object stdClass
99
+ */
100
+ public function quotaInfo() {
101
+ $call = '2/users/get_space_usage';
102
+ $params = array('api_v2' => true);
103
+ $response = $this->fetch('POST', self::API_URL_V2, $call, $params);
104
+ return $response;
105
+ }
106
+
107
+ /**
108
+ * Uploads a physical file from disk
109
+ * Dropbox impose a 150MB limit to files uploaded via the API. If the file
110
+ * exceeds this limit or does not exist, an Exception will be thrown
111
+ * @param string $file Absolute path to the file to be uploaded
112
+ * @param string|bool $filename The destination filename of the uploaded file
113
+ * @param string $path Path to upload the file to, relative to root
114
+ * @param boolean $overwrite Should the file be overwritten? (Default: true)
115
+ * @return object stdClass
116
+ */
117
+ public function putFile($file, $path = '', $overwrite = true) {
118
+ if (!file_exists($file)) {
119
+ // Throw an Exception if the file does not exist
120
+ throw new Exception('Local file ' . $file . ' does not exist');
121
+ }
122
+ $filesize = iwp_mmb_get_file_size($file);
123
+ if ($filesize >= 157286400) {
124
+ $output = $this->chunked_upload_single_call_new($file, $path,$overwrite);
125
+ return $output;
126
+
127
+ }else{
128
+ $handle = @fopen($file, 'r');
129
+ //Set the file content to $this->InFile
130
+ $this->OAuth->setInFile(fread($handle, filesize($file)));
131
+ fclose($handle);
132
+
133
+ $filename = (is_string($filename)) ? $filename : basename($file);
134
+ $path = '/' . $this->encodePath($path .'/'. $filename);
135
+ $params = array(
136
+ 'path' => $path,
137
+ 'mute' => true,
138
+ 'mode' => 'overwrite',
139
+ 'api_v2' => true,
140
+ 'content_upload' => true
141
+ );
142
+ $response = $this->fetch('POST', self::CONTENT_URL_V2, 'files/upload', $params);
143
+ return $response;
144
+ }
145
+
146
+ }
147
+
148
+ public function chunked_upload_single_call_new($file, $path = '',$overwrite=true){
149
+ $file = str_replace("\\", "/",$file);
150
+ if (!is_readable($file) or !is_file($file))
151
+ throw new IWP_DropboxException("Error: File \"$file\" is not readable or doesn't exist.");
152
+ $file_handle=fopen($file,'r');
153
+ $uploadID=null;
154
+ $offset=0;
155
+ $ProgressFunction=null;
156
+ while ($data=fread($file_handle, (1024*1024*30))) { //1024*1024*30 = 30MB
157
+ $firstCommit = (0 == $offset);
158
+ iwp_mmb_auto_print('dropbox_chucked_upload');
159
+ $this->OAuth->setInFile($data);
160
+
161
+ if ($firstCommit) {
162
+ $params = array(
163
+ 'close' => false,
164
+ 'api_v2' => true,
165
+ 'content_upload' => true
166
+ );
167
+ $response = $this->fetch('POST', self::CONTENT_URL_V2, 'files/upload_session/start', $params);
168
+ $firstCommit = false;
169
+
170
+ } else {
171
+ $params = array(
172
+ 'cursor' => array(
173
+ 'session_id' => $uploadID,
174
+ // If you send it as a string, Dropbox will be unhappy
175
+ 'offset' => (int)$offset
176
+ ),
177
+ 'api_v2' => true,
178
+ 'content_upload' => true
179
+ );
180
+ $response = $this->append_upload($params, false);
181
+ }
182
+
183
+ // On subsequent chunks, use the upload ID returned by the previous request
184
+ if (isset($response['body']->session_id)) {
185
+ $uploadID = $response['body']->session_id;
186
+ }
187
+
188
+ /*
189
+ 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.
190
+ */
191
+ if (!isset($response['body']->error)) {
192
+ $offset = ftell($file_handle);
193
+ $output['response']= $response;
194
+ if($isCommit ==false){
195
+
196
+ $output['offset']= $offset;
197
+ $output['upload_id']= $uploadID;
198
+ }
199
+ $this->OAuth->setInFile(null);
200
+ }
201
+ fseek($file_handle, $offset);
202
+ }
203
+ fclose($file_handle);
204
+ $filename = (is_string($filename)) ? $filename : basename($file);
205
+ $params = array(
206
+ 'cursor' => array(
207
+ 'session_id' => $uploadID,
208
+ 'offset' => (int)$offset
209
+ ),
210
+ 'commit' => array(
211
+ 'path' => '/' . $this->encodePath($path .'/'. $filename),
212
+ 'mode' => 'overwrite'
213
+ ),
214
+ 'api_v2' => true,
215
+ 'content_upload' => true
216
+ );
217
+ $response = $this->append_upload($params, true);
218
+
219
+ return $response;
220
+ }
221
+
222
+ /**
223
+ * Not used
224
+ * Uploads file data from a stream
225
+ * Note: This function is experimental and requires further testing
226
+ * @todo Add filesize check
227
+ *@ param resource $stream A readable stream created using fopen()
228
+ * @param string $filename The destination filename, including path
229
+ * @param boolean $overwrite Should the file be overwritten? (Default: true)
230
+ * @return array
231
+ */
232
+ // public function putStream($stream, $filename, $overwrite = true) {
233
+ // $this->OAuth->setInFile($stream);
234
+ // $path = $this->encodePath($filename);
235
+ // $call = 'files_put/' . $this->root . '/' . $path;
236
+ // $params = array('overwrite' => (int) $overwrite);
237
+
238
+ // return $this->fetch('PUT', self::CONTENT_URL, $call, $params);
239
+ // }
240
+
241
+ /**
242
+ * Uploads large files to Dropbox in mulitple chunks
243
+ * @param string $file Absolute path to the file to be uploaded
244
+ * @param string|bool $filename The destination filename of the uploaded file
245
+ * @param string $path Path to upload the file to, relative to root
246
+ * @param boolean $overwrite Should the file be overwritten? (Default: true)
247
+ * @return stdClass
248
+ */
249
+ public function chunked_upload($file, $path = '', $overwrite = true, $uploadID = null, $offset = 0, $isCommit = false) {
250
+ $starting_backup_path_time = time();
251
+
252
+ $file = str_replace("\\", "/",$file);
253
+ if (!file_exists($file)) throw new Exception('Local file ' . $file . ' does not exist');
254
+
255
+ if (!($handle = @fopen($file, 'r'))) throw new Exception('Could not open ' . $file . ' for reading');
256
+
257
+ // Seek to the correct position on the file pointer
258
+ fseek($handle, $offset);
259
+ $to_exit = false;
260
+
261
+ //Set firstCommit to true so that the upload session start endpoint is called.
262
+ $firstCommit = (0 == $offset);
263
+
264
+ // Read from the file handle until EOF, uploading each chunk
265
+ if ($data = fread($handle, $this->chunkSize)) {
266
+
267
+ // Set the file, request parameters and send the request
268
+ $this->OAuth->setInFile($data);
269
+
270
+ if ($firstCommit) {
271
+ $params = array(
272
+ 'close' => false,
273
+ 'api_v2' => true,
274
+ 'content_upload' => true
275
+ );
276
+ $response = $this->fetch('POST', self::CONTENT_URL_V2, 'files/upload_session/start', $params);
277
+ $firstCommit = false;
278
+
279
+ } else {
280
+ $params = array(
281
+ 'cursor' => array(
282
+ 'session_id' => $uploadID,
283
+ // If you send it as a string, Dropbox will be unhappy
284
+ 'offset' => (int)$offset
285
+ ),
286
+ 'api_v2' => true,
287
+ 'content_upload' => true
288
+ );
289
+ $response = $this->append_upload($params, false);
290
+ }
291
+
292
+ // On subsequent chunks, use the upload ID returned by the previous request
293
+ if (isset($response['body']->session_id)) {
294
+ $uploadID = $response['body']->session_id;
295
+ }
296
+
297
+ /*
298
+ 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.
299
+ */
300
+ if (!isset($response['body']->error)) {
301
+ $offset = ftell($handle);
302
+ $output['response']= $response;
303
+ if($isCommit ==false){
304
+
305
+ $output['offset']= $offset;
306
+ $output['upload_id']= $uploadID;
307
+ }
308
+ $this->OAuth->setInFile(null);
309
+ }
310
+
311
+ }
312
+ // Complete the chunked upload
313
+ if ($isCommit) {
314
+ $filename = (is_string($filename)) ? $filename : basename($file);
315
+ $params = array(
316
+ 'cursor' => array(
317
+ 'session_id' => $uploadID,
318
+ 'offset' => (int)$offset
319
+ ),
320
+ 'commit' => array(
321
+ 'path' => '/' . $this->encodePath($path .'/'. $filename),
322
+ 'mode' => 'overwrite'
323
+ ),
324
+ 'api_v2' => true,
325
+ 'content_upload' => true
326
+ );
327
+ $response = $this->append_upload($params, true);
328
+ $offset = ftell($handle);
329
+ $output['response']= $response;
330
+ }
331
+
332
+ fclose($handle);
333
+ return $output;
334
+ }
335
+
336
+ private function append_upload($params, $last_call) {
337
+ try {
338
+ if ($last_call){
339
+ $response = $this->fetch('POST', self::CONTENT_URL_V2, 'files/upload_session/finish', $params);
340
+ } else {
341
+ $response = $this->fetch('POST', self::CONTENT_URL_V2, 'files/upload_session/append_v2', $params);
342
+ }
343
+ } catch (Exception $e) {
344
+ $responseCheck = json_decode($e->getMessage());
345
+ if (isset($responseCheck) && strpos($responseCheck[0] , 'incorrect_offset') !== false) {
346
+ $expected_offset = $responseCheck[1];
347
+ throw new Exception('Submitted input out of alignment: got ['.$params['cursor']['offset'].'] expected ['.$expected_offset.']');
348
+ //$params['cursor']['offset'] = $responseCheck[1];
349
+ //$response = $this->append_upload($params, $last_call);
350
+ } else {
351
+ throw $e;
352
+ }
353
+ }
354
+ return $response;
355
+ }
356
+
357
+ /**
358
+ * Downloads a file
359
+ * Returns the base filename, raw file data and mime type returned by Fileinfo
360
+ * @param string $file Path to file, relative to root, including path
361
+ * @param string $outFile Filename to write the downloaded file to
362
+ * @param string $revision The revision of the file to retrieve
363
+ * @return array
364
+ */
365
+ public function getFile($file, $outFile = false, $revision = null, $allow_resume = array()) {
366
+ $handle = null;
367
+ // $tempFolder = $this->getTempFolderFromOutFile(wp_normalize_path($outFile));
368
+ if ($outFile !== false) {
369
+ // Create a file handle if $outFile is specified
370
+ $this->prepareSetOutFile($outFile, 'w');
371
+ }
372
+
373
+ // $file = $this->encodePath($file);
374
+ $call = 'files/download';
375
+ $params = array('path' => '/'.$this->normalisePath($file), 'api_v2' => true, 'content_download' => true);
376
+ $response = $this->fetch('GET', self::CONTENT_URL_V2, $call, $params);
377
+ // Close the file handle if one was opened
378
+ if ($handle) fclose($handle);
379
+
380
+ return array(
381
+ 'name' => ($outFile) ? $outFile : basename($file),
382
+ 'mime' => $this->getMimeType(($outFile) ? $outFile : $response['body'], $outFile),
383
+ 'meta' => json_decode($response['headers']['dropbox-api-result']),
384
+ 'data' => $response['body'],
385
+ );
386
+ }
387
+
388
+ public function prepareSetOutFile($outFile, $mode) {
389
+ // $tempFolderFile = $this->getTempFolderFromOutFile(wp_normalize_path($outFile));
390
+ $tempFolderFile = wp_normalize_path($outFile);
391
+
392
+ //setting chmod from filesystem
393
+
394
+ if (!$handle = @fopen($tempFolderFile, $mode)) {
395
+ throw new Exception("Unable to open file handle for $tempFolderFile");
396
+ } else {
397
+ $this->OAuth->setOutFile($handle);
398
+ return $handle;
399
+ }
400
+ }
401
+
402
+ public function getTempFolderFromOutFile($outFile, $mode = '') {
403
+ //this function creates the file and its respective folders ; this function also create the exact file path from the DB values
404
+ $config = WPTC_Factory::get('config');
405
+ $is_staging_running = $config->get_option('is_staging_running');
406
+ if($is_staging_running){
407
+ $site_abspath = $config->get_option('site_abspath');
408
+ $this_absbath_length = (strlen($site_abspath) - 1);
409
+ } else{
410
+ $this_absbath_length = (strlen(ABSPATH) - 1);
411
+ }
412
+
413
+ $this_temp_file = $config->get_option('backup_db_path');
414
+ $this_temp_file = $config->wp_filesystem_safe_abspath_replace($this_temp_file);
415
+ $this_temp_file = $this_temp_file. '/tCapsule' . substr($outFile, $this_absbath_length);
416
+
417
+ //get the folder name from the full file path
418
+ $base_file_name = basename($this_temp_file);
419
+ $base_file_name_pos = strrpos($this_temp_file, $base_file_name);
420
+ $base_file_name_pos = $base_file_name_pos - 1;
421
+ $this_temp_folder = substr($this_temp_file, 0, $base_file_name_pos);
422
+
423
+ $this_temp_folder = $config->wp_filesystem_safe_abspath_replace($this_temp_folder);
424
+
425
+ $this->base->createRecursiveFileSystemFolder($this_temp_folder);
426
+ return $this_temp_file;
427
+ }
428
+
429
+ /**
430
+ * Downloads a file
431
+ * Returns the base filename, raw file data and mime type returned by Fileinfo
432
+ * @param string $file Path to file, relative to root, including path
433
+ * @param string $outFile Filename to write the downloaded file to
434
+ * @param string $revision The revision of the file to retrieve
435
+ * @return array
436
+ */
437
+ public function chunkedDownload($file, $outFile = false, $revision = null, $isChunkDownload = array(), $meta_file_download = null) {
438
+ global $start_time_tc;
439
+ $start_time_tc = time();
440
+ $handle = null;
441
+ if ($outFile !== false) {
442
+ // Create a file handle if $outFile is specified
443
+ if ($isChunkDownload['c_offset'] == 0) {
444
+ //while restoring ... first
445
+ $handle = $this->prepareSetOutFile($outFile, 'w');
446
+ } else {
447
+ $handle = $this->prepareSetOutFile($outFile, 'a');
448
+ }
449
+ }
450
+
451
+ $outFilePath = wp_normalize_path($outFile);
452
+ $call = 'files/download';
453
+ $params = array('path' =>'/'.$file, 'api_v2' => true, 'content_download' => true);
454
+ $response = $this->fetch('GET', self::CONTENT_URL_V2, $call, $params, $isChunkDownload);
455
+
456
+ // Set the data offset
457
+ if ($response) {
458
+ $offset = filesize($outFilePath);
459
+ }
460
+
461
+ if (empty($meta_file_download)) {
462
+ if ($this->tracker) {
463
+ $this->tracker->track_download($outFile, false, $offset, $isChunkDownload);
464
+ }
465
+ } else {
466
+ $this->tracker->track_meta_download($offset, $isChunkDownload);
467
+ }
468
+
469
+ // Close the file handle if one was opened
470
+ if ($handle) {
471
+ fclose($handle);
472
+ }
473
+
474
+ $data = array(
475
+ 'name' => ($outFile) ? $outFile : basename($file),
476
+ 'mime' => $this->getMimeType(($outFile) ? $outFile : $response['body'], $outFile),
477
+ 'meta' => json_decode($response['headers']['dropbox-api-result']),
478
+ 'data' => $response['body'],
479
+ 'chunked' => true,
480
+ );
481
+ return $data;
482
+ }
483
+
484
+ public function delete($path) {
485
+ $call = '2/files/delete';
486
+ $params = array('path' => '/' . $this->normalisePath($path), 'api_v2' => true);
487
+ $response = $this->fetch('POST', self::API_URL_V2, $call, $params);
488
+ return $response;
489
+ }
490
+ /**
491
+ * Not used
492
+ * Retrieves file and folder metadata
493
+ * @param string $path The path to the file/folder, relative to root
494
+ * @param string $rev Return metadata for a specific revision (Default: latest rev)
495
+ * @param int $limit Maximum number of listings to return
496
+ * @param string $hash Metadata hash to compare against
497
+ * @param bool $list Return contents field with response
498
+ * @param bool $deleted Include files/folders that have been deleted
499
+ * @return object stdClass
500
+ */
501
+ public function metaData($path = null, $rev = null, $limit = 10000, $hash = false, $list = true, $deleted = false) {
502
+ $call = '2/files/get_metadata' ;
503
+ $params = array(
504
+ 'path' => '/' . $this->normalisePath($path),
505
+ 'api_v2' => true
506
+ );
507
+
508
+ return $this->fetch('POST', self::API_URL_V2, $call, $params);
509
+ }
510
+
511
+ /**
512
+ * Not used
513
+ * Return "delta entries", intructing you how to update
514
+ * your application state to match the server's state
515
+ * Important: This method does not make changes to the application state
516
+ * @param null|string $cursor Used to keep track of your current state
517
+ * @return array Array of delta entries
518
+ */
519
+ // public function delta($cursor = null) {
520
+ // $call = 'delta';
521
+ // $params = array('cursor' => $cursor);
522
+
523
+ // return $this->fetch('POST', self::API_URL, $call, $params);
524
+ // }
525
+
526
+ /**
527
+ * Not used
528
+ * Obtains metadata for the previous revisions of a file
529
+ * @param string Path to the file, relative to root
530
+ * @param integer Number of revisions to return (1-1000)
531
+ * @return array
532
+ */
533
+ // public function revisions($file, $limit = 10) {
534
+ // $call = 'revisions/' . $this->root . '/' . $this->encodePath($file);
535
+ // $params = array(
536
+ // 'rev_limit' => ($limit < 1) ? 1 : (($limit > 1000) ? 1000 : (int) $limit),
537
+ // );
538
+
539
+ // return $this->fetch('GET', self::API_URL, $call, $params);
540
+ // }
541
+
542
+ /**
543
+ * Not used
544
+ * Restores a file path to a previous revision
545
+ * @param string $file Path to the file, relative to root
546
+ * @param string $revision The revision of the file to restore
547
+ * @return object stdClass
548
+ */
549
+ // public function restore($file, $revision) {
550
+ // $call = 'restore/' . $this->root . '/' . $this->encodePath($file);
551
+ // $params = array('rev' => $revision);
552
+
553
+ // return $this->fetch('POST', self::API_URL, $call, $params);
554
+ // }
555
+
556
+ /**
557
+ * Returns metadata for all files and folders that match the search query
558
+ * @param mixed $query The search string. Must be at least 3 characters long
559
+ * @param string $path The path to the folder you want to search in
560
+ * @param integer $limit Maximum number of results to return (1-1000)
561
+ * @param boolean $deleted Include deleted files/folders in the search
562
+ * @return array
563
+ */
564
+ //Not used
565
+ // public function search($query, $path = '', $limit = 1000, $deleted = false) {
566
+ // $call = 'search/' . $this->root . '/' . $this->encodePath($path);
567
+ // $params = array(
568
+ // 'query' => $query,
569
+ // 'file_limit' => ($limit < 1) ? 1 : (($limit > 1000) ? 1000 : (int) $limit),
570
+ // 'include_deleted' => (int) $deleted,
571
+ // );
572
+
573
+ // return $this->fetch('GET', self::API_URL, $call, $params);
574
+ // }
575
+
576
+ /**
577
+ * Not used
578
+ * Creates and returns a shareable link to files or folders
579
+ * The link returned is for a preview page from which the user an choose to
580
+ * download the file if they wish. For direct download links, see media().
581
+ * @param string $path The path to the file/folder you want a sharable link to
582
+ * @return object stdClass
583
+ */
584
+ // public function shares($path, $shortUrl = true) {
585
+ // $call = 'shares/' . $this->root . '/' . $this->encodePath($path);
586
+ // $params = array('short_url' => ($shortUrl) ? 1 : 0);
587
+
588
+ // return $this->fetch('POST', self::API_URL, $call, $params);
589
+ // }
590
+
591
+ /**
592
+ * Not used
593
+ * Returns a link directly to a file
594
+ * @param string $path The path to the media file you want a direct link to
595
+ * @return object stdClass
596
+ */
597
+ // public function media($path) {
598
+ // $call = 'media/' . $this->root . '/' . $this->encodePath($path);
599
+
600
+ // return $this->fetch('POST', self::API_URL, $call);
601
+ // }
602
+
603
+ /**
604
+ * Not used
605
+ * Gets a thumbnail for an image
606
+ * @param string $file The path to the image you wish to thumbnail
607
+ * @param string $format The thumbnail format, either JPEG or PNG
608
+ * @param string $size The size of the thumbnail
609
+ * @return array
610
+ */
611
+ // public function thumbnails($file, $format = 'JPEG', $size = 'small') {
612
+ // $format = strtoupper($format);
613
+ // // If $format is not 'PNG', default to 'JPEG'
614
+ // if ($format != 'PNG') {
615
+ // $format = 'JPEG';
616
+ // }
617
+
618
+ // $size = strtolower($size);
619
+ // $sizes = array('s', 'm', 'l', 'xl', 'small', 'medium', 'large');
620
+ // // If $size is not valid, default to 'small'
621
+ // if (!in_array($size, $sizes)) {
622
+ // $size = 'small';
623
+ // }
624
+
625
+ // $call = 'thumbnails/' . $this->root . '/' . $this->encodePath($file);
626
+ // $params = array('format' => $format, 'size' => $size);
627
+ // $response = $this->fetch('GET', self::CONTENT_URL, $call, $params);
628
+
629
+ // return array(
630
+ // 'name' => basename($file),
631
+ // 'mime' => $this->getMimeType($response['body']),
632
+ // 'meta' => json_decode($response['headers']['x-dropbox-metadata']),
633
+ // 'data' => $response['body'],
634
+ // );
635
+ // }
636
+
637
+ /**
638
+ * Not used
639
+ * Creates and returns a copy_ref to a file
640
+ * This reference string can be used to copy that file to another user's
641
+ * Dropbox by passing it in as the from_copy_ref parameter on /fileops/copy
642
+ * @param $path File for which ref should be created, relative to root
643
+ * @return array
644
+ */
645
+ // public function copyRef($path) {
646
+ // $call = 'copy_ref/' . $this->root . '/' . $this->encodePath($path);
647
+
648
+ // return $this->fetch('GET', self::API_URL, $call);
649
+ // }
650
+
651
+ /**
652
+ * Not used
653
+ * Copies a file or folder to a new location
654
+ * @param string $from File or folder to be copied, relative to root
655
+ * @param string $to Destination path, relative to root
656
+ * @param null|string $fromCopyRef Must be used instead of the from_path
657
+ * @return object stdClass
658
+ */
659
+ // public function copy($from, $to, $fromCopyRef = null) {
660
+ // $call = 'fileops/copy';
661
+ // $params = array(
662
+ // 'root' => $this->root,
663
+ // 'from_path' => $this->normalisePath($from),
664
+ // 'to_path' => $this->normalisePath($to),
665
+ // );
666
+
667
+ // if ($fromCopyRef) {
668
+ // $params['from_path'] = null;
669
+ // $params['from_copy_ref'] = $fromCopyRef;
670
+ // }
671
+
672
+ // return $this->fetch('POST', self::API_URL, $call, $params);
673
+ // }
674
+
675
+ /**
676
+ * Not used
677
+ * Creates a folder
678
+ * @param string New folder to create relative to root
679
+ * @return object stdClass
680
+ */
681
+ // public function create($path) {
682
+ // $call = 'fileops/create_folder';
683
+ // $params = array('root' => $this->root, 'path' => $this->normalisePath($path));
684
+
685
+ // return $this->fetch('POST', self::API_URL, $call, $params);
686
+ // }
687
+
688
+ /**
689
+ * Not used
690
+ * Deletes a file or folder
691
+ * @param string $path The path to the file or folder to be deleted
692
+ * @return object stdClass
693
+ */
694
+ // public function delete($path) {
695
+ // $call = '2/files/delete';
696
+ // $params = array('path' => '/' . $this->normalisePath($path), 'api_v2' => true);
697
+ // $response = $this->fetch('POST', self::API_URL_V2, $call, $params);
698
+ // return $response;
699
+ // }
700
+
701
+ /**
702
+ * Not used
703
+ * Moves a file or folder to a new location
704
+ * @param string $from File or folder to be moved, relative to root
705
+ * @param string $to Destination path, relative to root
706
+ * @return object stdClass
707
+ */
708
+ // public function move($from, $to) {
709
+ // $call = 'fileops/move';
710
+ // $params = array(
711
+ // 'root' => $this->root,
712
+ // 'from_path' => $this->normalisePath($from),
713
+ // 'to_path' => $this->normalisePath($to),
714
+ // );
715
+
716
+ // return $this->fetch('POST', self::API_URL, $call, $params);
717
+ // }
718
+
719
+ /**
720
+ * Intermediate fetch function
721
+ * @param string $method The HTTP method
722
+ * @param string $url The API endpoint
723
+ * @param string $call The API method to call
724
+ * @param array $params Additional parameters
725
+ * @return mixed
726
+ */
727
+ private function fetch($method, $url, $call, array $params = array(), $isChunkDownload = array())
728
+ {
729
+ // Make the API call via the consumer
730
+ $response = $this->OAuth->fetch($method, $url, $call, $params, $isChunkDownload);
731
+
732
+ // Format the response and return
733
+ switch ($this->responseFormat) {
734
+ case 'json':
735
+ return json_encode($response);
736
+ case 'jsonp':
737
+ $response = json_encode($response);
738
+ return $this->callback . '(' . $response . ')';
739
+ default:
740
+ return $response;
741
+ }
742
+ }
743
+
744
+
745
+ /**
746
+ * Set the chunk size for chunked uploads
747
+ * If $chunkSize is empty, set to 4194304 bytes (4 MB)
748
+ * @see \Dropbox\API\chunkedUpload()
749
+ */
750
+ public function setChunkSize($chunkSize = 4194304) {
751
+ if (!is_int($chunkSize)) {
752
+ throw new Exception('Expecting chunk size to be an integer, got ' . gettype($chunkSize));
753
+ } elseif ($chunkSize > 157286400) {
754
+ throw new Exception('Chunk size must not exceed 157286400 bytes, got ' . $chunkSize);
755
+ } else {
756
+ $this->chunkSize = $chunkSize;
757
+ }
758
+ }
759
+
760
+ /**
761
+ * Get the mime type of downloaded file
762
+ * If the Fileinfo extension is not loaded, return false
763
+ * @param string $data File contents as a string or filename
764
+ * @param string $isFilename Is $data a filename?
765
+ * @return boolean|string Mime type and encoding of the file
766
+ */
767
+ private function getMimeType($data, $isFilename = false) {
768
+ if (extension_loaded('fileinfo')) {
769
+ $finfo = new finfo(FILEINFO_MIME);
770
+ if ($isFilename !== false) {
771
+ return @$finfo->file($data);
772
+ }
773
+
774
+ return $finfo->buffer($data);
775
+ }
776
+
777
+ return false;
778
+ }
779
+
780
+ /**
781
+ * Trim the path of forward slashes and replace
782
+ * consecutive forward slashes with a single slash
783
+ * then replace backslashes with forward slashes
784
+ * @param string $path The path to normalise
785
+ * @return string
786
+ */
787
+ private function normalisePath($path) {
788
+ $path = preg_replace('#/+#', '/', trim($path, '/'));
789
+ return $path;
790
+ }
791
+
792
+ /**
793
+ * Encode the path, then replace encoded slashes
794
+ * with literal forward slash characters
795
+ * @param string $path The path to encode
796
+ * @return string
797
+ */
798
+ private function encodePath($path) {
799
+ // 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.
800
+ return $this->normalisePath($path);
801
+ }
802
+ }
lib/Dropbox/Exception.php ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Dropbox Exception class
5
+ * @author Ben Tadiar <ben@handcraftedbyben.co.uk>
6
+ * @link https://github.com/benthedesigner/dropbox
7
+ * @package Dropbox
8
+ */
9
+ class IWP_Dropbox_Exception extends Exception {
10
+ }
11
+
12
+ class IWP_Dropbox_BadRequestException extends Exception {
13
+ }
14
+
15
+ class IWP_Dropbox_CurlException extends Exception {
16
+ }
17
+
18
+ class IWP_Dropbox_NotAcceptableException extends Exception {
19
+ }
20
+
21
+ class IWP_Dropbox_NotFoundException extends Exception {
22
+ }
23
+
24
+ class IWP_Dropbox_NotModifiedException extends Exception {
25
+ }
26
+
27
+ class IWP_Dropbox_UnsupportedMediaTypeException extends Exception {
28
+ }
29
+
30
+ class IWP_Dropbox_TokenExpired extends Exception {
31
+ }
lib/Dropbox/OAuth/Consumer/ConsumerAbstract.php ADDED
@@ -0,0 +1,371 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Abstract OAuth consumer
5
+ * @author Ben Tadiar <ben@handcraftedbyben.co.uk>
6
+ * @link https://github.com/benthedesigner/dropbox
7
+ * @package Dropbox\OAuth
8
+ * @subpackage Consumer
9
+ */
10
+
11
+ abstract class IWP_Dropbox_OAuth_Consumer_ConsumerAbstract
12
+ {
13
+ // Dropbox web endpoint
14
+ const WEB_URL = 'https://www.dropbox.com/';
15
+
16
+ // OAuth flow methods
17
+ const REQUEST_TOKEN_METHOD = 'oauth2/REQUEST_TOKEN_METHOD';
18
+ const AUTHORISE_METHOD = 'oauth2/authorize';
19
+ const ACCESS_TOKEN_METHOD = 'oauth2/token';
20
+ const API_URL = 'https://api.dropbox.com/1/';
21
+ const OAUTH_UPGRADE = 'oauth2/token_from_oauth1';
22
+
23
+ /**
24
+ * Signature method, either PLAINTEXT or HMAC-SHA1
25
+ * @var string
26
+ */
27
+ private $sigMethod = 'PLAINTEXT';
28
+
29
+ /**
30
+ * Output file handle
31
+ * @var null|resource
32
+ */
33
+ protected $outFile = null;
34
+
35
+ /**
36
+ * Input file handle
37
+ * @var null|resource
38
+ */
39
+ protected $inFile = null;
40
+
41
+ /**
42
+ * OAuth token
43
+ * @var stdclass
44
+ */
45
+ private $token = null;
46
+
47
+ /**
48
+ * Acquire an unauthorised request token
49
+ * @link http://tools.ietf.org/html/rfc5849#section-2.1
50
+ * @return void
51
+ */
52
+
53
+ protected function authenticate()
54
+ {
55
+ global $iwp_backup_core;
56
+
57
+ $access_token = $this->storage->get('access_token');
58
+ //Check if the new token type is set if not they need to be upgraded to OAuth2
59
+ if (!empty($access_token) && isset($access_token->oauth_token) && !isset($access_token->token_type)) {
60
+ $iwp_backup_core->log('OAuth v1 token found: upgrading to v2');
61
+ $this->upgradeOAuth();
62
+ $iwp_backup_core->log('OAuth token upgrade successful');
63
+ }
64
+
65
+ if (empty($access_token) || !isset($access_token->oauth_token)) {
66
+ try {
67
+ $this->getAccessToken();
68
+ } catch(Exception $e) {
69
+ $excep_class = get_class($e);
70
+ // 04-Sep-2015 - Dropbox started throwing a 400, which caused a Dropbox_BadRequestException which previously wasn't being caught
71
+ if ('Dropbox_BadRequestException' == $excep_class || 'Dropbox_Exception' == $excep_class) {
72
+ global $iwp_backup_core;
73
+ $iwp_backup_core->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)');
74
+ //$this->getRequestToken();
75
+ $this->authorise();
76
+ } else {
77
+ throw $e;
78
+ }
79
+ }
80
+ }
81
+ }
82
+
83
+ public function getRequestToken()
84
+ {
85
+ $url = WPTC_Dropbox_API::API_URL_V2 . self::REQUEST_TOKEN_METHOD;
86
+ $response = $this->fetch('POST', $url, '');
87
+
88
+ return $this->parseTokenString($response['body']);
89
+ }
90
+
91
+ /**
92
+ * Build the user authorisation URL
93
+ * @return string
94
+ */
95
+ public function getAuthoriseUrl()
96
+ {
97
+ $params = array(
98
+ 'client_id' => WPTC_DROPBOX_CLIENT_ID,
99
+ 'response_type' => 'code',
100
+ 'redirect_uri' => WPTC_DROPBOX_REDIRECT_URL,
101
+ 'state' => WPTC_DROPBOX_WP_REDIRECT_URL,
102
+ );
103
+
104
+
105
+ // Build the URL and redirect the user
106
+ $query = '?' . http_build_query($params, '', '&');
107
+ $url = self::WEB_URL . self::AUTHORISE_METHOD . $query;
108
+
109
+ return $url;
110
+ }
111
+
112
+ public function upgradeOAuth()
113
+ {
114
+ // 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)
115
+
116
+ $url = self::API_URL . self::OAUTH_UPGRADE;
117
+ $config = WPTC_Factory::get('config');
118
+ $this->token = new stdClass();
119
+ $this->token->oauth_token = $config->get_option('access_token');
120
+ $this->token->oauth_token_secret = $config->get_option('access_token_secret');
121
+ $response = $this->fetch('POST', $url, '');
122
+ return $response['body'];
123
+ }
124
+
125
+ /**
126
+ * Acquire an access token
127
+ * Tokens acquired at this point should be stored to
128
+ * prevent having to request new tokens for each API call
129
+ * @link http://tools.ietf.org/html/rfc5849#section-2.3
130
+ */
131
+ public function getAccessToken()
132
+ {
133
+ // Get the signed request URL
134
+ $response = $this->fetch('POST', WPTC_Dropbox_API::API_URL_V2, self::ACCESS_TOKEN_METHOD);
135
+ return $this->parseTokenString($response['body']);
136
+ }
137
+
138
+ /**
139
+ * Generate signed request URL
140
+ * See inline comments for description
141
+ * @link http://tools.ietf.org/html/rfc5849#section-3.4
142
+ * @param string $method HTTP request method
143
+ * @param string $url API endpoint to send the request to
144
+ * @param string $call API call to send
145
+ * @param array $additional Additional parameters as an associative array
146
+ * @return array
147
+ */
148
+ protected function getSignedRequest($method, $url, $call, array $additional = array())
149
+ {
150
+ // Get the request/access token
151
+ $token = $this->getToken();
152
+ // Prepare the standard request parameters differently for OAuth1 and OAuth2; we still need OAuth1 to make the request to the upgrade token endpoint
153
+ if (!empty($token)) {
154
+ $params = array(
155
+ 'access_token' => $token,
156
+ );
157
+
158
+ /*
159
+ 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.
160
+ */
161
+
162
+ if (isset($additional['api_v2']) && $additional['api_v2'] == true) {
163
+ unset($additional['api_v2']);
164
+ if (isset($additional['content_download']) && $additional['content_download'] == true) {
165
+ unset($additional['content_download']);
166
+ $headers = array(
167
+ 'Authorization: Bearer '.$params['access_token'],
168
+ 'Content-Type:',
169
+ 'Dropbox-API-Arg: '.json_encode($additional),
170
+ );
171
+ $additional = '';
172
+ } else if (isset($additional['content_upload']) && $additional['content_upload'] == true) {
173
+ unset($additional['content_upload']);
174
+ $headers = array(
175
+ 'Authorization: Bearer '.$params['access_token'],
176
+ 'Content-Type: application/octet-stream',
177
+ 'Dropbox-API-Arg: '.json_encode($additional),
178
+ );
179
+ $additional = '';
180
+ } else {
181
+ $headers = array(
182
+ 'Authorization: Bearer '.$params['access_token'],
183
+ 'Content-Type: application/json',
184
+ );
185
+ }
186
+ return array(
187
+ 'url' => $url . $call,
188
+ 'postfields' => $additional,
189
+ 'headers' => $headers,
190
+ );
191
+ }
192
+ } else {
193
+
194
+ // Generate a random string for the request
195
+ $nonce = md5(microtime(true) . uniqid('', true));
196
+ $params = array(
197
+ 'oauth_consumer_key' => WPTC_DROPBOX_CLIENT_ID,
198
+ 'oauth_token' => $this->token->oauth_token,
199
+ 'oauth_signature_method' => $this->sigMethod,
200
+ 'oauth_version' => '1.0',
201
+ // Generate nonce and timestamp if signature method is HMAC-SHA1
202
+ 'oauth_timestamp' => ($this->sigMethod == 'HMAC-SHA1') ? time() : null,
203
+ 'oauth_nonce' => ($this->sigMethod == 'HMAC-SHA1') ? $nonce : null,
204
+ );
205
+ }
206
+
207
+ // Merge with the additional request parameters
208
+ $params = array_merge($params, $additional);
209
+ ksort($params);
210
+
211
+ // URL encode each parameter to RFC3986 for use in the base string
212
+ $encoded = array();
213
+ foreach($params as $param => $value) {
214
+ if ($value !== null) {
215
+ // If the value is a file upload (prefixed with @), replace it with
216
+ // the destination filename, the file path will be sent in POSTFIELDS
217
+ if (isset($value[0]) && $value[0] === '@') $value = $params['filename'];
218
+ # Prevent spurious PHP warning by only doing non-arrays
219
+ if (!is_array($value)) $encoded[] = $this->encode($param) . '=' . $this->encode($value);
220
+ } else {
221
+ unset($params[$param]);
222
+ }
223
+ }
224
+
225
+ // Build the first part of the string
226
+ $base = $method . '&' . $this->encode($url . $call) . '&';
227
+
228
+ // Re-encode the encoded parameter string and append to $base
229
+ $base .= $this->encode(implode('&', $encoded));
230
+
231
+ // Concatenate the secrets with an ampersand
232
+ $key = WPTC_DROPBOX_CLIENT_SECRET . '&' . $this->token->oauth_token_secret;
233
+
234
+ // Get the signature string based on signature method
235
+ $signature = $this->getSignature($base, $key);
236
+ $params['oauth_signature'] = $signature;
237
+
238
+ // Build the signed request URL
239
+ $query = '?' . http_build_query($params, '', '&');
240
+ return array(
241
+ 'url' => $url . $call . $query,
242
+ 'postfields' => $params,
243
+ );
244
+ }
245
+
246
+ /**
247
+ * Generate the oauth_signature for a request
248
+ * @param string $base Signature base string, used by HMAC-SHA1
249
+ * @param string $key Concatenated consumer and token secrets
250
+ */
251
+ private function getSignature($base, $key)
252
+ {
253
+ switch ($this->sigMethod) {
254
+ case 'PLAINTEXT':
255
+ $signature = $key;
256
+ break;
257
+ case 'HMAC-SHA1':
258
+ $signature = base64_encode(hash_hmac('sha1', $base, $key, true));
259
+ break;
260
+ }
261
+
262
+ return $signature;
263
+ }
264
+
265
+ /**
266
+ * Set the token to use for OAuth requests
267
+ * @param stdtclass $token A key secret pair
268
+ */
269
+ public function setToken($token)
270
+ {
271
+
272
+ $this->token = $token;
273
+
274
+ return $this;
275
+ }
276
+
277
+ public function getToken()
278
+ {
279
+ return $this->token;
280
+ }
281
+
282
+ public function resetToken()
283
+ {
284
+ $token = new stdClass;
285
+ $token->oauth_token = false;
286
+ $token->oauth_token_secret = false;
287
+
288
+ $this->setToken($token);
289
+
290
+ return $this;
291
+ }
292
+
293
+ /**
294
+ * Set the OAuth signature method
295
+ * @param string $method Either PLAINTEXT or HMAC-SHA1
296
+ * @return void
297
+ */
298
+ public function setSignatureMethod($method)
299
+ {
300
+ $method = strtoupper($method);
301
+
302
+ switch ($method) {
303
+ case 'PLAINTEXT':
304
+ case 'HMAC-SHA1':
305
+ $this->sigMethod = $method;
306
+ break;
307
+ default:
308
+ throw new Exception('Unsupported signature method ' . $method);
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Set the output file
314
+ * @param resource Resource to stream response data to
315
+ * @return void
316
+ */
317
+ public function setOutFile($handle)
318
+ {
319
+ if (!is_resource($handle) || get_resource_type($handle) != 'stream') {
320
+ throw new Exception('Outfile must be a stream resource');
321
+ }
322
+ $this->outFile = $handle;
323
+ }
324
+
325
+ /**
326
+ * Set the input file
327
+ * @param resource Resource to read data from
328
+ * @return void
329
+ */
330
+ public function setInFile($handle)
331
+ {
332
+ $this->inFile = $handle;
333
+ }
334
+
335
+ /**
336
+ * Parse response parameters for a token into an object
337
+ * Dropbox returns tokens in the response parameters, and
338
+ * not a JSON encoded object as per other API requests
339
+ * @link http://oauth.net/core/1.0/#response_parameters
340
+ * @param string $response
341
+ * @return object stdClass
342
+ */
343
+ private function parseTokenString($response)
344
+ {
345
+ if (!$response)
346
+ throw new Exception('Response cannot be null');
347
+
348
+ $parts = explode('&', $response);
349
+ $token = new stdClass();
350
+ foreach ($parts as $part) {
351
+ list($k, $v) = explode('=', $part, 2);
352
+ $k = strtolower($k);
353
+ $token->$k = $v;
354
+ }
355
+
356
+ return $token;
357
+ }
358
+
359
+ /**
360
+ * Encode a value to RFC3986
361
+ * This is a convenience method to decode ~ symbols encoded
362
+ * by rawurldecode. This will encode all characters except
363
+ * the unreserved set, ALPHA, DIGIT, '-', '.', '_', '~'
364
+ * @link http://tools.ietf.org/html/rfc5849#section-3.6
365
+ * @param mixed $value
366
+ */
367
+ private function encode($value)
368
+ {
369
+ return str_replace('%7E', '~', rawurlencode($value));
370
+ }
371
+ }
lib/Dropbox/OAuth/Consumer/Curl.php ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * OAuth consumer using PHP cURL
5
+ * @author Ben Tadiar <ben@handcraftedbyben.co.uk>
6
+ * @link https://github.com/benthedesigner/dropbox
7
+ * @package Dropbox\OAuth
8
+ * @subpackage Consumer
9
+ */
10
+ error_reporting(0);
11
+ class IWP_Dropbox_OAuth_Consumer_Curl extends IWP_Dropbox_OAuth_Consumer_ConsumerAbstract {
12
+
13
+ /**
14
+ * Default cURL options
15
+ * @var array
16
+ */
17
+
18
+ protected $defaultOptions = array(
19
+ CURLOPT_SSL_VERIFYPEER => true,
20
+ CURLOPT_VERBOSE => true,
21
+ CURLOPT_HEADER => true,
22
+ CURLINFO_HEADER_OUT => false,
23
+ CURLOPT_RETURNTRANSFER => true,
24
+ CURLOPT_FOLLOWLOCATION => false,
25
+ );
26
+
27
+ /**
28
+ * Store the last response form the API
29
+ * @var mixed
30
+ */
31
+ protected $lastResponse = null;
32
+
33
+ /**
34
+ * Set properties and begin authentication
35
+ * @param string $key
36
+ * @param string $secret
37
+ */
38
+ public function __construct($key, $secret) {
39
+ // Check the cURL extension is loaded
40
+ if (!extension_loaded('curl')) {
41
+ throw new Exception('The cURL OAuth consumer requires the cURL extension');
42
+ }
43
+
44
+ $this->consumerKey = $key;
45
+ $this->consumerSecret = $secret;
46
+ }
47
+
48
+ /**
49
+ * Execute an API call
50
+ * @todo Improve error handling
51
+ * @param string $method The HTTP method
52
+ * @param string $url The API endpoint
53
+ * @param string $call The API method to call
54
+ * @param array $additional Additional parameters
55
+ * @return string|object stdClass
56
+ */
57
+
58
+
59
+ public function fetch($method, $url, $call, array $additional = array(), $isChunkDownload = array())
60
+ {
61
+ // Get the signed request URL
62
+ $request = $this->getSignedRequest($method, $url, $call, $additional);
63
+ if ($request === false) {
64
+ throw new Exception("Upgrade failed", 401);
65
+ }
66
+ // Initialise and execute a cURL request
67
+ $handle = curl_init($request['url']);
68
+
69
+ // Get the default options array
70
+ $options = $this->defaultOptions;
71
+ $options[CURLOPT_CAINFO] = dirname(__FILE__) . '/ca-bundle.pem';
72
+
73
+ //Disabling this as of now
74
+
75
+ if (!defined('WPTC_BRIDGE')) {
76
+ if (!class_exists('WP_HTTP_Proxy')){
77
+ if (!defined('WPTC_BRIDGE')) {
78
+ require_once(ABSPATH.WPINC.'/class-http.php');
79
+ } else {
80
+ throw new Exception("WP_HTTP_Proxy Class not foound", 500);
81
+ }
82
+ }
83
+ $proxy = new WP_HTTP_Proxy();
84
+
85
+ if ($proxy->is_enabled()) {
86
+ # WP_HTTP_Proxy returns empty strings if nothing is set
87
+ $user = $proxy->username();
88
+ $pass = $proxy->password();
89
+ $host = $proxy->host();
90
+ $port = (int)$proxy->port();
91
+ if (empty($port)) $port = 8080;
92
+ if (!empty($host) && $proxy->send_through_proxy($request['url'])) {
93
+ $options[CURLOPT_PROXY] = $host;
94
+ $options[CURLOPT_PROXYTYPE] = CURLPROXY_HTTP;
95
+ $options[CURLOPT_PROXYPORT] = $port;
96
+ if (!empty($user) && !empty($pass)) {
97
+ $options[CURLOPT_PROXYAUTH] = CURLAUTH_ANY;
98
+ $options[CURLOPT_PROXYUSERPWD] = sprintf('%s:%s', $user, $pass);
99
+ }
100
+ }
101
+ }
102
+ }
103
+ if (isset($request['headers'])) $options[CURLOPT_HTTPHEADER] = $request['headers'];
104
+
105
+ /*
106
+ 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.
107
+ */
108
+ if (isset($additional['api_v2']) && !empty($request['postfields'])) {
109
+ $request['postfields'] = json_encode($request['postfields']);
110
+ }
111
+
112
+ if ($method == 'GET' && $this->outFile) { // GET
113
+ $options[CURLOPT_RETURNTRANSFER] = false;
114
+ $options[CURLOPT_HEADER] = false;
115
+ $options[CURLOPT_FILE] = $this->outFile;
116
+ $options[CURLOPT_BINARYTRANSFER] = true;
117
+ $options[CURLOPT_FAILONERROR] = true;
118
+ /*
119
+ Not sure if this is used, keeping it here for backwards compatibility at the moment.
120
+ With API v2 the headers are set in the $request they are set above if they are set.
121
+ */
122
+ if (isset($additional['headers'])) $options[CURLOPT_HTTPHEADER] = $additional['headers'];
123
+ $this->outFile = null;
124
+ } elseif ($method == 'POST' && $this->outFile) { // POST request for download a file
125
+ $options[CURLOPT_POST] = true;
126
+ $options[CURLOPT_RETURNTRANSFER] = false;
127
+ $options[CURLOPT_HEADER] = false;
128
+ $options[CURLOPT_FILE] = $this->outFile;
129
+ $options[CURLOPT_BINARYTRANSFER] = true;
130
+ $options[CURLOPT_FAILONERROR] = true;
131
+ $this->outFile = null;
132
+ } elseif ($method == 'POST' && $this->inFile) { // POST request for upload a file
133
+ $options[CURLOPT_POST] = true;
134
+ $options[CURLOPT_POSTFIELDS] = $this->inFile;
135
+ } elseif ($method == 'POST') { // POST request
136
+ $options[CURLOPT_POST] = true;
137
+ $options[CURLOPT_POSTFIELDS] = $request['postfields'];
138
+ } elseif ($method == 'PUT' && $this->inFile) { // PUT request
139
+ $options[CURLOPT_PUT] = true;
140
+ $options[CURLOPT_INFILE] = $this->inFile;
141
+ // @todo Update so the data is not loaded into memory to get its size
142
+ $options[CURLOPT_INFILESIZE] = strlen(stream_get_contents($this->inFile));
143
+ fseek($this->inFile, 0);
144
+ $this->inFile = null;
145
+ }
146
+
147
+
148
+ // Set the cURL options at once
149
+ curl_setopt_array($handle, $options);
150
+
151
+ // Execute, get any error and close
152
+ $response = curl_exec($handle);
153
+ $error = curl_error($handle);
154
+ $getinfo = curl_getinfo($handle);
155
+ curl_close($handle);
156
+
157
+ //Check if a cURL error has occured
158
+ if ($response === false) {
159
+ throw new IWP_Dropbox_CurlException($error);
160
+ } else {
161
+ // Parse the response if it is a string
162
+ if (is_string($response)) {
163
+ $response = $this->parse($response);
164
+ }
165
+
166
+ // Set the last response
167
+ $this->lastResponse = $response;
168
+
169
+ $code = (!empty($response['code'])) ? $response['code'] : $getinfo['http_code'];
170
+
171
+ // The API doesn't return an error message for the 304 status code...
172
+ // 304's are only returned when the path supplied during metadata calls has not been modified
173
+ if ($code == 304) {
174
+ $response['body'] = new stdClass;
175
+ $response['body']->error = 'The folder contents have not changed';
176
+ }
177
+
178
+ // Check if an error occurred and throw an Exception
179
+ if (!empty($response['body']->error) || $code >= 400) {
180
+ // Dropbox returns error messages inconsistently...
181
+ if (!empty($response['body']->error) && $response['body']->error instanceof stdClass) {
182
+ $array = array_values((array) $response['body']->error);
183
+ //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.
184
+ if (strpos($array[0] , 'incorrect_offset') !== false) {
185
+ $message = json_encode($array);
186
+ } elseif (strpos($array[0] , 'lookup_failed') !== false ) {
187
+ //re-structure the array so it is correctly formatted for API
188
+ //Note: Dropbox v2 returns different errors at different stages hence this fix
189
+ $correctOffset = array(
190
+ '0' => $array[1]->{'.tag'},
191
+ '1' => $array[1]->correct_offset
192
+ );
193
+
194
+ $message = json_encode($correctOffset);
195
+ } else {
196
+ $message = $array[0];
197
+ }
198
+ } elseif (!empty($response['body']->error)) {
199
+ $message = $response['body']->error;
200
+ } elseif (is_string($response['body'])) {
201
+ // 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
202
+ $message = $response['body'];
203
+ } else {
204
+ $message = "HTTP bad response code: $code";
205
+ }
206
+
207
+ // Throw an Exception with the appropriate with the appropriate message and code
208
+ switch ($code) {
209
+ case 304:
210
+ throw new IWP_Dropbox_NotModifiedException($message, 304);
211
+ case 400:
212
+ throw new IWP_Dropbox_BadRequestException($message, 400);
213
+ case 404:
214
+ throw new IWP_Dropbox_NotFoundException($message, 404);
215
+ case 406:
216
+ throw new IWP_Dropbox_NotAcceptableException($message, 406);
217
+ case 415:
218
+ throw new IWP_Dropbox_UnsupportedMediaTypeException($message, 415);
219
+ case 401:
220
+ //401 means oauth token is expired continue to manually handle the exception depending on the situation
221
+ throw new IWP_Dropbox_TokenExpired($message, 401);
222
+ // continue;
223
+ case 409:
224
+ //409 in API V2 every error will return with a 409 to find out what the error is the error description should be checked.
225
+ throw new IWP_Dropbox_Exception($message, $code);
226
+ default:
227
+ throw new IWP_Dropbox_Exception($message, $code);
228
+ }
229
+ }
230
+
231
+ return $response;
232
+ }
233
+ }
234
+ /**
235
+ * Parse a cURL response
236
+ * @param string $response
237
+ * @return array
238
+ */
239
+ private function parse($response)
240
+ {
241
+ // Explode the response into headers and body parts (separated by double EOL)
242
+ list($headers, $response) = explode("\r\n\r\n", $response, 2);
243
+
244
+ // Explode response headers
245
+ $lines = explode("\r\n", $headers);
246
+
247
+ // If the status code is 100, the API server must send a final response
248
+ // We need to explode the response again to get the actual response
249
+ if (preg_match('#^HTTP/1.1 100#i', $lines[0])) {
250
+ list($headers, $response) = explode("\r\n\r\n", $response, 2);
251
+ $lines = explode("\r\n", $headers);
252
+ }
253
+
254
+ // Get the HTTP response code from the first line
255
+ $first = array_shift($lines);
256
+ $pattern = '#^HTTP/1.1 ([0-9]{3})#i';
257
+ preg_match($pattern, $first, $matches);
258
+ $code = $matches[1];
259
+
260
+ // Parse the remaining headers into an associative array
261
+ $headers = array();
262
+ foreach ($lines as $line) {
263
+ list($k, $v) = explode(': ', $line, 2);
264
+ $headers[strtolower($k)] = $v;
265
+ }
266
+
267
+ // If the response body is not a JSON encoded string
268
+ // we'll return the entire response body
269
+ if (!$body = json_decode($response)) {
270
+ $body = $response;
271
+ }
272
+
273
+ if (is_string($body)) {
274
+ $body_lines = explode("\r\n", $body);
275
+ if (preg_match('#^HTTP/1.1 100#i', $body_lines[0]) && preg_match('#^HTTP/1.#i', $body_lines[2])) {
276
+ return $this->parse($body);
277
+ }
278
+ }
279
+
280
+ return array('code' => $code, 'body' => $body, 'headers' => $headers);
281
+ }
282
+
283
+ /**
284
+ * Return the response for the last API request
285
+ * @return mixed
286
+ */
287
+ public function getlastResponse() {
288
+ return $this->lastResponse;
289
+ }
290
+ }
lib/{Dropbox2 → Dropbox}/OAuth/Consumer/ca-bundle.pem RENAMED
File without changes
lib/Dropbox2/API.php CHANGED
@@ -1,801 +1,661 @@
1
- <?php
2
-
3
- /**
4
- * Dropbox API base class
5
- * @author Ben Tadiar <ben@handcraftedbyben.co.uk>
6
- * @link https://github.com/benthedesigner/dropbox
7
- * @link https://www.dropbox.com/developers
8
- * @link https://status.dropbox.com Dropbox status
9
- * @package Dropbox
10
- */
11
-
12
- class IWP_Dropbox_API {
13
- // API Endpoints
14
- const API_URL = 'https://api.dropbox.com/1/';
15
- const API_URL_V2 = 'https://api.dropboxapi.com/';
16
- const CONTENT_URL = 'https://api-content.dropbox.com/1/';
17
- const CONTENT_URL_V2 = 'https://content.dropboxapi.com/2/';
18
-
19
- /**
20
- * OAuth consumer object
21
- * @var null|OAuth\Consumer
22
- */
23
- private $OAuth;
24
-
25
- /**
26
- * The root level for file paths
27
- * Either `dropbox` or `sandbox` (preferred)
28
- * @var null|string
29
- */
30
- private $root;
31
-
32
- /**
33
- * Chunk size used for chunked uploads
34
- * @see \Dropbox_API::chunkedUpload()
35
- */
36
- private $chunkSize = 4194304;
37
-
38
- private $responseFormat = 'php';
39
-
40
- /**
41
- * Object to track uploads
42
- */
43
- private $tracker;
44
-
45
- private $base;
46
-
47
- /**
48
- * Set the OAuth consumer object
49
- * See 'General Notes' at the link below for information on access type
50
- * @link https://www.dropbox.com/developers/reference/api
51
- * @param OAuth\Consumer\ConsumerAbstract $OAuth
52
- * @param string $root Dropbox app access type
53
- */
54
- public function __construct($OAuth, $root = 'dropbox') {
55
- $this->OAuth = $OAuth;
56
- $this->setRoot($root);
57
- }
58
-
59
- /**
60
- * Set the root level
61
- * @param mixed $root
62
- * @throws Exception
63
- * @return void
64
- */
65
- public function setRoot($root) {
66
- if ($root !== 'sandbox' && $root !== 'dropbox') {
67
- throw new Exception("Expected a root of either 'dropbox' or 'sandbox', got '$root'");
68
- } else {
69
- $this->root = $root;
70
- }
71
- }
72
-
73
- /**
74
- * Set the tracker
75
- * @param Tracker $tracker
76
- */
77
- public function setTracker($tracker) {
78
- $this->tracker = $tracker;
79
- }
80
-
81
- /**
82
- * Retrieves information about the user's account
83
- * @return object stdClass
84
- */
85
- public function accountInfo() {
86
- //API V1
87
- // return $this->fetch('POST', self::API_URL, 'account/info');
88
-
89
- $call = '2/users/get_current_account';
90
- $params = array('api_v2' => true);
91
- $response = $this->fetch('POST', self::API_URL_V2, $call, $params);
92
- return $response;
93
- }
94
-
95
- /**
96
- * Retrieves information about the user's quota
97
- * @return object stdClass
98
- */
99
- public function quotaInfo() {
100
- $call = '2/users/get_space_usage';
101
- $params = array('api_v2' => true);
102
- $response = $this->fetch('POST', self::API_URL_V2, $call, $params);
103
- return $response;
104
- }
105
-
106
- /**
107
- * Uploads a physical file from disk
108
- * Dropbox impose a 150MB limit to files uploaded via the API. If the file
109
- * exceeds this limit or does not exist, an Exception will be thrown
110
- * @param string $file Absolute path to the file to be uploaded
111
- * @param string|bool $filename The destination filename of the uploaded file
112
- * @param string $path Path to upload the file to, relative to root
113
- * @param boolean $overwrite Should the file be overwritten? (Default: true)
114
- * @return object stdClass
115
- */
116
- public function putFile($file, $path = '', $overwrite = true) {
117
- if (!file_exists($file)) {
118
- // Throw an Exception if the file does not exist
119
- throw new Exception('Local file ' . $file . ' does not exist');
120
- }
121
- $filesize = iwp_mmb_get_file_size($file);
122
- if ($filesize >= 157286400) {
123
- $output = $this->chunked_upload_single_call_new($file, $path,$overwrite);
124
- return $output;
125
-
126
- }else{
127
- $handle = @fopen($file, 'r');
128
- //Set the file content to $this->InFile
129
- $this->OAuth->setInFile(fread($handle, filesize($file)));
130
- fclose($handle);
131
-
132
- $filename = (is_string($filename)) ? $filename : basename($file);
133
- $path = '/' . $this->encodePath($path .'/'. $filename);
134
- $params = array(
135
- 'path' => $path,
136
- 'mute' => true,
137
- 'mode' => 'overwrite',
138
- 'api_v2' => true,
139
- 'content_upload' => true
140
- );
141
- $response = $this->fetch('POST', self::CONTENT_URL_V2, 'files/upload', $params);
142
- return $response;
143
- }
144
-
145
- }
146
-
147
- public function chunked_upload_single_call_new($file, $path = '',$overwrite=true){
148
- $file = str_replace("\\", "/",$file);
149
- if (!is_readable($file) or !is_file($file))
150
- throw new IWP_DropboxException("Error: File \"$file\" is not readable or doesn't exist.");
151
- $file_handle=fopen($file,'r');
152
- $uploadID=null;
153
- $offset=0;
154
- $ProgressFunction=null;
155
- while ($data=fread($file_handle, (1024*1024*30))) { //1024*1024*30 = 30MB
156
- $firstCommit = (0 == $offset);
157
- iwp_mmb_auto_print('dropbox_chucked_upload');
158
- $this->OAuth->setInFile($data);
159
-
160
- if ($firstCommit) {
161
- $params = array(
162
- 'close' => false,
163
- 'api_v2' => true,
164
- 'content_upload' => true
165
- );
166
- $response = $this->fetch('POST', self::CONTENT_URL_V2, 'files/upload_session/start', $params);
167
- $firstCommit = false;
168
-
169
- } else {
170
- $params = array(
171
- 'cursor' => array(
172
- 'session_id' => $uploadID,
173
- // If you send it as a string, Dropbox will be unhappy
174
- 'offset' => (int)$offset
175
- ),
176
- 'api_v2' => true,
177
- 'content_upload' => true
178
- );
179
- $response = $this->append_upload($params, false);
180
- }
181
-
182
- // On subsequent chunks, use the upload ID returned by the previous request
183
- if (isset($response['body']->session_id)) {
184
- $uploadID = $response['body']->session_id;
185
- }
186
-
187
- /*
188
- 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.
189
- */
190
- if (!isset($response['body']->error)) {
191
- $offset = ftell($file_handle);
192
- $output['response']= $response;
193
- if($isCommit ==false){
194
-
195
- $output['offset']= $offset;
196
- $output['upload_id']= $uploadID;
197
- }
198
- $this->OAuth->setInFile(null);
199
- }
200
- fseek($file_handle, $offset);
201
- }
202
- fclose($file_handle);
203
- $filename = (is_string($filename)) ? $filename : basename($file);
204
- $params = array(
205
- 'cursor' => array(
206
- 'session_id' => $uploadID,
207
- 'offset' => (int)$offset
208
- ),
209
- 'commit' => array(
210
- 'path' => '/' . $this->encodePath($path .'/'. $filename),
211
- 'mode' => 'overwrite'
212
- ),
213
- 'api_v2' => true,
214
- 'content_upload' => true
215
- );
216
- $response = $this->append_upload($params, true);
217
-
218
- return $response;
219
- }
220
-
221
- /**
222
- * Not used
223
- * Uploads file data from a stream
224
- * Note: This function is experimental and requires further testing
225
- * @todo Add filesize check
226
- *@ param resource $stream A readable stream created using fopen()
227
- * @param string $filename The destination filename, including path
228
- * @param boolean $overwrite Should the file be overwritten? (Default: true)
229
- * @return array
230
- */
231
- // public function putStream($stream, $filename, $overwrite = true) {
232
- // $this->OAuth->setInFile($stream);
233
- // $path = $this->encodePath($filename);
234
- // $call = 'files_put/' . $this->root . '/' . $path;
235
- // $params = array('overwrite' => (int) $overwrite);
236
-
237
- // return $this->fetch('PUT', self::CONTENT_URL, $call, $params);
238
- // }
239
-
240
- /**
241
- * Uploads large files to Dropbox in mulitple chunks
242
- * @param string $file Absolute path to the file to be uploaded
243
- * @param string|bool $filename The destination filename of the uploaded file
244
- * @param string $path Path to upload the file to, relative to root
245
- * @param boolean $overwrite Should the file be overwritten? (Default: true)
246
- * @return stdClass
247
- */
248
- public function chunked_upload($file, $path = '', $overwrite = true, $uploadID = null, $offset = 0, $isCommit = false) {
249
- $starting_backup_path_time = time();
250
-
251
- $file = str_replace("\\", "/",$file);
252
- if (!file_exists($file)) throw new Exception('Local file ' . $file . ' does not exist');
253
-
254
- if (!($handle = @fopen($file, 'r'))) throw new Exception('Could not open ' . $file . ' for reading');
255
-
256
- // Seek to the correct position on the file pointer
257
- fseek($handle, $offset);
258
- $to_exit = false;
259
-
260
- //Set firstCommit to true so that the upload session start endpoint is called.
261
- $firstCommit = (0 == $offset);
262
-
263
- // Read from the file handle until EOF, uploading each chunk
264
- if ($data = fread($handle, $this->chunkSize)) {
265
-
266
- // Set the file, request parameters and send the request
267
- $this->OAuth->setInFile($data);
268
-
269
- if ($firstCommit) {
270
- $params = array(
271
- 'close' => false,
272
- 'api_v2' => true,
273
- 'content_upload' => true
274
- );
275
- $response = $this->fetch('POST', self::CONTENT_URL_V2, 'files/upload_session/start', $params);
276
- $firstCommit = false;
277
-
278
- } else {
279
- $params = array(
280
- 'cursor' => array(
281
- 'session_id' => $uploadID,
282
- // If you send it as a string, Dropbox will be unhappy
283
- 'offset' => (int)$offset
284
- ),
285
- 'api_v2' => true,
286
- 'content_upload' => true
287
- );
288
- $response = $this->append_upload($params, false);
289
- }
290
-
291
- // On subsequent chunks, use the upload ID returned by the previous request
292
- if (isset($response['body']->session_id)) {
293
- $uploadID = $response['body']->session_id;
294
- }
295
-
296
- /*
297
- 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.
298
- */
299
- if (!isset($response['body']->error)) {
300
- $offset = ftell($handle);
301
- $output['response']= $response;
302
- if($isCommit ==false){
303
-
304
- $output['offset']= $offset;
305
- $output['upload_id']= $uploadID;
306
- }
307
- $this->OAuth->setInFile(null);
308
- }
309
-
310
- }
311
- // Complete the chunked upload
312
- if ($isCommit) {
313
- $filename = (is_string($filename)) ? $filename : basename($file);
314
- $params = array(
315
- 'cursor' => array(
316
- 'session_id' => $uploadID,
317
- 'offset' => (int)$offset
318
- ),
319
- 'commit' => array(
320
- 'path' => '/' . $this->encodePath($path .'/'. $filename),
321
- 'mode' => 'overwrite'
322
- ),
323
- 'api_v2' => true,
324
- 'content_upload' => true
325
- );
326
- $response = $this->append_upload($params, true);
327
- $offset = ftell($handle);
328
- $output['response']= $response;
329
- }
330
-
331
- fclose($handle);
332
- return $output;
333
- }
334
-
335
- private function append_upload($params, $last_call) {
336
- try {
337
- if ($last_call){
338
- $response = $this->fetch('POST', self::CONTENT_URL_V2, 'files/upload_session/finish', $params);
339
- } else {
340
- $response = $this->fetch('POST', self::CONTENT_URL_V2, 'files/upload_session/append_v2', $params);
341
- }
342
- } catch (Exception $e) {
343
- $responseCheck = json_decode($e->getMessage());
344
- if (isset($responseCheck) && strpos($responseCheck[0] , 'incorrect_offset') !== false) {
345
- $expected_offset = $responseCheck[1];
346
- throw new Exception('Submitted input out of alignment: got ['.$params['cursor']['offset'].'] expected ['.$expected_offset.']');
347
- //$params['cursor']['offset'] = $responseCheck[1];
348
- //$response = $this->append_upload($params, $last_call);
349
- } else {
350
- throw $e;
351
- }
352
- }
353
- return $response;
354
- }
355
-
356
- /**
357
- * Downloads a file
358
- * Returns the base filename, raw file data and mime type returned by Fileinfo
359
- * @param string $file Path to file, relative to root, including path
360
- * @param string $outFile Filename to write the downloaded file to
361
- * @param string $revision The revision of the file to retrieve
362
- * @return array
363
- */
364
- public function getFile($file, $outFile = false, $revision = null, $allow_resume = array()) {
365
- $handle = null;
366
- // $tempFolder = $this->getTempFolderFromOutFile(wp_normalize_path($outFile));
367
- if ($outFile !== false) {
368
- // Create a file handle if $outFile is specified
369
- $this->prepareSetOutFile($outFile, 'w');
370
- }
371
-
372
- // $file = $this->encodePath($file);
373
- $call = 'files/download';
374
- $params = array('path' => '/'.$this->normalisePath($file), 'api_v2' => true, 'content_download' => true);
375
- $response = $this->fetch('GET', self::CONTENT_URL_V2, $call, $params);
376
- // Close the file handle if one was opened
377
- if ($handle) fclose($handle);
378
-
379
- return array(
380
- 'name' => ($outFile) ? $outFile : basename($file),
381
- 'mime' => $this->getMimeType(($outFile) ? $outFile : $response['body'], $outFile),
382
- 'meta' => json_decode($response['headers']['dropbox-api-result']),
383
- 'data' => $response['body'],
384
- );
385
- }
386
-
387
- public function prepareSetOutFile($outFile, $mode) {
388
- // $tempFolderFile = $this->getTempFolderFromOutFile(wp_normalize_path($outFile));
389
- $tempFolderFile = wp_normalize_path($outFile);
390
-
391
- //setting chmod from filesystem
392
-
393
- if (!$handle = @fopen($tempFolderFile, $mode)) {
394
- throw new Exception("Unable to open file handle for $tempFolderFile");
395
- } else {
396
- $this->OAuth->setOutFile($handle);
397
- return $handle;
398
- }
399
- }
400
-
401
- public function getTempFolderFromOutFile($outFile, $mode = '') {
402
- //this function creates the file and its respective folders ; this function also create the exact file path from the DB values
403
- $config = WPTC_Factory::get('config');
404
- $is_staging_running = $config->get_option('is_staging_running');
405
- if($is_staging_running){
406
- $site_abspath = $config->get_option('site_abspath');
407
- $this_absbath_length = (strlen($site_abspath) - 1);
408
- } else{
409
- $this_absbath_length = (strlen(ABSPATH) - 1);
410
- }
411
-
412
- $this_temp_file = $config->get_option('backup_db_path');
413
- $this_temp_file = $config->wp_filesystem_safe_abspath_replace($this_temp_file);
414
- $this_temp_file = $this_temp_file. '/tCapsule' . substr($outFile, $this_absbath_length);
415
-
416
- //get the folder name from the full file path
417
- $base_file_name = basename($this_temp_file);
418
- $base_file_name_pos = strrpos($this_temp_file, $base_file_name);
419
- $base_file_name_pos = $base_file_name_pos - 1;
420
- $this_temp_folder = substr($this_temp_file, 0, $base_file_name_pos);
421
-
422
- $this_temp_folder = $config->wp_filesystem_safe_abspath_replace($this_temp_folder);
423
-
424
- $this->base->createRecursiveFileSystemFolder($this_temp_folder);
425
- return $this_temp_file;
426
- }
427
-
428
- /**
429
- * Downloads a file
430
- * Returns the base filename, raw file data and mime type returned by Fileinfo
431
- * @param string $file Path to file, relative to root, including path
432
- * @param string $outFile Filename to write the downloaded file to
433
- * @param string $revision The revision of the file to retrieve
434
- * @return array
435
- */
436
- public function chunkedDownload($file, $outFile = false, $revision = null, $isChunkDownload = array(), $meta_file_download = null) {
437
- global $start_time_tc;
438
- $start_time_tc = time();
439
- $handle = null;
440
- if ($outFile !== false) {
441
- // Create a file handle if $outFile is specified
442
- if ($isChunkDownload['c_offset'] == 0) {
443
- //while restoring ... first
444
- $handle = $this->prepareSetOutFile($outFile, 'w');
445
- } else {
446
- $handle = $this->prepareSetOutFile($outFile, 'a');
447
- }
448
- }
449
-
450
- $outFilePath = wp_normalize_path($outFile);
451
- $call = 'files/download';
452
- $params = array('path' =>'/'.$file, 'api_v2' => true, 'content_download' => true);
453
- $response = $this->fetch('GET', self::CONTENT_URL_V2, $call, $params, $isChunkDownload);
454
-
455
- // Set the data offset
456
- if ($response) {
457
- $offset = filesize($outFilePath);
458
- }
459
-
460
- if (empty($meta_file_download)) {
461
- if ($this->tracker) {
462
- $this->tracker->track_download($outFile, false, $offset, $isChunkDownload);
463
- }
464
- } else {
465
- $this->tracker->track_meta_download($offset, $isChunkDownload);
466
- }
467
-
468
- // Close the file handle if one was opened
469
- if ($handle) {
470
- fclose($handle);
471
- }
472
-
473
- $data = array(
474
- 'name' => ($outFile) ? $outFile : basename($file),
475
- 'mime' => $this->getMimeType(($outFile) ? $outFile : $response['body'], $outFile),
476
- 'meta' => json_decode($response['headers']['dropbox-api-result']),
477
- 'data' => $response['body'],
478
- 'chunked' => true,
479
- );
480
- return $data;
481
- }
482
-
483
- public function delete($path) {
484
- $call = '2/files/delete';
485
- $params = array('path' => '/' . $this->normalisePath($path), 'api_v2' => true);
486
- $response = $this->fetch('POST', self::API_URL_V2, $call, $params);
487
- return $response;
488
- }
489
- /**
490
- * Not used
491
- * Retrieves file and folder metadata
492
- * @param string $path The path to the file/folder, relative to root
493
- * @param string $rev Return metadata for a specific revision (Default: latest rev)
494
- * @param int $limit Maximum number of listings to return
495
- * @param string $hash Metadata hash to compare against
496
- * @param bool $list Return contents field with response
497
- * @param bool $deleted Include files/folders that have been deleted
498
- * @return object stdClass
499
- */
500
- public function metaData($path = null, $rev = null, $limit = 10000, $hash = false, $list = true, $deleted = false) {
501
- $call = '2/files/get_metadata' ;
502
- $params = array(
503
- 'path' => '/' . $this->normalisePath($path),
504
- 'api_v2' => true
505
- );
506
-
507
- return $this->fetch('POST', self::API_URL_V2, $call, $params);
508
- }
509
-
510
- /**
511
- * Not used
512
- * Return "delta entries", intructing you how to update
513
- * your application state to match the server's state
514
- * Important: This method does not make changes to the application state
515
- * @param null|string $cursor Used to keep track of your current state
516
- * @return array Array of delta entries
517
- */
518
- // public function delta($cursor = null) {
519
- // $call = 'delta';
520
- // $params = array('cursor' => $cursor);
521
-
522
- // return $this->fetch('POST', self::API_URL, $call, $params);
523
- // }
524
-
525
- /**
526
- * Not used
527
- * Obtains metadata for the previous revisions of a file
528
- * @param string Path to the file, relative to root
529
- * @param integer Number of revisions to return (1-1000)
530
- * @return array
531
- */
532
- // public function revisions($file, $limit = 10) {
533
- // $call = 'revisions/' . $this->root . '/' . $this->encodePath($file);
534
- // $params = array(
535
- // 'rev_limit' => ($limit < 1) ? 1 : (($limit > 1000) ? 1000 : (int) $limit),
536
- // );
537
-
538
- // return $this->fetch('GET', self::API_URL, $call, $params);
539
- // }
540
-
541
- /**
542
- * Not used
543
- * Restores a file path to a previous revision
544
- * @param string $file Path to the file, relative to root
545
- * @param string $revision The revision of the file to restore
546
- * @return object stdClass
547
- */
548
- // public function restore($file, $revision) {
549
- // $call = 'restore/' . $this->root . '/' . $this->encodePath($file);
550
- // $params = array('rev' => $revision);
551
-
552
- // return $this->fetch('POST', self::API_URL, $call, $params);
553
- // }
554
-
555
- /**
556
- * Returns metadata for all files and folders that match the search query
557
- * @param mixed $query The search string. Must be at least 3 characters long
558
- * @param string $path The path to the folder you want to search in
559
- * @param integer $limit Maximum number of results to return (1-1000)
560
- * @param boolean $deleted Include deleted files/folders in the search
561
- * @return array
562
- */
563
- //Not used
564
- // public function search($query, $path = '', $limit = 1000, $deleted = false) {
565
- // $call = 'search/' . $this->root . '/' . $this->encodePath($path);
566
- // $params = array(
567
- // 'query' => $query,
568
- // 'file_limit' => ($limit < 1) ? 1 : (($limit > 1000) ? 1000 : (int) $limit),
569
- // 'include_deleted' => (int) $deleted,
570
- // );
571
-
572
- // return $this->fetch('GET', self::API_URL, $call, $params);
573
- // }
574
-
575
- /**
576
- * Not used
577
- * Creates and returns a shareable link to files or folders
578
- * The link returned is for a preview page from which the user an choose to
579
- * download the file if they wish. For direct download links, see media().
580
- * @param string $path The path to the file/folder you want a sharable link to
581
- * @return object stdClass
582
- */
583
- // public function shares($path, $shortUrl = true) {
584
- // $call = 'shares/' . $this->root . '/' . $this->encodePath($path);
585
- // $params = array('short_url' => ($shortUrl) ? 1 : 0);
586
-
587
- // return $this->fetch('POST', self::API_URL, $call, $params);
588
- // }
589
-
590
- /**
591
- * Not used
592
- * Returns a link directly to a file
593
- * @param string $path The path to the media file you want a direct link to
594
- * @return object stdClass
595
- */
596
- // public function media($path) {
597
- // $call = 'media/' . $this->root . '/' . $this->encodePath($path);
598
-
599
- // return $this->fetch('POST', self::API_URL, $call);
600
- // }
601
-
602
- /**
603
- * Not used
604
- * Gets a thumbnail for an image
605
- * @param string $file The path to the image you wish to thumbnail
606
- * @param string $format The thumbnail format, either JPEG or PNG
607
- * @param string $size The size of the thumbnail
608
- * @return array
609
- */
610
- // public function thumbnails($file, $format = 'JPEG', $size = 'small') {
611
- // $format = strtoupper($format);
612
- // // If $format is not 'PNG', default to 'JPEG'
613
- // if ($format != 'PNG') {
614
- // $format = 'JPEG';
615
- // }
616
-
617
- // $size = strtolower($size);
618
- // $sizes = array('s', 'm', 'l', 'xl', 'small', 'medium', 'large');
619
- // // If $size is not valid, default to 'small'
620
- // if (!in_array($size, $sizes)) {
621
- // $size = 'small';
622
- // }
623
-
624
- // $call = 'thumbnails/' . $this->root . '/' . $this->encodePath($file);
625
- // $params = array('format' => $format, 'size' => $size);
626
- // $response = $this->fetch('GET', self::CONTENT_URL, $call, $params);
627
-
628
- // return array(
629
- // 'name' => basename($file),
630
- // 'mime' => $this->getMimeType($response['body']),
631
- // 'meta' => json_decode($response['headers']['x-dropbox-metadata']),
632
- // 'data' => $response['body'],
633
- // );
634
- // }
635
-
636
- /**
637
- * Not used
638
- * Creates and returns a copy_ref to a file
639
- * This reference string can be used to copy that file to another user's
640
- * Dropbox by passing it in as the from_copy_ref parameter on /fileops/copy
641
- * @param $path File for which ref should be created, relative to root
642
- * @return array
643
- */
644
- // public function copyRef($path) {
645
- // $call = 'copy_ref/' . $this->root . '/' . $this->encodePath($path);
646
-
647
- // return $this->fetch('GET', self::API_URL, $call);
648
- // }
649
-
650
- /**
651
- * Not used
652
- * Copies a file or folder to a new location
653
- * @param string $from File or folder to be copied, relative to root
654
- * @param string $to Destination path, relative to root
655
- * @param null|string $fromCopyRef Must be used instead of the from_path
656
- * @return object stdClass
657
- */
658
- // public function copy($from, $to, $fromCopyRef = null) {
659
- // $call = 'fileops/copy';
660
- // $params = array(
661
- // 'root' => $this->root,
662
- // 'from_path' => $this->normalisePath($from),
663
- // 'to_path' => $this->normalisePath($to),
664
- // );
665
-
666
- // if ($fromCopyRef) {
667
- // $params['from_path'] = null;
668
- // $params['from_copy_ref'] = $fromCopyRef;
669
- // }
670
-
671
- // return $this->fetch('POST', self::API_URL, $call, $params);
672
- // }
673
-
674
- /**
675
- * Not used
676
- * Creates a folder
677
- * @param string New folder to create relative to root
678
- * @return object stdClass
679
- */
680
- // public function create($path) {
681
- // $call = 'fileops/create_folder';
682
- // $params = array('root' => $this->root, 'path' => $this->normalisePath($path));
683
-
684
- // return $this->fetch('POST', self::API_URL, $call, $params);
685
- // }
686
-
687
- /**
688
- * Not used
689
- * Deletes a file or folder
690
- * @param string $path The path to the file or folder to be deleted
691
- * @return object stdClass
692
- */
693
- // public function delete($path) {
694
- // $call = '2/files/delete';
695
- // $params = array('path' => '/' . $this->normalisePath($path), 'api_v2' => true);
696
- // $response = $this->fetch('POST', self::API_URL_V2, $call, $params);
697
- // return $response;
698
- // }
699
-
700
- /**
701
- * Not used
702
- * Moves a file or folder to a new location
703
- * @param string $from File or folder to be moved, relative to root
704
- * @param string $to Destination path, relative to root
705
- * @return object stdClass
706
- */
707
- // public function move($from, $to) {
708
- // $call = 'fileops/move';
709
- // $params = array(
710
- // 'root' => $this->root,
711
- // 'from_path' => $this->normalisePath($from),
712
- // 'to_path' => $this->normalisePath($to),
713
- // );
714
-
715
- // return $this->fetch('POST', self::API_URL, $call, $params);
716
- // }
717
-
718
- /**
719
- * Intermediate fetch function
720
- * @param string $method The HTTP method
721
- * @param string $url The API endpoint
722
- * @param string $call The API method to call
723
- * @param array $params Additional parameters
724
- * @return mixed
725
- */
726
- private function fetch($method, $url, $call, array $params = array(), $isChunkDownload = array())
727
- {
728
- // Make the API call via the consumer
729
- $response = $this->OAuth->fetch($method, $url, $call, $params, $isChunkDownload);
730
-
731
- // Format the response and return
732
- switch ($this->responseFormat) {
733
- case 'json':
734
- return json_encode($response);
735
- case 'jsonp':
736
- $response = json_encode($response);
737
- return $this->callback . '(' . $response . ')';
738
- default:
739
- return $response;
740
- }
741
- }
742
-
743
-
744
- /**
745
- * Set the chunk size for chunked uploads
746
- * If $chunkSize is empty, set to 4194304 bytes (4 MB)
747
- * @see \Dropbox\API\chunkedUpload()
748
- */
749
- public function setChunkSize($chunkSize = 4194304) {
750
- if (!is_int($chunkSize)) {
751
- throw new Exception('Expecting chunk size to be an integer, got ' . gettype($chunkSize));
752
- } elseif ($chunkSize > 157286400) {
753
- throw new Exception('Chunk size must not exceed 157286400 bytes, got ' . $chunkSize);
754
- } else {
755
- $this->chunkSize = $chunkSize;
756
- }
757
- }
758
-
759
- /**
760
- * Get the mime type of downloaded file
761
- * If the Fileinfo extension is not loaded, return false
762
- * @param string $data File contents as a string or filename
763
- * @param string $isFilename Is $data a filename?
764
- * @return boolean|string Mime type and encoding of the file
765
- */
766
- private function getMimeType($data, $isFilename = false) {
767
- if (extension_loaded('fileinfo')) {
768
- $finfo = new finfo(FILEINFO_MIME);
769
- if ($isFilename !== false) {
770
- return @$finfo->file($data);
771
- }
772
-
773
- return $finfo->buffer($data);
774
- }
775
-
776
- return false;
777
- }
778
-
779
- /**
780
- * Trim the path of forward slashes and replace
781
- * consecutive forward slashes with a single slash
782
- * then replace backslashes with forward slashes
783
- * @param string $path The path to normalise
784
- * @return string
785
- */
786
- private function normalisePath($path) {
787
- $path = preg_replace('#/+#', '/', trim($path, '/'));
788
- return $path;
789
- }
790
-
791
- /**
792
- * Encode the path, then replace encoded slashes
793
- * with literal forward slash characters
794
- * @param string $path The path to encode
795
- * @return string
796
- */
797
- private function encodePath($path) {
798
- // 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.
799
- return $this->normalisePath($path);
800
- }
801
- }
1
+ <?php
2
+
3
+ /**
4
+ * Dropbox API base class
5
+ * @author Ben Tadiar <ben@handcraftedbyben.co.uk>
6
+ * @link https://github.com/benthedesigner/dropbox
7
+ * @link https://www.dropbox.com/developers
8
+ * @link https://status.dropbox.com Dropbox status
9
+ * @package Dropbox
10
+ */
11
+ class IWP_MMB_Dropbox_API {
12
+ // API Endpoints
13
+ const API_URL = 'https://api.dropbox.com/1/';
14
+ const API_URL_V2 = 'https://api.dropboxapi.com/';
15
+ const CONTENT_URL = 'https://api-content.dropbox.com/1/';
16
+ const CONTENT_URL_V2 = 'https://content.dropboxapi.com/2/';
17
+
18
+ /**
19
+ * OAuth consumer object
20
+ * @var null|OAuth\Consumer
21
+ */
22
+ private $OAuth;
23
+
24
+ /**
25
+ * The root level for file paths
26
+ * Either `dropbox` or `sandbox` (preferred)
27
+ * @var null|string
28
+ */
29
+ private $root;
30
+
31
+ /**
32
+ * Format of the API response
33
+ * @var string
34
+ */
35
+ private $responseFormat = 'php';
36
+
37
+ /**
38
+ * JSONP callback
39
+ * @var string
40
+ */
41
+ private $callback = 'dropboxCallback';
42
+
43
+ /**
44
+ * Chunk size used for chunked uploads
45
+ * @see \Dropbox\API::chunkedUpload()
46
+ */
47
+ private $chunkSize = 4194304;
48
+
49
+ /**
50
+ * Set the OAuth consumer object
51
+ * See 'General Notes' at the link below for information on access type
52
+ * @link https://www.dropbox.com/developers/reference/api
53
+ * @param OAuth\Consumer\ConsumerAbstract $OAuth
54
+ * @param string $root Dropbox app access type
55
+ */
56
+ public function __construct(Dropbox_ConsumerAbstract $OAuth, $root = 'sandbox') {
57
+ $this->OAuth = $OAuth;
58
+ $this->setRoot($root);
59
+ }
60
+
61
+ /**
62
+ * Set the root level
63
+ * @param mixed $root
64
+ * @throws Exception
65
+ * @return void
66
+ */
67
+ public function setRoot($root) {
68
+ if ($root !== 'sandbox' && $root !== 'dropbox') {
69
+ throw new Exception("Expected a root of either 'dropbox' or 'sandbox', got '$root'");
70
+ } else {
71
+ $this->root = $root;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Retrieves information about the user's account
77
+ * @return object stdClass
78
+ */
79
+ public function accountInfo() {
80
+ $call = '2/users/get_current_account';
81
+ $params = array('api_v2' => true);
82
+ $response = $this->fetch('POST', self::API_URL_V2, $call, $params);
83
+ return $response;
84
+ }
85
+
86
+ /**
87
+ * Retrieves information about the user's quota
88
+ * @return object stdClass
89
+ */
90
+ public function quotaInfo() {
91
+ $call = '2/users/get_space_usage';
92
+ $params = array('api_v2' => true);
93
+ $response = $this->fetch('POST', self::API_URL_V2, $call, $params);
94
+ return $response;
95
+ }
96
+
97
+ /**
98
+ * Not used
99
+ * Uploads a physical file from disk
100
+ * Dropbox impose a 150MB limit to files uploaded via the API. If the file
101
+ * exceeds this limit or does not exist, an Exception will be thrown
102
+ * @param string $file Absolute path to the file to be uploaded
103
+ * @param string|bool $filename The destination filename of the uploaded file
104
+ * @param string $path Path to upload the file to, relative to root
105
+ * @param boolean $overwrite Should the file be overwritten? (Default: true)
106
+ * @return object stdClass
107
+ */
108
+ // public function putFile($file, $filename = false, $path = '', $overwrite = true)
109
+ // {
110
+ // if (file_exists($file)) {
111
+ // if (filesize($file) <= 157286400) {
112
+ // $call = 'files/' . $this->root . '/' . $this->encodePath($path);
113
+ // // If no filename is provided we'll use the original filename
114
+ // $filename = (is_string($filename)) ? $filename : basename($file);
115
+ // $params = array(
116
+ // 'filename' => $filename,
117
+ // 'file' => '@' . str_replace('\\', '/', $file) . ';filename=' . $filename,
118
+ // 'overwrite' => (int) $overwrite,
119
+ // );
120
+ // $response = $this->fetch('POST', self::CONTENT_URL, $call, $params);
121
+ // return $response;
122
+ // }
123
+ // throw new Exception('File exceeds 150MB upload limit');
124
+ // }
125
+
126
+ // // Throw an Exception if the file does not exist
127
+ // throw new Exception('Local file ' . $file . ' does not exist');
128
+ // }
129
+
130
+ /**
131
+ * Not used
132
+ * Uploads file data from a stream
133
+ * Note: This function is experimental and requires further testing
134
+ * @todo Add filesize check
135
+ * @param resource $stream A readable stream created using fopen()
136
+ * @param string $filename The destination filename, including path
137
+ * @param boolean $overwrite Should the file be overwritten? (Default: true)
138
+ * @return array
139
+ */
140
+ // public function putStream($stream, $filename, $overwrite = true)
141
+ // {
142
+ // $this->OAuth->setInFile($stream);
143
+ // $path = $this->encodePath($filename);
144
+ // $call = 'files_put/' . $this->root . '/' . $path;
145
+ // $params = array('overwrite' => (int) $overwrite);
146
+ // $response = $this->fetch('PUT', self::CONTENT_URL, $call, $params);
147
+ // return $response;
148
+ // }
149
+
150
+ /**
151
+ * Uploads large files to Dropbox in mulitple chunks
152
+ * @param string $file Absolute path to the file to be uploaded
153
+ * @param string|bool $filename The destination filename of the uploaded file
154
+ * @param string $path Path to upload the file to, relative to root
155
+ * @param boolean $overwrite Should the file be overwritten? (Default: true)
156
+ * @param integer $offset position to seek to when opening the file
157
+ * @param string $uploadID existing upload_id to resume an upload
158
+ * @param string|array function to call back to upon each chunk
159
+ * @return stdClass
160
+ */
161
+ public function chunkedUpload($file, $filename = false, $path = '', $overwrite = true, $offset = 0, $uploadID = null, $callback = null) {
162
+
163
+ if (file_exists($file)) {
164
+ if ($handle = @fopen($file, 'r')) {
165
+ // Set initial upload ID and offset
166
+ if ($offset > 0) {
167
+ fseek($handle, $offset);
168
+ }
169
+
170
+ /*
171
+ Set firstCommit to true so that the upload session start endpoint is called.
172
+ */
173
+ $firstCommit = (0 == $offset);
174
+
175
+ // Read from the file handle until EOF, uploading each chunk
176
+ while ($data = fread($handle, $this->chunkSize)) {
177
+
178
+ // Set the file, request parameters and send the request
179
+ $this->OAuth->setInFile($data);
180
+
181
+ if ($firstCommit) {
182
+ $params = array(
183
+ 'close' => false,
184
+ 'api_v2' => true,
185
+ 'content_upload' => true
186
+ );
187
+ $response = $this->fetch('POST', self::CONTENT_URL_V2, 'files/upload_session/start', $params);
188
+ $firstCommit = false;
189
+ } else {
190
+ $params = array(
191
+ 'cursor' => array(
192
+ 'session_id' => $uploadID,
193
+ // If you send it as a string, Dropbox will be unhappy
194
+ 'offset' => (int)$offset
195
+ ),
196
+ 'api_v2' => true,
197
+ 'content_upload' => true
198
+ );
199
+ $response = $this->append_upload($params, false);
200
+ }
201
+
202
+ // On subsequent chunks, use the upload ID returned by the previous request
203
+ if (isset($response['body']->session_id)) {
204
+ $uploadID = $response['body']->session_id;
205
+ }
206
+
207
+ /*
208
+ 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.
209
+ */
210
+ if (!isset($response['body']->error)) {
211
+ $offset = ftell($handle);
212
+ if ($callback) {
213
+ call_user_func($callback, $offset, $uploadID, $file);
214
+ }
215
+ $this->OAuth->setInFile(null);
216
+ }
217
+ }
218
+
219
+ // Complete the chunked upload
220
+ $filename = (is_string($filename)) ? $filename : basename($file);
221
+ $params = array(
222
+ 'cursor' => array(
223
+ 'session_id' => $uploadID,
224
+ 'offset' => $offset
225
+ ),
226
+ 'commit' => array(
227
+ 'path' => '/' . $this->encodePath($path . $filename),
228
+ 'mode' => 'add'
229
+ ),
230
+ 'api_v2' => true,
231
+ 'content_upload' => true
232
+ );
233
+ $response = $this->append_upload($params, true);
234
+ return $response;
235
+ } else {
236
+ throw new Exception('Could not open ' . $file . ' for reading');
237
+ }
238
+ }
239
+
240
+ // Throw an Exception if the file does not exist
241
+ throw new Exception('Local file ' . $file . ' does not exist');
242
+ }
243
+
244
+ private function append_upload($params, $last_call) {
245
+ try {
246
+ if ($last_call){
247
+ $response = $this->fetch('POST', self::CONTENT_URL_V2, 'files/upload_session/finish', $params);
248
+ } else {
249
+ $response = $this->fetch('POST', self::CONTENT_URL_V2, 'files/upload_session/append_v2', $params);
250
+ }
251
+ } catch (Exception $e) {
252
+ $responseCheck = json_decode($e->getMessage());
253
+ if (isset($responseCheck) && strpos($responseCheck[0] , 'incorrect_offset') !== false) {
254
+ $expected_offset = $responseCheck[1];
255
+ throw new Exception('Submitted input out of alignment: got ['.$params['cursor']['offset'].'] expected ['.$expected_offset.']');
256
+
257
+ // $params['cursor']['offset'] = $responseCheck[1];
258
+ // $response = $this->append_upload($params, $last_call);
259
+ } else {
260
+ throw $e;
261
+ }
262
+ }
263
+ return $response;
264
+ }
265
+
266
+ /**
267
+ * Downloads a file
268
+ * Returns the base filename, raw file data and mime type returned by Fileinfo
269
+ * @param string $file Path to file, relative to root, including path
270
+ * @param string $outFile Filename to write the downloaded file to
271
+ * @param string $revision The revision of the file to retrieve
272
+ * @param boolean $allow_resume - append to the file if it already exists
273
+ * @return array
274
+ */
275
+ public function getFile($file, $outFile = false, $revision = null, $allow_resume = false) {
276
+ // Only allow php response format for this call
277
+ if ($this->responseFormat !== 'php') {
278
+ throw new Exception('This method only supports the `php` response format');
279
+ }
280
+
281
+ $handle = null;
282
+ if ($outFile !== false) {
283
+ // Create a file handle if $outFile is specified
284
+ if ($allow_resume && file_exists($outFile)) {
285
+ if (!$handle = fopen($outFile, 'a')) {
286
+ throw new Exception("Unable to open file handle for $outFile");
287
+ } else {
288
+ $this->OAuth->setOutFile($handle);
289
+ $params['headers'] = array('Range: bytes='.filesize($outFile).'-');
290
+ }
291
+ }
292
+ elseif (!$handle = fopen($outFile, 'w')) {
293
+ throw new Exception("Unable to open file handle for $outFile");
294
+ } else {
295
+ $this->OAuth->setOutFile($handle);
296
+ }
297
+ }
298
+
299
+ $file = $this->encodePath($file);
300
+ $call = 'files/download';
301
+ $params = array('path' => '/' . $file, 'api_v2' => true, 'content_download' => true);
302
+ $response = $this->fetch('GET', self::CONTENT_URL_V2, $call, $params);
303
+
304
+ // Close the file handle if one was opened
305
+ if ($handle) fclose($handle);
306
+
307
+ return array(
308
+ 'name' => ($outFile) ? $outFile : basename($file),
309
+ 'mime' => $this->getMimeType(($outFile) ? $outFile : $response['body'], $outFile),
310
+ 'meta' => json_decode($response['headers']['dropbox-api-result']),
311
+ 'data' => $response['body'],
312
+ );
313
+ }
314
+
315
+ /**
316
+ * Not used
317
+ * Retrieves file and folder metadata
318
+ * @param string $path The path to the file/folder, relative to root
319
+ * @param string $rev Return metadata for a specific revision (Default: latest rev)
320
+ * @param int $limit Maximum number of listings to return
321
+ * @param string $hash Metadata hash to compare against
322
+ * @param bool $list Return contents field with response
323
+ * @param bool $deleted Include files/folders that have been deleted
324
+ * @return object stdClass
325
+ */
326
+ public function metaData($path = null, $rev = null, $limit = 10000, $hash = false, $list = true, $deleted = false) {
327
+ $call = '2/files/get_metadata' ;
328
+ $params = array(
329
+ 'path' => '/' . $this->normalisePath($path),
330
+ 'api_v2' => true
331
+ );
332
+
333
+ return $this->fetch('POST', self::API_URL_V2, $call, $params);
334
+ }
335
+
336
+ /**
337
+ * Not used
338
+ * Return "delta entries", intructing you how to update
339
+ * your application state to match the server's state
340
+ * Important: This method does not make changes to the application state
341
+ * @param null|string $cursor Used to keep track of your current state
342
+ * @return array Array of delta entries
343
+ */
344
+ // public function delta($cursor = null)
345
+ // {
346
+ // $call = 'delta';
347
+ // $params = array('cursor' => $cursor);
348
+ // $response = $this->fetch('POST', self::API_URL, $call, $params);
349
+ // return $response;
350
+ // }
351
+
352
+ /**
353
+ * Not used
354
+ * Obtains metadata for the previous revisions of a file
355
+ * @param string Path to the file, relative to root
356
+ * @param integer Number of revisions to return (1-1000)
357
+ * @return array
358
+ */
359
+ // public function revisions($file, $limit = 10)
360
+ // {
361
+ // $call = 'revisions/' . $this->root . '/' . $this->encodePath($file);
362
+ // $params = array(
363
+ // 'rev_limit' => ($limit < 1) ? 1 : (($limit > 1000) ? 1000 : (int) $limit),
364
+ // );
365
+ // $response = $this->fetch('GET', self::API_URL, $call, $params);
366
+ // return $response;
367
+ // }
368
+
369
+ /**
370
+ * Not used
371
+ * Restores a file path to a previous revision
372
+ * @param string $file Path to the file, relative to root
373
+ * @param string $revision The revision of the file to restore
374
+ * @return object stdClass
375
+ */
376
+ // public function restore($file, $revision)
377
+ // {
378
+ // $call = 'restore/' . $this->root . '/' . $this->encodePath($file);
379
+ // $params = array('rev' => $revision);
380
+ // $response = $this->fetch('POST', self::API_URL, $call, $params);
381
+ // return $response;
382
+ // }
383
+
384
+ /**
385
+ * Returns metadata for all files and folders that match the search query
386
+ * @param mixed $query The search string. Must be at least 3 characters long
387
+ * @param string [$path=''] The path to the folder you want to search in
388
+ * @param integer [$limit=1000] Maximum number of results to return (1-1000)
389
+ * @param integer [$start=0] Result number to start from
390
+ * @return array
391
+ */
392
+ public function search($query, $path = '', $limit = 1000, $start = 0) {
393
+ $call = '2/files/search';
394
+ $path = $this->encodePath($path);
395
+ // APIv2 requires that the path match this regex: String(pattern="(/(.|[\r\n])*)?|(ns:[0-9]+(/.*)?)")
396
+ if ($path && '/' != substr($path, 0, 1)) $path = "/$path";
397
+ $params = array(
398
+ 'path' => $path,
399
+ 'query' => $query,
400
+ 'start' => $start,
401
+ 'max_results' => ($limit < 1) ? 1 : (($limit > 1000) ? 1000 : (int) $limit),
402
+ 'api_v2' => true,
403
+ );
404
+ $response = $this->fetch('POST', self::API_URL_V2, $call, $params);
405
+ return $response;
406
+ }
407
+
408
+ /**
409
+ * Not used
410
+ * Creates and returns a shareable link to files or folders
411
+ * The link returned is for a preview page from which the user an choose to
412
+ * download the file if they wish. For direct download links, see media().
413
+ * @param string $path The path to the file/folder you want a sharable link to
414
+ * @return object stdClass
415
+ */
416
+ // public function shares($path, $shortUrl = true)
417
+ // {
418
+ // $call = 'shares/' . $this->root . '/' .$this->encodePath($path);
419
+ // $params = array('short_url' => ($shortUrl) ? 1 : 0);
420
+ // $response = $this->fetch('POST', self::API_URL, $call, $params);
421
+ // return $response;
422
+ // }
423
+
424
+ /**
425
+ * Not used
426
+ * Returns a link directly to a file
427
+ * @param string $path The path to the media file you want a direct link to
428
+ * @return object stdClass
429
+ */
430
+ // public function media($path)
431
+ // {
432
+ // $call = 'media/' . $this->root . '/' . $this->encodePath($path);
433
+ // $response = $this->fetch('POST', self::API_URL, $call);
434
+ // return $response;
435
+ // }
436
+
437
+ /**
438
+ * Not used
439
+ * Gets a thumbnail for an image
440
+ * @param string $file The path to the image you wish to thumbnail
441
+ * @param string $format The thumbnail format, either JPEG or PNG
442
+ * @param string $size The size of the thumbnail
443
+ * @return array
444
+ */
445
+ // public function thumbnails($file, $format = 'JPEG', $size = 'small')
446
+ // {
447
+ // // Only allow php response format for this call
448
+ // if ($this->responseFormat !== 'php') {
449
+ // throw new Exception('This method only supports the `php` response format');
450
+ // }
451
+
452
+ // $format = strtoupper($format);
453
+ // // If $format is not 'PNG', default to 'JPEG'
454
+ // if ($format != 'PNG') $format = 'JPEG';
455
+
456
+ // $size = strtolower($size);
457
+ // $sizes = array('s', 'm', 'l', 'xl', 'small', 'medium', 'large');
458
+ // // If $size is not valid, default to 'small'
459
+ // if (!in_array($size, $sizes)) $size = 'small';
460
+
461
+ // $call = 'thumbnails/' . $this->root . '/' . $this->encodePath($file);
462
+ // $params = array('format' => $format, 'size' => $size);
463
+ // $response = $this->fetch('GET', self::CONTENT_URL, $call, $params);
464
+
465
+ // return array(
466
+ // 'name' => basename($file),
467
+ // 'mime' => $this->getMimeType($response['body']),
468
+ // 'meta' => json_decode($response['headers']['x-dropbox-metadata']),
469
+ // 'data' => $response['body'],
470
+ // );
471
+ // }
472
+
473
+ /**
474
+ * Not used
475
+ * THIS IS NOT AVAILABLE IN V2.
476
+ * Creates and returns a copy_ref to a file
477
+ * This reference string can be used to copy that file to another user's
478
+ * Dropbox by passing it in as the from_copy_ref parameter on /fileops/copy
479
+ * @param $path File for which ref should be created, relative to root
480
+ * @return array
481
+ */
482
+ // public function copyRef($path)
483
+ // {
484
+ // $call = 'copy_ref/' . $this->root . '/' . $this->encodePath($path);
485
+ // $response = $this->fetch('GET', self::API_URL, $call);
486
+ // return $response;
487
+ // }
488
+
489
+ /**
490
+ * Not used
491
+ * Copies a file or folder to a new location
492
+ * @param string $from File or folder to be copied, relative to root
493
+ * @param string $to Destination path, relative to root
494
+ * @param null|string $fromCopyRef Must be used instead of the from_path
495
+ * @return object stdClass
496
+ */
497
+ // public function copy($from, $to, $fromCopyRef = null)
498
+ // {
499
+ // $call = 'fileops/copy';
500
+ // $params = array(
501
+ // 'root' => $this->root,
502
+ // 'from_path' => $this->normalisePath($from),
503
+ // 'to_path' => $this->normalisePath($to),
504
+ // );
505
+
506
+ // if ($fromCopyRef) {
507
+ // $params['from_path'] = null;
508
+ // $params['from_copy_ref'] = $fromCopyRef;
509
+ // }
510
+
511
+ // $response = $this->fetch('POST', self::API_URL, $call, $params);
512
+ // return $response;
513
+ // }
514
+
515
+ /**
516
+ * Not used
517
+ * Creates a folder
518
+ * @param string New folder to create relative to root
519
+ * @return object stdClass
520
+ */
521
+ // public function create($path)
522
+ // {
523
+ // $call = 'fileops/create_folder';
524
+ // $params = array('root' => $this->root, 'path' => $this->normalisePath($path));
525
+ // $response = $this->fetch('POST', self::API_URL, $call, $params);
526
+ // return $response;
527
+ // }
528
+
529
+ /**
530
+ * Deletes a file or folder
531
+ * @param string $path The path to the file or folder to be deleted
532
+ * @return object stdClass
533
+ */
534
+ public function delete($path) {
535
+ $call = '2/files/delete';
536
+ $params = array('path' => '/' . $this->normalisePath($path), 'api_v2' => true);
537
+ $response = $this->fetch('POST', self::API_URL_V2, $call, $params);
538
+ return $response;
539
+ }
540
+
541
+ /**
542
+ * Not used
543
+ * Moves a file or folder to a new location
544
+ * @param string $from File or folder to be moved, relative to root
545
+ * @param string $to Destination path, relative to root
546
+ * @return object stdClass
547
+ */
548
+ // public function move($from, $to)
549
+ // {
550
+ // $call = 'fileops/move';
551
+ // $params = array(
552
+ // 'root' => $this->root,
553
+ // 'from_path' => $this->normalisePath($from),
554
+ // 'to_path' => $this->normalisePath($to),
555
+ // );
556
+ // $response = $this->fetch('POST', self::API_URL, $call, $params);
557
+ // return $response;
558
+ // }
559
+
560
+ /**
561
+ * Intermediate fetch function
562
+ * @param string $method The HTTP method
563
+ * @param string $url The API endpoint
564
+ * @param string $call The API method to call
565
+ * @param array $params Additional parameters
566
+ * @return mixed
567
+ */
568
+ private function fetch($method, $url, $call, array $params = array()) {
569
+ // Make the API call via the consumer
570
+ $response = $this->OAuth->fetch($method, $url, $call, $params);
571
+
572
+ // Format the response and return
573
+ switch ($this->responseFormat) {
574
+ case 'json':
575
+ return json_encode($response);
576
+ case 'jsonp':
577
+ $response = json_encode($response);
578
+ return $this->callback . '(' . $response . ')';
579
+ default:
580
+ return $response;
581
+ }
582
+ }
583
+
584
+ /**
585
+ * Set the API response format
586
+ * @param string $format One of php, json or jsonp
587
+ * @return void
588
+ */
589
+ public function setResponseFormat($format) {
590
+ $format = strtolower($format);
591
+ if (!in_array($format, array('php', 'json', 'jsonp'))) {
592
+ throw new Exception("Expected a format of php, json or jsonp, got '$format'");
593
+ } else {
594
+ $this->responseFormat = $format;
595
+ }
596
+ }
597
+
598
+ /**
599
+ * Set the chunk size for chunked uploads
600
+ * If $chunkSize is empty, set to 4194304 bytes (4 MB)
601
+ * @see \Dropbox\API\chunkedUpload()
602
+ */
603
+ public function setChunkSize($chunkSize = 4194304) {
604
+ if (!is_int($chunkSize)) {
605
+ throw new Exception('Expecting chunk size to be an integer, got ' . gettype($chunkSize));
606
+ } elseif ($chunkSize > 157286400) {
607
+ throw new Exception('Chunk size must not exceed 157286400 bytes, got ' . $chunkSize);
608
+ } else {
609
+ $this->chunkSize = $chunkSize;
610
+ }
611
+ }
612
+
613
+ /**
614
+ * Set the JSONP callback function
615
+ * @param string $function
616
+ * @return void
617
+ */
618
+ public function setCallback($function) {
619
+ $this->callback = $function;
620
+ }
621
+
622
+ /**
623
+ * Get the mime type of downloaded file
624
+ * If the Fileinfo extension is not loaded, return false
625
+ * @param string $data File contents as a string or filename
626
+ * @param string $isFilename Is $data a filename?
627
+ * @return boolean|string Mime type and encoding of the file
628
+ */
629
+ private function getMimeType($data, $isFilename = false) {
630
+ if (extension_loaded('fileinfo')) {
631
+ $finfo = new finfo(FILEINFO_MIME);
632
+ if ($isFilename !== false) {
633
+ return $finfo->file($data);
634
+ }
635
+ return $finfo->buffer($data);
636
+ }
637
+ return false;
638
+ }
639
+
640
+ /**
641
+ * Trim the path of forward slashes and replace
642
+ * consecutive forward slashes with a single slash
643
+ * @param string $path The path to normalise
644
+ * @return string
645
+ */
646
+ private function normalisePath($path) {
647
+ $path = preg_replace('#/+#', '/', trim($path, '/'));
648
+ return $path;
649
+ }
650
+
651
+ /**
652
+ * Encode the path, then replace encoded slashes
653
+ * with literal forward slash characters
654
+ * @param string $path The path to encode
655
+ * @return string
656
+ */
657
+ private function encodePath($path) {
658
+ // 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.
659
+ return $this->normalisePath($path);
660
+ }
661
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Dropbox2/Exception.php CHANGED
@@ -6,26 +6,23 @@
6
  * @link https://github.com/benthedesigner/dropbox
7
  * @package Dropbox
8
  */
9
- class IWP_Dropbox_Exception extends Exception {
10
  }
11
 
12
- class IWP_Dropbox_BadRequestException extends Exception {
13
  }
14
 
15
- class IWP_Dropbox_CurlException extends Exception {
16
  }
17
 
18
- class IWP_Dropbox_NotAcceptableException extends Exception {
19
  }
20
 
21
- class IWP_Dropbox_NotFoundException extends Exception {
22
  }
23
 
24
- class IWP_Dropbox_NotModifiedException extends Exception {
25
  }
26
 
27
- class IWP_Dropbox_UnsupportedMediaTypeException extends Exception {
28
- }
29
-
30
- class IWP_Dropbox_TokenExpired extends Exception {
31
  }
6
  * @link https://github.com/benthedesigner/dropbox
7
  * @package Dropbox
8
  */
9
+ class Dropbox_Exception extends Exception {
10
  }
11
 
12
+ class Dropbox_BadRequestException extends Exception {
13
  }
14
 
15
+ class Dropbox_CurlException extends Exception {
16
  }
17
 
18
+ class Dropbox_NotAcceptableException extends Exception {
19
  }
20
 
21
+ class Dropbox_NotFoundException extends Exception {
22
  }
23
 
24
+ class Dropbox_NotModifiedException extends Exception {
25
  }
26
 
27
+ class Dropbox_UnsupportedMediaTypeException extends Exception {
 
 
 
28
  }
lib/Dropbox2/OAuth/Consumer/ConsumerAbstract.php CHANGED
@@ -8,333 +8,451 @@
8
  * @subpackage Consumer
9
  */
10
 
11
- abstract class IWP_Dropbox_OAuth_Consumer_ConsumerAbstract
12
  {
13
- // Dropbox web endpoint
14
- const WEB_URL = 'https://www.dropbox.com/';
15
-
16
- // OAuth flow methods
17
- const REQUEST_TOKEN_METHOD = 'oauth2/REQUEST_TOKEN_METHOD';
18
- const AUTHORISE_METHOD = 'oauth2/authorize';
19
- const ACCESS_TOKEN_METHOD = 'oauth2/token';
20
- const API_URL = 'https://api.dropbox.com/1/';
21
- const OAUTH_UPGRADE = 'oauth2/token_from_oauth1';
22
-
23
- /**
24
- * Signature method, either PLAINTEXT or HMAC-SHA1
25
- * @var string
26
- */
27
- private $sigMethod = 'PLAINTEXT';
28
-
29
- /**
30
- * Output file handle
31
- * @var null|resource
32
- */
33
- protected $outFile = null;
34
-
35
- /**
36
- * Input file handle
37
- * @var null|resource
38
- */
39
- protected $inFile = null;
40
-
41
- /**
42
- * OAuth token
43
- * @var stdclass
44
- */
45
- private $token = null;
46
-
47
- /**
48
- * Acquire an unauthorised request token
49
- * @link http://tools.ietf.org/html/rfc5849#section-2.1
50
- * @return void
51
- */
52
- public function getRequestToken()
53
- {
54
- $url = WPTC_Dropbox_API::API_URL_V2 . self::REQUEST_TOKEN_METHOD;
55
- $response = $this->fetch('POST', $url, '');
56
-
57
- return $this->parseTokenString($response['body']);
58
- }
59
-
60
- /**
61
- * Build the user authorisation URL
62
- * @return string
63
- */
64
- public function getAuthoriseUrl()
65
- {
66
- $params = array(
67
- 'client_id' => WPTC_DROPBOX_CLIENT_ID,
68
- 'response_type' => 'code',
69
- 'redirect_uri' => WPTC_DROPBOX_REDIRECT_URL,
70
- 'state' => WPTC_DROPBOX_WP_REDIRECT_URL,
71
- );
72
-
73
-
74
- // Build the URL and redirect the user
75
- $query = '?' . http_build_query($params, '', '&');
76
- $url = self::WEB_URL . self::AUTHORISE_METHOD . $query;
77
-
78
- return $url;
79
- }
80
-
81
- public function upgradeOAuth()
82
- {
 
 
83
  // 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)
84
-
85
- $url = self::API_URL . self::OAUTH_UPGRADE;
86
- $config = WPTC_Factory::get('config');
87
- $this->token = new stdClass();
88
- $this->token->oauth_token = $config->get_option('access_token');
89
- $this->token->oauth_token_secret = $config->get_option('access_token_secret');
90
- $response = $this->fetch('POST', $url, '');
91
- return $response['body'];
92
- }
93
-
94
- /**
95
- * Acquire an access token
96
- * Tokens acquired at this point should be stored to
97
- * prevent having to request new tokens for each API call
98
- * @link http://tools.ietf.org/html/rfc5849#section-2.3
99
- */
100
- public function getAccessToken()
101
- {
102
- // Get the signed request URL
103
- $response = $this->fetch('POST', WPTC_Dropbox_API::API_URL_V2, self::ACCESS_TOKEN_METHOD);
104
- return $this->parseTokenString($response['body']);
105
- }
106
-
107
- /**
108
- * Generate signed request URL
109
- * See inline comments for description
110
- * @link http://tools.ietf.org/html/rfc5849#section-3.4
111
- * @param string $method HTTP request method
112
- * @param string $url API endpoint to send the request to
113
- * @param string $call API call to send
114
- * @param array $additional Additional parameters as an associative array
115
- * @return array
116
- */
117
- protected function getSignedRequest($method, $url, $call, array $additional = array())
118
- {
119
- // Get the request/access token
120
- $token = $this->getToken();
121
- // Prepare the standard request parameters differently for OAuth1 and OAuth2; we still need OAuth1 to make the request to the upgrade token endpoint
122
- if (!empty($token)) {
123
- $params = array(
124
- 'access_token' => $token,
125
- );
126
-
127
- /*
128
- 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.
129
- */
130
-
131
- if (isset($additional['api_v2']) && $additional['api_v2'] == true) {
132
- unset($additional['api_v2']);
133
- if (isset($additional['content_download']) && $additional['content_download'] == true) {
134
- unset($additional['content_download']);
135
- $headers = array(
136
- 'Authorization: Bearer '.$params['access_token'],
137
- 'Content-Type:',
138
- 'Dropbox-API-Arg: '.json_encode($additional),
139
- );
140
- $additional = '';
141
- } else if (isset($additional['content_upload']) && $additional['content_upload'] == true) {
142
- unset($additional['content_upload']);
143
- $headers = array(
144
- 'Authorization: Bearer '.$params['access_token'],
145
- 'Content-Type: application/octet-stream',
146
- 'Dropbox-API-Arg: '.json_encode($additional),
147
- );
148
- $additional = '';
149
- } else {
150
- $headers = array(
151
- 'Authorization: Bearer '.$params['access_token'],
152
- 'Content-Type: application/json',
153
- );
154
- }
155
- return array(
156
- 'url' => $url . $call,
157
- 'postfields' => $additional,
158
- 'headers' => $headers,
159
- );
160
- }
161
- } else {
162
-
163
- // Generate a random string for the request
164
- $nonce = md5(microtime(true) . uniqid('', true));
165
- $params = array(
166
- 'oauth_consumer_key' => WPTC_DROPBOX_CLIENT_ID,
167
- 'oauth_token' => $this->token->oauth_token,
168
- 'oauth_signature_method' => $this->sigMethod,
169
- 'oauth_version' => '1.0',
170
- // Generate nonce and timestamp if signature method is HMAC-SHA1
171
- 'oauth_timestamp' => ($this->sigMethod == 'HMAC-SHA1') ? time() : null,
172
- 'oauth_nonce' => ($this->sigMethod == 'HMAC-SHA1') ? $nonce : null,
173
- );
174
- }
175
-
176
- // Merge with the additional request parameters
177
- $params = array_merge($params, $additional);
178
- ksort($params);
179
-
180
- // URL encode each parameter to RFC3986 for use in the base string
181
- $encoded = array();
182
- foreach($params as $param => $value) {
183
- if ($value !== null) {
184
- // If the value is a file upload (prefixed with @), replace it with
185
- // the destination filename, the file path will be sent in POSTFIELDS
186
- if (isset($value[0]) && $value[0] === '@') $value = $params['filename'];
187
- # Prevent spurious PHP warning by only doing non-arrays
188
- if (!is_array($value)) $encoded[] = $this->encode($param) . '=' . $this->encode($value);
189
- } else {
190
- unset($params[$param]);
191
- }
192
- }
193
-
194
- // Build the first part of the string
195
- $base = $method . '&' . $this->encode($url . $call) . '&';
196
-
197
- // Re-encode the encoded parameter string and append to $base
198
- $base .= $this->encode(implode('&', $encoded));
199
-
200
- // Concatenate the secrets with an ampersand
201
- $key = WPTC_DROPBOX_CLIENT_SECRET . '&' . $this->token->oauth_token_secret;
202
-
203
- // Get the signature string based on signature method
204
- $signature = $this->getSignature($base, $key);
205
- $params['oauth_signature'] = $signature;
206
-
207
- // Build the signed request URL
208
- $query = '?' . http_build_query($params, '', '&');
209
- return array(
210
- 'url' => $url . $call . $query,
211
- 'postfields' => $params,
212
- );
213
- }
214
-
215
- /**
216
- * Generate the oauth_signature for a request
217
- * @param string $base Signature base string, used by HMAC-SHA1
218
- * @param string $key Concatenated consumer and token secrets
219
- */
220
- private function getSignature($base, $key)
221
- {
222
- switch ($this->sigMethod) {
223
- case 'PLAINTEXT':
224
- $signature = $key;
225
- break;
226
- case 'HMAC-SHA1':
227
- $signature = base64_encode(hash_hmac('sha1', $base, $key, true));
228
- break;
229
- }
230
-
231
- return $signature;
232
- }
233
-
234
- /**
235
- * Set the token to use for OAuth requests
236
- * @param stdtclass $token A key secret pair
237
- */
238
- public function setToken($token)
239
- {
240
-
241
- $this->token = $token;
242
-
243
- return $this;
244
- }
245
-
246
- public function getToken()
247
- {
248
- return $this->token;
249
- }
250
-
251
- public function resetToken()
252
- {
253
- $token = new stdClass;
254
- $token->oauth_token = false;
255
- $token->oauth_token_secret = false;
256
-
257
- $this->setToken($token);
258
-
259
- return $this;
260
- }
261
-
262
- /**
263
- * Set the OAuth signature method
264
- * @param string $method Either PLAINTEXT or HMAC-SHA1
265
- * @return void
266
- */
267
- public function setSignatureMethod($method)
268
- {
269
- $method = strtoupper($method);
270
-
271
- switch ($method) {
272
- case 'PLAINTEXT':
273
- case 'HMAC-SHA1':
274
- $this->sigMethod = $method;
275
- break;
276
- default:
277
- throw new Exception('Unsupported signature method ' . $method);
278
- }
279
- }
280
-
281
- /**
282
- * Set the output file
283
- * @param resource Resource to stream response data to
284
- * @return void
285
- */
286
- public function setOutFile($handle)
287
- {
288
- if (!is_resource($handle) || get_resource_type($handle) != 'stream') {
289
- throw new Exception('Outfile must be a stream resource');
290
- }
291
- $this->outFile = $handle;
292
- }
293
-
294
- /**
295
- * Set the input file
296
- * @param resource Resource to read data from
297
- * @return void
298
- */
299
- public function setInFile($handle)
300
- {
301
- $this->inFile = $handle;
302
- }
303
-
304
- /**
305
- * Parse response parameters for a token into an object
306
- * Dropbox returns tokens in the response parameters, and
307
- * not a JSON encoded object as per other API requests
308
- * @link http://oauth.net/core/1.0/#response_parameters
309
- * @param string $response
310
- * @return object stdClass
311
- */
312
- private function parseTokenString($response)
313
- {
314
- if (!$response)
315
- throw new Exception('Response cannot be null');
316
-
317
- $parts = explode('&', $response);
318
- $token = new stdClass();
319
- foreach ($parts as $part) {
320
- list($k, $v) = explode('=', $part, 2);
321
- $k = strtolower($k);
322
- $token->$k = $v;
323
- }
324
-
325
- return $token;
326
- }
327
-
328
- /**
329
- * Encode a value to RFC3986
330
- * This is a convenience method to decode ~ symbols encoded
331
- * by rawurldecode. This will encode all characters except
332
- * the unreserved set, ALPHA, DIGIT, '-', '.', '_', '~'
333
- * @link http://tools.ietf.org/html/rfc5849#section-3.6
334
- * @param mixed $value
335
- */
336
- private function encode($value)
337
- {
338
- return str_replace('%7E', '~', rawurlencode($value));
339
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
  }
8
  * @subpackage Consumer
9
  */
10
 
11
+ abstract class Dropbox_ConsumerAbstract
12
  {
13
+ // Dropbox web endpoint. v2 API has just dropped the 1/ suffix to the below.
14
+ const WEB_URL = 'https://www.dropbox.com/';
15
+
16
+ // OAuth flow methods
17
+ const AUTHORISE_METHOD = 'oauth2/authorize';
18
+ // Beware - the documentation in one place says oauth2/token/revoke, but that appears to be wrong
19
+ const DEAUTHORISE_METHOD = '2/auth/token/revoke';
20
+ const ACCESS_TOKEN_METHOD = 'oauth2/token';
21
+ // The next endpoint only exists with APIv1
22
+ const OAUTH_UPGRADE = 'oauth2/token_from_oauth1';
23
+
24
+ /**
25
+ * Signature method, either PLAINTEXT or HMAC-SHA1
26
+ * @var string
27
+ */
28
+ private $sigMethod = 'PLAINTEXT';
29
+
30
+ private $token = null;
31
+
32
+ /**
33
+ * Output file handle
34
+ * @var null|resource
35
+ */
36
+ protected $outFile = null;
37
+
38
+ /**
39
+ * Input file handle
40
+ * @var null|resource
41
+ */
42
+ protected $inFile = null;
43
+
44
+ /**
45
+ * Authenticate using 3-legged OAuth flow, firstly
46
+ * checking we don't already have tokens to use
47
+ * @return void
48
+ */
49
+ protected function authenticate()
50
+ {
51
+ global $iwp_backup_core;
52
+
53
+ $access_token = $this->storage->get('access_token');
54
+ //Check if the new token type is set if not they need to be upgraded to OAuth2
55
+ if (!empty($access_token) && isset($access_token->oauth_token) && !isset($access_token->token_type)) {
56
+ $iwp_backup_core->log('OAuth v1 token found: upgrading to v2');
57
+ $this->upgradeOAuth();
58
+ $iwp_backup_core->log('OAuth token upgrade successful');
59
+ }
60
+
61
+ if (empty($access_token) || !isset($access_token->oauth_token)) {
62
+ try {
63
+ $this->getAccessToken();
64
+ } catch(Exception $e) {
65
+ $excep_class = get_class($e);
66
+ // 04-Sep-2015 - Dropbox started throwing a 400, which caused a Dropbox_BadRequestException which previously wasn't being caught
67
+ if ('Dropbox_BadRequestException' == $excep_class || 'Dropbox_Exception' == $excep_class) {
68
+ global $iwp_backup_core;
69
+ $iwp_backup_core->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)');
70
+ //$this->getRequestToken();
71
+ $this->authorise();
72
+ } else {
73
+ throw $e;
74
+ }
75
+ }
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Upgrade the user's OAuth1 token to a OAuth2 token
81
+ * @return void
82
+ */
83
+ private function upgradeOAuth()
84
+ {
85
  // 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)
86
+ $url = IWP_MMB_Dropbox_API::API_URL . self::OAUTH_UPGRADE;
87
+ $response = $this->fetch('POST', $url, '');
88
+ $token = new stdClass();
89
+ /*
90
+ oauth token secret and oauth token were needed by oauth1
91
+ these are replaced in oauth2 with an access token
92
+ currently they are still there just in case a method somewhere is expecting them to both be set
93
+ as far as I can tell only the oauth token is used
94
+ after more testing token secret can be removed.
95
+ */
96
+
97
+ $token->oauth_token_secret = $response['body']->access_token;
98
+ $token->oauth_token = $response['body']->access_token;
99
+ $token->token_type = $response['body']->token_type;
100
+ $this->storage->set($token, 'access_token');
101
+ $this->storage->set('true','upgraded');
102
+ $this->storage->do_unset('request_token');
103
+ }
104
+
105
+ /**
106
+ * Obtain user authorisation
107
+ * The user will be redirected to Dropbox' web endpoint
108
+ * @link http://tools.ietf.org/html/rfc5849#section-2.2
109
+ * @return void
110
+ */
111
+ private function authorise()
112
+ {
113
+ // Only redirect if not using CLI
114
+ if (PHP_SAPI !== 'cli' && (!defined('DOING_CRON') || !DOING_CRON) && (!defined('DOING_AJAX') || !DOING_AJAX)) {
115
+ $url = $this->getAuthoriseUrl();
116
+ if (!headers_sent()) {
117
+ header('Location: ' . $url);
118
+ exit;
119
+ } else {
120
+ throw new Dropbox_Exception(sprintf(__('The %s authentication could not go ahead, because something else on your site is breaking it. Try disabling your other plugins and switching to a default theme. (Specifically, you are looking for the component that sends output (most likely PHP warnings/errors) before the page begins. Turning off any debugging settings may also help).', 'InfiniteWP'), 'Dropbox'));
121
+ }
122
+ ?><?php
123
+ return false;
124
+ }
125
+ global $iwp_backup_core;
126
+ $iwp_backup_core->log('Dropbox reauthorisation needed; but we are running from cron, AJAX or the CLI, so this is not possible');
127
+ $this->storage->do_unset('access_token');
128
+ throw new Dropbox_Exception(sprintf(__('You need to re-authenticate with %s, as your existing credentials are not working.', 'InfiniteWP'), 'Dropbox'));
129
+
130
+ return false;
131
+ }
132
+
133
+ public function setToken($token)
134
+ {
135
+
136
+ $this->token = $token;
137
+
138
+ return $this;
139
+ }
140
+
141
+ /**
142
+ * Build the user authorisation URL
143
+ * @return string
144
+ */
145
+ public function getAuthoriseUrl()
146
+ {
147
+ /*
148
+ Generate a random key to be passed to Dropbox and stored in session to be checked to prevent CSRF
149
+ Uses OpenSSL or Mcrypt or defaults to pure PHP implementaion if neither are available.
150
+ */
151
+
152
+ global $iwp_backup_core;
153
+ if (!function_exists('crypt_random_string')) $iwp_backup_core->ensure_phpseclib('Crypt_Random', 'Crypt/Random');
154
+
155
+ $CSRF = base64_encode(crypt_random_string(16));
156
+ $this->storage->set($CSRF,'CSRF');
157
+
158
+ $appkey = $this->storage->get('appkey');
159
+
160
+ if (!empty($appkey) && 'dropbox:' == substr($appkey, 0, 8)) {
161
+ $key = substr($appkey, 8);
162
+ } else if (!empty($appkey)) {
163
+ $key = $appkey;
164
+ }
165
+
166
+ $params = array(
167
+ 'client_id' => empty($key) ? $this->oauth2_id : $key,
168
+ 'response_type' => 'code',
169
+ 'redirect_uri' => empty($key) ? $this->callback : $this->callbackhome,
170
+ 'state' => empty($key) ? $CSRF.$this->callbackhome : $CSRF,
171
+ );
172
+
173
+ // Build the URL and redirect the user
174
+ $query = '?' . http_build_query($params, '', '&');
175
+ $url = self::WEB_URL . self::AUTHORISE_METHOD . $query;
176
+ return $url;
177
+ }
178
+
179
+ protected function deauthenticate()
180
+ {
181
+ $url = IWP_MMB_Dropbox_API::API_URL_V2 . self::DEAUTHORISE_METHOD;
182
+ $response = $this->fetch('POST', $url, '', array('api_v2' => true));
183
+ $this->storage->delete();
184
+ }
185
+
186
+ /**
187
+ * Acquire an access token
188
+ * Tokens acquired at this point should be stored to
189
+ * prevent having to request new tokens for each API call
190
+ * @link http://tools.ietf.org/html/rfc5849#section-2.3
191
+ */
192
+ public function getAccessToken()
193
+ {
194
+
195
+ // 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.
196
+ $code = $this->storage->get('code');
197
+ /*
198
+ Checks to see if the user is using their own Dropbox App if so then they need to get
199
+ a request token. If they are using our App then we just need to save these details
200
+ */
201
+ if (!empty($code)){
202
+ $appkey = $this->storage->get('appkey');
203
+ if (!empty($appkey)){
204
+ // Get the signed request URL
205
+ $url = IWP_MMB_Dropbox_API::API_URL_V2 . self::ACCESS_TOKEN_METHOD;
206
+ $params = array(
207
+ 'code' => $code,
208
+ 'grant_type' => 'authorization_code',
209
+ 'redirect_uri' => $this->callbackhome,
210
+ 'client_id' => $this->consumerKey,
211
+ 'client_secret' => $this->consumerSecret,
212
+ );
213
+ $response = $this->fetch('POST', $url, '' , $params);
214
+
215
+ $code = json_decode(json_encode($response['body']),true);
216
+
217
+ } else {
218
+ $code = base64_decode($code);
219
+ $code = json_decode($code, true);
220
+ }
221
+
222
+ /*
223
+ Again oauth token secret and oauth token were needed by oauth1
224
+ these are replaced in oauth2 with an access token
225
+ currently they are still there just in case a method somewhere is expecting them to both be set
226
+ as far as I can tell only the oauth token is used
227
+ after more testing token secret can be removed.
228
+ */
229
+
230
+ $token = new stdClass();
231
+ $token->oauth_token_secret = $code['access_token'];
232
+ $token->oauth_token = $code['access_token'];
233
+ $token->account_id = $code['account_id'];
234
+ $token->token_type = $code['token_type'];
235
+ $token->uid = $code['uid'];
236
+ $this->storage->set($token, 'access_token');
237
+ $this->storage->do_unset('upgraded');
238
+
239
+ //reset code
240
+ $this->storage->do_unset('code');
241
+ } else {
242
+ throw new Dropbox_BadRequestException("No Dropbox Code found, will try to get one now", 400);
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Get the request/access token
248
+ * This will return the access/request token depending on
249
+ * which stage we are at in the OAuth flow, or a dummy object
250
+ * if we have not yet started the authentication process
251
+ * @return object stdClass
252
+ */
253
+ public function getToken()
254
+ {
255
+ return $this->token;
256
+ }
257
+ /**
258
+ * Generate signed request URL
259
+ * See inline comments for description
260
+ * @link http://tools.ietf.org/html/rfc5849#section-3.4
261
+ * @param string $method HTTP request method
262
+ * @param string $url API endpoint to send the request to
263
+ * @param string $call API call to send
264
+ * @param array $additional Additional parameters as an associative array
265
+ * @return array
266
+ */
267
+ protected function getSignedRequest($method, $url, $call, array $additional = array())
268
+ {
269
+ // Get the request/access token
270
+ $token = $this->getToken();
271
+
272
+ // Prepare the standard request parameters differently for OAuth1 and OAuth2; we still need OAuth1 to make the request to the upgrade token endpoint
273
+ if (!empty($token)) {
274
+ $params = array(
275
+ 'access_token' => $token,
276
+ );
277
+
278
+ /*
279
+ 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.
280
+ */
281
+
282
+ if (isset($additional['api_v2']) && $additional['api_v2'] == true) {
283
+ unset($additional['api_v2']);
284
+ if (isset($additional['content_download']) && $additional['content_download'] == true) {
285
+ unset($additional['content_download']);
286
+ $headers = array(
287
+ 'Authorization: Bearer '.$params['access_token'],
288
+ 'Content-Type:',
289
+ 'Dropbox-API-Arg: '.json_encode($additional),
290
+ );
291
+ $additional = '';
292
+ } else if (isset($additional['content_upload']) && $additional['content_upload'] == true) {
293
+ unset($additional['content_upload']);
294
+ $headers = array(
295
+ 'Authorization: Bearer '.$params['access_token'],
296
+ 'Content-Type: application/octet-stream',
297
+ 'Dropbox-API-Arg: '.json_encode($additional),
298
+ );
299
+ $additional = '';
300
+ } else {
301
+ $headers = array(
302
+ 'Authorization: Bearer '.$params['access_token'],
303
+ 'Content-Type: application/json',
304
+ );
305
+ }
306
+ return array(
307
+ 'url' => $url . $call,
308
+ 'postfields' => $additional,
309
+ 'headers' => $headers,
310
+ );
311
+ }
312
+ } else {
313
+ // Generate a random string for the request
314
+ $nonce = md5(microtime(true) . uniqid('', true));
315
+ $params = array(
316
+ 'oauth_consumer_key' => $this->consumerKey,
317
+ 'oauth_token' => $token->oauth_token,
318
+ 'oauth_signature_method' => $this->sigMethod,
319
+ 'oauth_version' => '1.0',
320
+ // Generate nonce and timestamp if signature method is HMAC-SHA1
321
+ 'oauth_timestamp' => ($this->sigMethod == 'HMAC-SHA1') ? time() : null,
322
+ 'oauth_nonce' => ($this->sigMethod == 'HMAC-SHA1') ? $nonce : null,
323
+ );
324
+ }
325
+
326
+ // Merge with the additional request parameters
327
+ $params = array_merge($params, $additional);
328
+ ksort($params);
329
+
330
+ // URL encode each parameter to RFC3986 for use in the base string
331
+ $encoded = array();
332
+ foreach($params as $param => $value) {
333
+ if ($value !== null) {
334
+ // If the value is a file upload (prefixed with @), replace it with
335
+ // the destination filename, the file path will be sent in POSTFIELDS
336
+ if (isset($value[0]) && $value[0] === '@') $value = $params['filename'];
337
+ # Prevent spurious PHP warning by only doing non-arrays
338
+ if (!is_array($value)) $encoded[] = $this->encode($param) . '=' . $this->encode($value);
339
+ } else {
340
+ unset($params[$param]);
341
+ }
342
+ }
343
+
344
+ // Build the first part of the string
345
+ $base = $method . '&' . $this->encode($url . $call) . '&';
346
+
347
+ // Re-encode the encoded parameter string and append to $base
348
+ $base .= $this->encode(implode('&', $encoded));
349
+
350
+ // Concatenate the secrets with an ampersand
351
+ $key = $this->consumerSecret . '&' . $token->oauth_token_secret;
352
+
353
+ // Get the signature string based on signature method
354
+ $signature = $this->getSignature($base, $key);
355
+ $params['oauth_signature'] = $signature;
356
+
357
+ // Build the signed request URL
358
+ $query = '?' . http_build_query($params, '', '&');
359
+
360
+ return array(
361
+ 'url' => $url . $call . $query,
362
+ 'postfields' => $params,
363
+ );
364
+ }
365
+
366
+ /**
367
+ * Generate the oauth_signature for a request
368
+ * @param string $base Signature base string, used by HMAC-SHA1
369
+ * @param string $key Concatenated consumer and token secrets
370
+ */
371
+ private function getSignature($base, $key)
372
+ {
373
+ switch ($this->sigMethod) {
374
+ case 'PLAINTEXT':
375
+ $signature = $key;
376
+ break;
377
+ case 'HMAC-SHA1':
378
+ $signature = base64_encode(hash_hmac('sha1', $base, $key, true));
379
+ break;
380
+ }
381
+
382
+ return $signature;
383
+ }
384
+
385
+ /**
386
+ * Set the OAuth signature method
387
+ * @param string $method Either PLAINTEXT or HMAC-SHA1
388
+ * @return void
389
+ */
390
+ public function setSignatureMethod($method)
391
+ {
392
+ $method = strtoupper($method);
393
+
394
+ switch ($method) {
395
+ case 'PLAINTEXT':
396
+ case 'HMAC-SHA1':
397
+ $this->sigMethod = $method;
398
+ break;
399
+ default:
400
+ throw new Dropbox_Exception('Unsupported signature method ' . $method);
401
+ }
402
+ }
403
+
404
+ /**
405
+ * Set the output file
406
+ * @param resource Resource to stream response data to
407
+ * @return void
408
+ */
409
+ public function setOutFile($handle)
410
+ {
411
+ if (!is_resource($handle) || get_resource_type($handle) != 'stream') {
412
+ throw new Dropbox_Exception('Outfile must be a stream resource');
413
+ }
414
+ $this->outFile = $handle;
415
+ }
416
+
417
+ /**
418
+ * Set the input file
419
+ * @param resource Resource to read data from
420
+ * @return void
421
+ */
422
+ public function setInFile($handle) {
423
+ $this->inFile = $handle;
424
+ }
425
+
426
+ /**
427
+ * Parse response parameters for a token into an object
428
+ * Dropbox returns tokens in the response parameters, and
429
+ * not a JSON encoded object as per other API requests
430
+ * @link http://oauth.net/core/1.0/#response_parameters
431
+ * @param string $response
432
+ * @return object stdClass
433
+ */
434
+ private function parseTokenString($response)
435
+ {
436
+ $parts = explode('&', $response);
437
+ $token = new stdClass();
438
+ foreach ($parts as $part) {
439
+ list($k, $v) = explode('=', $part, 2);
440
+ $k = strtolower($k);
441
+ $token->$k = $v;
442
+ }
443
+ return $token;
444
+ }
445
+
446
+ /**
447
+ * Encode a value to RFC3986
448
+ * This is a convenience method to decode ~ symbols encoded
449
+ * by rawurldecode. This will encode all characters except
450
+ * the unreserved set, ALPHA, DIGIT, '-', '.', '_', '~'
451
+ * @link http://tools.ietf.org/html/rfc5849#section-3.6
452
+ * @param mixed $value
453
+ */
454
+ private function encode($value)
455
+ {
456
+ return str_replace('%7E', '~', rawurlencode($value));
457
+ }
458
  }
lib/Dropbox2/OAuth/Consumer/Curl.php CHANGED
@@ -1,121 +1,121 @@
1
  <?php
2
 
3
  /**
4
- * OAuth consumer using PHP cURL
5
- * @author Ben Tadiar <ben@handcraftedbyben.co.uk>
6
- * @link https://github.com/benthedesigner/dropbox
7
- * @package Dropbox\OAuth
8
- * @subpackage Consumer
9
- */
10
- error_reporting(0);
11
- class IWP_Dropbox_OAuth_Consumer_Curl extends IWP_Dropbox_OAuth_Consumer_ConsumerAbstract {
12
-
13
- /**
14
- * Default cURL options
15
- * @var array
16
- */
17
-
18
- protected $defaultOptions = array(
19
- CURLOPT_SSL_VERIFYPEER => true,
20
- CURLOPT_VERBOSE => true,
21
- CURLOPT_HEADER => true,
22
- CURLINFO_HEADER_OUT => false,
23
- CURLOPT_RETURNTRANSFER => true,
24
- CURLOPT_FOLLOWLOCATION => false,
25
- );
26
-
27
- /**
28
- * Store the last response form the API
29
- * @var mixed
30
- */
31
- protected $lastResponse = null;
32
-
33
- /**
34
- * Set properties and begin authentication
35
- * @param string $key
36
- * @param string $secret
37
- */
38
- public function __construct($key, $secret) {
39
- // Check the cURL extension is loaded
40
- if (!extension_loaded('curl')) {
41
- throw new Exception('The cURL OAuth consumer requires the cURL extension');
42
- }
43
-
44
- $this->consumerKey = $key;
45
- $this->consumerSecret = $secret;
46
- }
47
-
48
- /**
49
- * Execute an API call
50
- * @todo Improve error handling
51
- * @param string $method The HTTP method
52
- * @param string $url The API endpoint
53
- * @param string $call The API method to call
54
- * @param array $additional Additional parameters
55
- * @return string|object stdClass
56
- */
57
-
58
-
59
- public function fetch($method, $url, $call, array $additional = array(), $isChunkDownload = array())
60
- {
61
- // Get the signed request URL
62
- $request = $this->getSignedRequest($method, $url, $call, $additional);
63
- if ($request === false) {
64
- throw new Exception("Upgrade failed", 401);
65
- }
66
- // Initialise and execute a cURL request
67
- $handle = curl_init($request['url']);
68
-
69
- // Get the default options array
70
- $options = $this->defaultOptions;
71
- $options[CURLOPT_CAINFO] = dirname(__FILE__) . '/ca-bundle.pem';
72
-
73
- //Disabling this as of now
74
- // if (get_option('updraft_ssl_disableverify')) {
75
- // $options[CURLOPT_SSL_VERIFYPEER] = false;
76
- // } else {
77
- // $options[CURLOPT_SSL_VERIFYPEER] = true;
78
- // }
79
- // if (!defined('WPTC_BRIDGE')) {
80
- if (!defined('WPTC_BRIDGE')) {
81
- if (!class_exists('WP_HTTP_Proxy')){
82
- if (!defined('WPTC_BRIDGE')) {
83
- require_once(ABSPATH.WPINC.'/class-http.php');
84
- } else {
85
- throw new Exception("WP_HTTP_Proxy Class not foound", 500);
86
- }
87
- }
88
- $proxy = new WP_HTTP_Proxy();
89
-
90
- if ($proxy->is_enabled()) {
91
- # WP_HTTP_Proxy returns empty strings if nothing is set
92
- $user = $proxy->username();
93
- $pass = $proxy->password();
94
- $host = $proxy->host();
95
- $port = (int)$proxy->port();
96
- if (empty($port)) $port = 8080;
97
- if (!empty($host) && $proxy->send_through_proxy($request['url'])) {
98
- $options[CURLOPT_PROXY] = $host;
99
- $options[CURLOPT_PROXYTYPE] = CURLPROXY_HTTP;
100
- $options[CURLOPT_PROXYPORT] = $port;
101
- if (!empty($user) && !empty($pass)) {
102
- $options[CURLOPT_PROXYAUTH] = CURLAUTH_ANY;
103
- $options[CURLOPT_PROXYUSERPWD] = sprintf('%s:%s', $user, $pass);
104
- }
105
- }
106
- }
107
- }
108
- if (isset($request['headers'])) $options[CURLOPT_HTTPHEADER] = $request['headers'];
109
-
110
- /*
111
- 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.
112
- */
113
- if (isset($additional['api_v2']) && !empty($request['postfields'])) {
114
- $request['postfields'] = json_encode($request['postfields']);
115
- }
116
-
117
- if ($method == 'GET' && $this->outFile) { // GET
118
- $options[CURLOPT_RETURNTRANSFER] = false;
119
  $options[CURLOPT_HEADER] = false;
120
  $options[CURLOPT_FILE] = $this->outFile;
121
  $options[CURLOPT_BINARYTRANSFER] = true;
@@ -126,170 +126,170 @@ class IWP_Dropbox_OAuth_Consumer_Curl extends IWP_Dropbox_OAuth_Consumer_Consume
126
  */
127
  if (isset($additional['headers'])) $options[CURLOPT_HTTPHEADER] = $additional['headers'];
128
  $this->outFile = null;
129
- } elseif ($method == 'POST' && $this->outFile) { // POST request for download a file
130
- $options[CURLOPT_POST] = true;
131
- $options[CURLOPT_RETURNTRANSFER] = false;
132
- $options[CURLOPT_HEADER] = false;
133
- $options[CURLOPT_FILE] = $this->outFile;
134
- $options[CURLOPT_BINARYTRANSFER] = true;
135
- $options[CURLOPT_FAILONERROR] = true;
136
- $this->outFile = null;
137
- } elseif ($method == 'POST' && $this->inFile) { // POST request for upload a file
138
- $options[CURLOPT_POST] = true;
139
- $options[CURLOPT_POSTFIELDS] = $this->inFile;
140
- } elseif ($method == 'POST') { // POST request
141
- $options[CURLOPT_POST] = true;
142
  $options[CURLOPT_POSTFIELDS] = $request['postfields'];
143
- } elseif ($method == 'PUT' && $this->inFile) { // PUT request
144
- $options[CURLOPT_PUT] = true;
145
- $options[CURLOPT_INFILE] = $this->inFile;
146
- // @todo Update so the data is not loaded into memory to get its size
147
- $options[CURLOPT_INFILESIZE] = strlen(stream_get_contents($this->inFile));
148
- fseek($this->inFile, 0);
149
- $this->inFile = null;
150
- }
151
-
152
-
153
- // Set the cURL options at once
154
- curl_setopt_array($handle, $options);
155
-
156
- // Execute, get any error and close
157
- $response = curl_exec($handle);
158
- $error = curl_error($handle);
159
- $getinfo = curl_getinfo($handle);
160
- curl_close($handle);
161
-
162
- //Check if a cURL error has occured
163
- if ($response === false) {
164
- throw new IWP_Dropbox_CurlException($error);
165
- } else {
166
- // Parse the response if it is a string
167
- if (is_string($response)) {
168
- $response = $this->parse($response);
169
- }
170
-
171
- // Set the last response
172
- $this->lastResponse = $response;
173
-
174
- $code = (!empty($response['code'])) ? $response['code'] : $getinfo['http_code'];
175
-
176
- // The API doesn't return an error message for the 304 status code...
177
- // 304's are only returned when the path supplied during metadata calls has not been modified
178
- if ($code == 304) {
179
- $response['body'] = new stdClass;
180
- $response['body']->error = 'The folder contents have not changed';
181
- }
182
-
183
- // Check if an error occurred and throw an Exception
184
- if (!empty($response['body']->error) || $code >= 400) {
185
- // Dropbox returns error messages inconsistently...
186
- if (!empty($response['body']->error) && $response['body']->error instanceof stdClass) {
187
- $array = array_values((array) $response['body']->error);
188
- //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.
189
- if (strpos($array[0] , 'incorrect_offset') !== false) {
190
- $message = json_encode($array);
191
- } elseif (strpos($array[0] , 'lookup_failed') !== false ) {
192
- //re-structure the array so it is correctly formatted for API
193
- //Note: Dropbox v2 returns different errors at different stages hence this fix
194
- $correctOffset = array(
195
- '0' => $array[1]->{'.tag'},
196
- '1' => $array[1]->correct_offset
197
- );
198
-
199
- $message = json_encode($correctOffset);
200
- } else {
201
- $message = $array[0];
202
- }
203
- } elseif (!empty($response['body']->error)) {
204
- $message = $response['body']->error;
205
- } elseif (is_string($response['body'])) {
206
  // 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
207
  $message = $response['body'];
208
- } else {
209
  $message = "HTTP bad response code: $code";
210
- }
211
-
212
- // Throw an Exception with the appropriate with the appropriate message and code
213
- switch ($code) {
214
- case 304:
215
- throw new IWP_Dropbox_NotModifiedException($message, 304);
216
- case 400:
217
- throw new IWP_Dropbox_BadRequestException($message, 400);
218
- case 404:
219
- throw new IWP_Dropbox_NotFoundException($message, 404);
220
- case 406:
221
- throw new IWP_Dropbox_NotAcceptableException($message, 406);
222
- case 415:
223
- throw new IWP_Dropbox_UnsupportedMediaTypeException($message, 415);
224
- case 401:
225
- //401 means oauth token is expired continue to manually handle the exception depending on the situation
226
- throw new IWP_Dropbox_TokenExpired($message, 401);
227
- // continue;
228
- case 409:
229
- //409 in API V2 every error will return with a 409 to find out what the error is the error description should be checked.
230
- throw new IWP_Dropbox_Exception($message, $code);
231
- default:
232
- throw new IWP_Dropbox_Exception($message, $code);
233
- }
234
- }
235
-
236
- return $response;
237
- }
238
- }
239
- /**
240
- * Parse a cURL response
241
- * @param string $response
242
- * @return array
243
- */
244
- private function parse($response)
245
- {
246
- // Explode the response into headers and body parts (separated by double EOL)
247
- list($headers, $response) = explode("\r\n\r\n", $response, 2);
248
-
249
- // Explode response headers
250
- $lines = explode("\r\n", $headers);
251
-
252
- // If the status code is 100, the API server must send a final response
253
- // We need to explode the response again to get the actual response
254
- if (preg_match('#^HTTP/1.1 100#i', $lines[0])) {
255
- list($headers, $response) = explode("\r\n\r\n", $response, 2);
256
- $lines = explode("\r\n", $headers);
257
- }
258
-
259
- // Get the HTTP response code from the first line
260
- $first = array_shift($lines);
261
- $pattern = '#^HTTP/1.1 ([0-9]{3})#i';
262
- preg_match($pattern, $first, $matches);
263
- $code = $matches[1];
264
-
265
- // Parse the remaining headers into an associative array
266
- $headers = array();
267
- foreach ($lines as $line) {
268
- list($k, $v) = explode(': ', $line, 2);
269
- $headers[strtolower($k)] = $v;
270
- }
271
-
272
- // If the response body is not a JSON encoded string
273
- // we'll return the entire response body
274
- if (!$body = json_decode($response)) {
275
- $body = $response;
276
- }
277
-
278
- if (is_string($body)) {
279
- $body_lines = explode("\r\n", $body);
280
- if (preg_match('#^HTTP/1.1 100#i', $body_lines[0]) && preg_match('#^HTTP/1.#i', $body_lines[2])) {
281
- return $this->parse($body);
282
- }
283
- }
284
-
285
- return array('code' => $code, 'body' => $body, 'headers' => $headers);
286
- }
287
-
288
- /**
289
- * Return the response for the last API request
290
- * @return mixed
291
- */
292
- public function getlastResponse() {
293
- return $this->lastResponse;
294
- }
 
295
  }
1
  <?php
2
 
3
  /**
4
+ * OAuth consumer using PHP cURL
5
+ * @author Ben Tadiar <ben@handcraftedbyben.co.uk>
6
+ * @link https://github.com/benthedesigner/dropbox
7
+ * @package Dropbox\OAuth
8
+ * @subpackage Consumer
9
+ */
10
+
11
+ class Dropbox_Curl extends Dropbox_ConsumerAbstract
12
+ {
13
+ /**
14
+ * Default cURL options
15
+ * @var array
16
+ */
17
+ protected $defaultOptions = array(
18
+ CURLOPT_VERBOSE => true,
19
+ CURLOPT_HEADER => true,
20
+ CURLINFO_HEADER_OUT => false,
21
+ CURLOPT_RETURNTRANSFER => true,
22
+ CURLOPT_FOLLOWLOCATION => false,
23
+ );
24
+
25
+ /**
26
+ * Store the last response form the API
27
+ * @var mixed
28
+ */
29
+ protected $lastResponse = null;
30
+
31
+ /**
32
+ * Set properties and begin authentication
33
+ * @param string $key
34
+ * @param string $secret
35
+ * @param \Dropbox\OAuth\Consumer\StorageInterface $storage
36
+ * @param string $callback
37
+ */
38
+ public function __construct($key, $oauth2_id, $secret, Dropbox_StorageInterface $storage, $callback = null, $callbackhome = null, $deauthenticate = false)
39
+ {
40
+ // Check the cURL extension is loaded
41
+ if (!extension_loaded('curl')) {
42
+ 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.');
43
+ }
44
+
45
+ $this->consumerKey = $key;
46
+ $this->oauth2_id = $oauth2_id;
47
+ $this->consumerSecret = $secret;
48
+ $this->storage = $storage;
49
+ $this->callback = $callback;
50
+ $this->callbackhome = $callbackhome;
51
+
52
+ // if ($deauthenticate) {
53
+ // $this->deauthenticate();
54
+ // } else {
55
+ // $this->authenticate();
56
+ // }
57
+ }
58
+
59
+ /**
60
+ * Execute an API call
61
+ * @todo Improve error handling
62
+ * @param string $method The HTTP method
63
+ * @param string $url The API endpoint
64
+ * @param string $call The API method to call
65
+ * @param array $additional Additional parameters
66
+ * @return string|object stdClass
67
+ */
68
+ public function fetch($method, $url, $call, array $additional = array())
69
+ {
70
+ // Get the signed request URL
71
+ $request = $this->getSignedRequest($method, $url, $call, $additional);
72
+
73
+ // Initialise and execute a cURL request
74
+ $handle = curl_init($request['url']);
75
+
76
+ // Get the default options array
77
+ $options = $this->defaultOptions;
78
+ if (!IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_useservercerts')) {
79
+ $options[CURLOPT_CAINFO] = $GLOBALS['iwp_mmb_plugin_dir'].'/lib/cacert.pem';
80
+ }
81
+ if (IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_disableverify')) {
82
+ $options[CURLOPT_SSL_VERIFYPEER] = false;
83
+ } else {
84
+ $options[CURLOPT_SSL_VERIFYPEER] = true;
85
+ }
86
+
87
+ if (!class_exists('WP_HTTP_Proxy')) require_once(ABSPATH.WPINC.'/class-http.php');
88
+ $proxy = new WP_HTTP_Proxy();
89
+
90
+ if ($proxy->is_enabled()) {
91
+ # WP_HTTP_Proxy returns empty strings if nothing is set
92
+ $user = $proxy->username();
93
+ $pass = $proxy->password();
94
+ $host = $proxy->host();
95
+ $port = (int)$proxy->port();
96
+ if (empty($port)) $port = 8080;
97
+ if (!empty($host) && $proxy->send_through_proxy($request['url'])) {
98
+ $options[CURLOPT_PROXY] = $host;
99
+ $options[CURLOPT_PROXYTYPE] = CURLPROXY_HTTP;
100
+ $options[CURLOPT_PROXYPORT] = $port;
101
+ if (!empty($user) && !empty($pass)) {
102
+ $options[CURLOPT_PROXYAUTH] = CURLAUTH_ANY;
103
+ $options[CURLOPT_PROXYUSERPWD] = sprintf('%s:%s', $user, $pass);
104
+ }
105
+ }
106
+ }
107
+
108
+ if (isset($request['headers'])) $options[CURLOPT_HTTPHEADER] = $request['headers'];
109
+
110
+ /*
111
+ 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.
112
+ */
113
+ if (isset($additional['api_v2']) && !empty($request['postfields'])) {
114
+ $request['postfields'] = json_encode($request['postfields']);
115
+ }
116
+
117
+ if ($method == 'GET' && $this->outFile) { // GET
118
+ $options[CURLOPT_RETURNTRANSFER] = false;
119
  $options[CURLOPT_HEADER] = false;
120
  $options[CURLOPT_FILE] = $this->outFile;
121
  $options[CURLOPT_BINARYTRANSFER] = true;
126
  */
127
  if (isset($additional['headers'])) $options[CURLOPT_HTTPHEADER] = $additional['headers'];
128
  $this->outFile = null;
129
+ } elseif ($method == 'POST' && $this->outFile) { // POST
130
+ $options[CURLOPT_POST] = true;
131
+ $options[CURLOPT_RETURNTRANSFER] = false;
132
+ $options[CURLOPT_HEADER] = false;
133
+ $options[CURLOPT_FILE] = $this->outFile;
134
+ $options[CURLOPT_BINARYTRANSFER] = true;
135
+ $options[CURLOPT_FAILONERROR] = true;
136
+ $this->outFile = null;
137
+ } elseif ($method == 'POST' && $this->inFile) { // POST
138
+ $options[CURLOPT_POST] = true;
139
+ $options[CURLOPT_POSTFIELDS] = $this->inFile;
140
+ } elseif ($method == 'POST') { // POST
141
+ $options[CURLOPT_POST] = true;
142
  $options[CURLOPT_POSTFIELDS] = $request['postfields'];
143
+ } elseif ($method == 'PUT' && $this->inFile) { // PUT
144
+ $options[CURLOPT_PUT] = true;
145
+ $options[CURLOPT_INFILE] = $this->inFile;
146
+ // @todo Update so the data is not loaded into memory to get its size
147
+ $options[CURLOPT_INFILESIZE] = strlen(stream_get_contents($this->inFile));
148
+ fseek($this->inFile, 0);
149
+ $this->inFile = null;
150
+ }
151
+
152
+ // Set the cURL options at once
153
+ curl_setopt_array($handle, $options);
154
+ // Execute, get any error and close
155
+ $response = curl_exec($handle);
156
+ $error = curl_error($handle);
157
+ $getinfo = curl_getinfo($handle);
158
+
159
+ curl_close($handle);
160
+
161
+ //Check if a cURL error has occured
162
+ if ($response === false) {
163
+ throw new Dropbox_CurlException($error);
164
+ } else {
165
+ // Parse the response if it is a string
166
+ if (is_string($response)) {
167
+ $response = $this->parse($response);
168
+ }
169
+
170
+ // Set the last response
171
+ $this->lastResponse = $response;
172
+
173
+ $code = (!empty($response['code'])) ? $response['code'] : $getinfo['http_code'];
174
+
175
+ // The API doesn't return an error message for the 304 status code...
176
+ // 304's are only returned when the path supplied during metadata calls has not been modified
177
+ if ($code == 304) {
178
+ $response['body'] = new stdClass;
179
+ $response['body']->error = 'The folder contents have not changed';
180
+ }
181
+
182
+ // Check if an error occurred and throw an Exception
183
+ if (!empty($response['body']->error) || $code >= 400) {
184
+ // Dropbox returns error messages inconsistently...
185
+ if (!empty($response['body']->error) && $response['body']->error instanceof stdClass) {
186
+ $array = array_values((array) $response['body']->error);
187
+ //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.
188
+ if (strpos($array[0] , 'incorrect_offset') !== false) {
189
+ $message = json_encode($array);
190
+ } elseif (strpos($array[0] , 'lookup_failed') !== false ) {
191
+ //re-structure the array so it is correctly formatted for API
192
+ //Note: Dropbox v2 returns different errors at different stages hence this fix
193
+ $correctOffset = array(
194
+ '0' => $array[1]->{'.tag'},
195
+ '1' => $array[1]->correct_offset
196
+ );
197
+
198
+ $message = json_encode($correctOffset);
199
+ } else {
200
+ $message = $array[0];
201
+ }
202
+ } elseif (!empty($response['body']->error)) {
203
+ $message = $response['body']->error;
204
+ } elseif (is_string($response['body'])) {
 
205
  // 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
206
  $message = $response['body'];
207
+ } else {
208
  $message = "HTTP bad response code: $code";
209
+ }
210
+
211
+ // Throw an Exception with the appropriate with the appropriate message and code
212
+ switch ($code) {
213
+ case 304:
214
+ throw new Dropbox_NotModifiedException($message, 304);
215
+ case 400:
216
+ throw new Dropbox_BadRequestException($message, 400);
217
+ case 404:
218
+ throw new Dropbox_NotFoundException($message, 404);
219
+ case 406:
220
+ throw new Dropbox_NotAcceptableException($message, 406);
221
+ case 415:
222
+ throw new Dropbox_UnsupportedMediaTypeException($message, 415);
223
+ case 401:
224
+ //401 means oauth token is expired continue to manually handle the exception depending on the situation
225
+ continue;
226
+ case 409:
227
+ //409 in API V2 every error will return with a 409 to find out what the error is the error description should be checked.
228
+ throw new Dropbox_Exception($message, $code);
229
+ default:
230
+ throw new Dropbox_Exception($message, $code);
231
+ }
232
+ }
233
+
234
+ return $response;
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Parse a cURL response
240
+ * @param string $response
241
+ * @return array
242
+ */
243
+ private function parse($response)
244
+ {
245
+ // Explode the response into headers and body parts (separated by double EOL)
246
+ list($headers, $response) = explode("\r\n\r\n", $response, 2);
247
+
248
+ // Explode response headers
249
+ $lines = explode("\r\n", $headers);
250
+
251
+ // If the status code is 100, the API server must send a final response
252
+ // We need to explode the response again to get the actual response
253
+ if (preg_match('#^HTTP/1.1 100#i', $lines[0])) {
254
+ list($headers, $response) = explode("\r\n\r\n", $response, 2);
255
+ $lines = explode("\r\n", $headers);
256
+ }
257
+
258
+ // Get the HTTP response code from the first line
259
+ $first = array_shift($lines);
260
+ $pattern = '#^HTTP/1.1 ([0-9]{3})#i';
261
+ preg_match($pattern, $first, $matches);
262
+ $code = $matches[1];
263
+
264
+ // Parse the remaining headers into an associative array
265
+ $headers = array();
266
+ foreach ($lines as $line) {
267
+ list($k, $v) = explode(': ', $line, 2);
268
+ $headers[strtolower($k)] = $v;
269
+ }
270
+
271
+ // If the response body is not a JSON encoded string
272
+ // we'll return the entire response body
273
+ if (!$body = json_decode($response)) {
274
+ $body = $response;
275
+ }
276
+
277
+ if (is_string($body)) {
278
+ $body_lines = explode("\r\n", $body);
279
+ if (preg_match('#^HTTP/1.1 100#i', $body_lines[0]) && preg_match('#^HTTP/1.#i', $body_lines[2])) {
280
+ return $this->parse($body);
281
+ }
282
+ }
283
+
284
+ return array('code' => $code, 'body' => $body, 'headers' => $headers);
285
+ }
286
+
287
+ /**
288
+ * Return the response for the last API request
289
+ * @return mixed
290
+ */
291
+ public function getlastResponse()
292
+ {
293
+ return $this->lastResponse;
294
+ }
295
  }
lib/Dropbox2/OAuth/Consumer/WordPress.php ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * OAuth consumer using the WordPress API
5
+ * @author David Anderson <david@updraftplus.com>
6
+ * @link https://github.com/DavidAnderson684/Dropbox
7
+ * @package Dropbox\OAuth
8
+ * @subpackage Consumer
9
+ */
10
+
11
+ class Dropbox_ConsumerWordPress extends Dropbox_ConsumerAbstract
12
+ {
13
+
14
+ /**
15
+ * Set properties and begin authentication
16
+ * @param string $key
17
+ * @param string $secret
18
+ * @param \Dropbox\OAuth\Consumer\StorageInterface $storage
19
+ * @param string $callback
20
+ */
21
+ public function __construct($key, $secret, Dropbox_StorageInterface $storage, $callback = null)
22
+ {
23
+ // Check we are in a WordPress environment
24
+ if (!defined('ABSPATH')) {
25
+ throw new Dropbox_Exception('The WordPress OAuth consumer requires a WordPress environment');
26
+ }
27
+
28
+ $this->consumerKey = $key;
29
+ $this->consumerSecret = $secret;
30
+ $this->storage = $storage;
31
+ $this->callback = $callback;
32
+ $this->authenticate();
33
+ }
34
+
35
+ /**
36
+ * Execute an API call
37
+ * @param string $method The HTTP method
38
+ * @param string $url The API endpoint
39
+ * @param string $call The API method to call
40
+ * @param array $additional Additional parameters
41
+ * @return array
42
+ */
43
+ public function fetch($method, $url, $call, array $additional = array())
44
+ {
45
+ // Get the signed request URL
46
+ $request = $this->getSignedRequest($method, $url, $call, $additional);
47
+ if ($method == 'GET') {
48
+ $args = array ( );
49
+ $response = wp_remote_get($request['url'], $args);
50
+ $this->outFile = null;
51
+ } elseif ($method == 'POST') {
52
+ $args = array( 'body' => $request['postfields'] );
53
+ $response = wp_remote_post($request['url'], $args );
54
+ } elseif ($method == 'PUT' && $this->inFile) {
55
+ return new WP_Error('unsupported', "WordPress does not have a native HTTP PUT function");
56
+ }
57
+
58
+ // If the response body is not a JSON encoded string
59
+ // we'll return the entire response body
60
+ // Important to do this first, as the next section relies on the decoding having taken place
61
+ if (!$body = json_decode(wp_remote_retrieve_body($response))) {
62
+ $body = wp_remote_retrieve_body($response);
63
+ }
64
+
65
+ // Check if an error occurred and throw an Exception. This is part of the authentication process - don't modify.
66
+ if (!empty($body->error)) {
67
+ $message = $body->error . ' (Status Code: ' . wp_remote_retrieve_response_code($response) . ')';
68
+ throw new Dropbox_Exception($message);
69
+ }
70
+
71
+ if (is_wp_error($response)) {
72
+ $message = $response->get_error_message();
73
+ throw new Dropbox_Exception($message);
74
+ }
75
+
76
+ $results = array ( 'body' => $body, 'code' => wp_remote_retrieve_response_code($response), 'headers' => $response['headers'] );
77
+ return $results;
78
+ }
79
+
80
+ }
lib/Dropbox2/OAuth/Storage/Encrypter.php ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * This class provides the functionality to encrypt
5
+ * and decrypt access tokens stored by the application
6
+ * @author Ben Tadiar <ben@handcraftedbyben.co.uk>
7
+ * @link https://github.com/benthedesigner/dropbox
8
+ * @package Dropbox\Oauth
9
+ * @subpackage Storage
10
+ */
11
+
12
+ /*
13
+ Using this was fairly pointless (it encrypts storage credentials at rest). But, it's implemented now, so needs supporting.
14
+ Investigation shows that mcrypt and phpseclib native encryption using different padding schemes.
15
+ 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.
16
+ 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.
17
+ 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.
18
+ */
19
+
20
+ class Dropbox_Encrypter
21
+ {
22
+ // Encryption settings - default settings yield encryption to AES (256-bit) standard
23
+ // @todo Provide PHPDOC for each class constant
24
+ const KEY_SIZE = 32;
25
+ const IV_SIZE = 16;
26
+
27
+ /**
28
+ * Encryption key
29
+ * @var null|string
30
+ */
31
+ private $key = null;
32
+
33
+ /**
34
+ * Check Mcrypt is loaded and set the encryption key
35
+ * @param string $key
36
+ * @return void
37
+ */
38
+ public function __construct($key)
39
+ {
40
+ if (preg_match('/^[A-Za-z0-9]+$/', $key) && $length = strlen($key) === self::KEY_SIZE) {
41
+ # Short-cut so that the mbstring extension is not required
42
+ $this->key = $key;
43
+ } elseif (($length = mb_strlen($key, '8bit')) !== self::KEY_SIZE) {
44
+ throw new Dropbox_Exception('Expecting a ' . self::KEY_SIZE . ' byte key, got ' . $length);
45
+ } else {
46
+ // Set the encryption key
47
+ $this->key = $key;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Encrypt the OAuth token
53
+ * @param \stdClass $token Serialized token object
54
+ * @return string
55
+ */
56
+ public function encrypt($token)
57
+ {
58
+
59
+ // Encryption: we always use phpseclib for this
60
+
61
+ global $iwp_backup_core;
62
+ $iwp_backup_core->ensure_phpseclib('Crypt_AES', 'Crypt/AES');
63
+ $iwp_backup_core->ensure_phpseclib('Crypt_Rijndael', 'Crypt/Rijndael');
64
+
65
+ if (!function_exists('crypt_random_string')) require_once($GLOBALS['iwp_mmb_plugin_dir'].'/vendor/phpseclib/phpseclib/phpseclib/Crypt/Random.php');
66
+
67
+ $iv = crypt_random_string(self::IV_SIZE);
68
+
69
+ // Defaults to CBC mode
70
+ $rijndael = new Crypt_Rijndael();
71
+
72
+ $rijndael->setKey($this->key);
73
+
74
+ $rijndael->setIV($iv);
75
+
76
+ $cipherText = $rijndael->encrypt($token);
77
+
78
+ return base64_encode($iv . $cipherText);
79
+ }
80
+
81
+ /**
82
+ * Decrypt the ciphertext
83
+ * @param string $cipherText
84
+ * @return object \stdClass Unserialized token
85
+ */
86
+ public function decrypt($cipherText)
87
+ {
88
+
89
+ // Decryption: prefer mcrypt, if available (since it can decrypt data encrypted by either mcrypt or phpseclib)
90
+
91
+ $cipherText = base64_decode($cipherText);
92
+ $iv = substr($cipherText, 0, self::IV_SIZE);
93
+ $cipherText = substr($cipherText, self::IV_SIZE);
94
+
95
+ if (function_exists('mcrypt_decrypt')) {
96
+ $token = @mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $this->key, $cipherText, MCRYPT_MODE_CBC, $iv);
97
+ } else {
98
+ global $iwp_backup_core;
99
+ $iwp_backup_core->ensure_phpseclib('Crypt_Rijndael', 'Crypt/Rijndael');
100
+
101
+ $rijndael = new Crypt_Rijndael();
102
+ $rijndael->setKey($this->key);
103
+ $rijndael->setIV($iv);
104
+ $token = $rijndael->decrypt($cipherText);
105
+ }
106
+
107
+ return $token;
108
+ }
109
+ }
lib/Dropbox2/OAuth/Storage/StorageInterface.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * OAuth storage handler interface
5
+ * @author Ben Tadiar <ben@handcraftedbyben.co.uk>
6
+ * @link https://github.com/benthedesigner/dropbox
7
+ * @package Dropbox\OAuth
8
+ * @subpackage Storage
9
+ */
10
+
11
+ interface Dropbox_StorageInterface
12
+ {
13
+ /**
14
+ * Get a token by type
15
+ * @param string $type Token type to retrieve
16
+ */
17
+ public function get($type);
18
+
19
+ /**
20
+ * Set a token by type
21
+ * @param \stdClass $token Token object to set
22
+ * @param string $type Token type
23
+ */
24
+ public function set($token, $type);
25
+
26
+ /**
27
+ * Delete tokens for the current session/user
28
+ */
29
+ public function delete();
30
+ }
lib/Dropbox2/OAuth/Storage/WordPress.php ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * OAuth storage handler using WordPress options
5
+ * This can only be used if you have a WordPress environment loaded, such that the (get|update|delete)_option functions are available
6
+ * See an example usage in http://wordpress.org/extend/plugins/updraftplus
7
+ * @author David Anderson <david@updraftplus.com>
8
+ * @link https://updraftplus.com
9
+ * @package Dropbox\Oauth
10
+ * @subpackage Storage
11
+ */
12
+
13
+ class Dropbox_WordPress implements Dropbox_StorageInterface
14
+ {
15
+ /**
16
+ * Option name
17
+ * @var string
18
+ */
19
+ protected $option_name_prefix = 'dropbox_token';
20
+
21
+ /**
22
+ * Option name (array storage)
23
+ * @var string
24
+ */
25
+ protected $option_array = '';
26
+
27
+ /**
28
+ * Encyption object
29
+ * @var Encrypter|null
30
+ */
31
+ protected $encrypter = null;
32
+
33
+ /**
34
+ * Backup module object
35
+ * @var Backup_module_object|null
36
+ */
37
+ protected $backup_module_object = null;
38
+
39
+ /**
40
+ * Check if an instance of the encrypter is passed, set the encryption object
41
+ * @return void
42
+ */
43
+ public function __construct(Dropbox_Encrypter $encrypter = null, $option_name_prefix = 'dropbox_token', $option_array = 'dropbox', $backup_module_object)
44
+ {
45
+ if ($encrypter instanceof Dropbox_Encrypter) {
46
+ $this->encrypter = $encrypter;
47
+ }
48
+
49
+ if ($backup_module_object instanceof IWP_MMB_UploadModule) {
50
+ $this->backup_module_object = $backup_module_object;
51
+ }
52
+
53
+ $this->option_name_prefix = $option_name_prefix;
54
+ $this->option_array = $option_array;
55
+
56
+ }
57
+
58
+ /**
59
+ * Get an entry from the Dropbox options in the database
60
+ * If the encryption object is set then decrypt the token before returning
61
+ * @param string $type is the key to retrieve
62
+ * @return array|bool
63
+ */
64
+ public function get($type)
65
+ {
66
+ if ($type != 'request_token' && $type != 'access_token' && $type != 'appkey' && $type != 'CSRF' && $type != 'code') {
67
+ throw new Dropbox_Exception("Expected a type of either 'request_token', 'access_token', 'CSRF' or 'code', got '$type'");
68
+ } else {
69
+ if (false !== ($opts = $this->backup_module_object->get_options())) {
70
+ if ($type == 'request_token' || $type == 'access_token'){
71
+ if (!empty($opts[$this->option_name_prefix.$type])) {
72
+ $gettoken = $opts[$this->option_name_prefix.$type];
73
+ $token = $gettoken;
74
+ return $token;
75
+ }
76
+ } else {
77
+ if (!empty($opts[$type])) {
78
+ return $opts[$type];
79
+ }
80
+ }
81
+ }
82
+ return false;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Set a value in the database by type
88
+ * If the value is a token and the encryption object is set then encrypt the token before storing
89
+ * @param \stdClass Token object to set
90
+ * @param string $type Token type
91
+ * @return void
92
+ */
93
+ public function set($token, $type)
94
+ {
95
+ if ($type != 'request_token' && $type != 'access_token' && $type != 'upgraded' && $type != 'CSRF' && $type != 'code') {
96
+ throw new Dropbox_Exception("Expected a type of either 'request_token', 'access_token', 'CSRF', 'upgraded' or 'code', got '$type'");
97
+ } else {
98
+
99
+ $opts = $this->backup_module_object->get_options();
100
+
101
+ if ($type == 'access_token'){
102
+ $token = $this->encrypt($token);
103
+ $opts[$this->option_name_prefix.$type] = $token;
104
+ } else if ($type == 'request_token' ) {
105
+ $opts[$this->option_name_prefix.$type] = $token;
106
+ } else {
107
+ $opts[$type] = $token;
108
+ }
109
+
110
+ $this->backup_module_object->set_options($opts, true);
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Remove a value in the database by type rather than setting to null / empty
116
+ * set the value to null here so that when it gets to the options filter it will
117
+ * unset the value there, this avoids a bug where if the value is not set then
118
+ * the option filter will take the value from the database and save that version back.
119
+ *
120
+ * N.B. Before PHP 7.0, you can't call a method name unset()
121
+ *
122
+ * @param string $type Token type
123
+ * @return void
124
+ */
125
+ public function do_unset($type)
126
+ {
127
+ if ($type != 'request_token' && $type != 'access_token' && $type != 'upgraded' && $type != 'CSRF' && $type != 'code') {
128
+ throw new Dropbox_Exception("Expected a type of either 'request_token', 'access_token', 'CSRF', 'upgraded' or 'code', got '$type'");
129
+ } else {
130
+
131
+ $opts = $this->backup_module_object->get_options();
132
+
133
+ if ($type == 'access_token' || $type == 'request_token'){
134
+ $opts[$this->option_name_prefix.$type] = null;
135
+ } else {
136
+ $opts[$type] = null;
137
+ }
138
+ $this->backup_module_object->set_options($opts, true);
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Delete the request and access tokens currently stored in the database
144
+ * @return bool
145
+ */
146
+ public function delete()
147
+ {
148
+ $opts = $this->backup_module_object->get_options();
149
+ $opts[$this->option_name_prefix.'request_token'] = null;
150
+ $opts[$this->option_name_prefix.'access_token'] = null;
151
+ unset($opts['ownername']);
152
+ unset($opts['upgraded']);
153
+ $this->backup_module_object->set_options($opts, true);
154
+ return true;
155
+ }
156
+
157
+ /**
158
+ * Use the Encrypter to encrypt a token and return it
159
+ * If there is not encrypter object, return just the
160
+ * serialized token object for storage
161
+ * @param stdClass $token OAuth token to encrypt
162
+ * @return stdClass|string
163
+ */
164
+ protected function encrypt($token)
165
+ {
166
+ // Serialize the token object
167
+ $token = serialize($token);
168
+
169
+ // Encrypt the token if there is an Encrypter instance
170
+ if ($this->encrypter instanceof Dropbox_Encrypter) {
171
+ $token = $this->encrypter->encrypt($token);
172
+ }
173
+
174
+ // Return the token
175
+ return $token;
176
+ }
177
+
178
+ /**
179
+ * Decrypt a token using the Encrypter object and return it
180
+ * If there is no Encrypter object, assume the token was stored
181
+ * serialized and return the unserialized token object
182
+ * @param stdClass $token OAuth token to encrypt
183
+ * @return stdClass|string
184
+ */
185
+ protected function decrypt($token)
186
+ {
187
+ // Decrypt the token if there is an Encrypter instance
188
+ if ($this->encrypter instanceof Dropbox_Encrypter) {
189
+ $token = $this->encrypter->decrypt($token);
190
+ }
191
+
192
+ // Return the unserialized token
193
+ return @unserialize($token);
194
+ }
195
+ }
lib/Google2/Auth/Abstract.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2010 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ if (!class_exists('Google_Client')) {
19
+ require_once dirname(__FILE__) . '/../autoload.php';
20
+ }
21
+
22
+ /**
23
+ * Abstract class for the Authentication in the API client
24
+ * @author Chris Chabot <chabotc@google.com>
25
+ *
26
+ */
27
+ abstract class Google_Auth_Abstract
28
+ {
29
+ /**
30
+ * An utility function that first calls $this->auth->sign($request) and then
31
+ * executes makeRequest() on that signed request. Used for when a request
32
+ * should be authenticated
33
+ * @param Google_Http_Request $request
34
+ * @return Google_Http_Request $request
35
+ */
36
+ abstract public function authenticatedRequest(Google_Http_Request $request);
37
+ abstract public function sign(Google_Http_Request $request);
38
+ }
lib/Google2/Auth/AppIdentity.php ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2014 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ /*
19
+ * WARNING - this class depends on the Google App Engine PHP library
20
+ * which is 5.3 and above only, so if you include this in a PHP 5.2
21
+ * setup or one without 5.3 things will blow up.
22
+ */
23
+ use google\appengine\api\app_identity\AppIdentityService;
24
+
25
+ if (!class_exists('Google_Client')) {
26
+ require_once dirname(__FILE__) . '/../autoload.php';
27
+ }
28
+
29
+ /**
30
+ * Authentication via the Google App Engine App Identity service.
31
+ */
32
+ class Google_Auth_AppIdentity extends Google_Auth_Abstract
33
+ {
34
+ const CACHE_PREFIX = "Google_Auth_AppIdentity::";
35
+ private $client;
36
+ private $token = false;
37
+ private $tokenScopes = false;
38
+
39
+ public function __construct(Google_Client $client, $config = null)
40
+ {
41
+ $this->client = $client;
42
+ }
43
+
44
+ /**
45
+ * Retrieve an access token for the scopes supplied.
46
+ */
47
+ public function authenticateForScope($scopes)
48
+ {
49
+ if ($this->token && $this->tokenScopes == $scopes) {
50
+ return $this->token;
51
+ }
52
+
53
+ $cacheKey = self::CACHE_PREFIX;
54
+ if (is_string($scopes)) {
55
+ $cacheKey .= $scopes;
56
+ } else if (is_array($scopes)) {
57
+ $cacheKey .= implode(":", $scopes);
58
+ }
59
+
60
+ $this->token = $this->client->getCache()->get($cacheKey);
61
+ if (!$this->token) {
62
+ $this->retrieveToken($scopes, $cacheKey);
63
+ } else if ($this->token['expiration_time'] < time()) {
64
+ $this->client->getCache()->delete($cacheKey);
65
+ $this->retrieveToken($scopes, $cacheKey);
66
+ }
67
+
68
+ $this->tokenScopes = $scopes;
69
+ return $this->token;
70
+ }
71
+
72
+ /**
73
+ * Retrieve a new access token and store it in cache
74
+ * @param mixed $scopes
75
+ * @param string $cacheKey
76
+ */
77
+ private function retrieveToken($scopes, $cacheKey)
78
+ {
79
+ $this->token = AppIdentityService::getAccessToken($scopes);
80
+ if ($this->token) {
81
+ $this->client->getCache()->set(
82
+ $cacheKey,
83
+ $this->token
84
+ );
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Perform an authenticated / signed apiHttpRequest.
90
+ * This function takes the apiHttpRequest, calls apiAuth->sign on it
91
+ * (which can modify the request in what ever way fits the auth mechanism)
92
+ * and then calls apiCurlIO::makeRequest on the signed request
93
+ *
94
+ * @param Google_Http_Request $request
95
+ * @return Google_Http_Request The resulting HTTP response including the
96
+ * responseHttpCode, responseHeaders and responseBody.
97
+ */
98
+ public function authenticatedRequest(Google_Http_Request $request)
99
+ {
100
+ $request = $this->sign($request);
101
+ return $this->client->getIo()->makeRequest($request);
102
+ }
103
+
104
+ public function sign(Google_Http_Request $request)
105
+ {
106
+ if (!$this->token) {
107
+ // No token, so nothing to do.
108
+ return $request;
109
+ }
110
+
111
+ $this->client->getLogger()->debug('App Identity authentication');
112
+
113
+ // Add the OAuth2 header to the request
114
+ $request->setRequestHeaders(
115
+ array('Authorization' => 'Bearer ' . $this->token['access_token'])
116
+ );
117
+
118
+ return $request;
119
+ }
120
+ }
lib/Google2/Auth/AssertionCredentials.php ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2012 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ if (!class_exists('Google_Client')) {
19
+ require_once dirname(__FILE__) . '/../autoload.php';
20
+ }
21
+
22
+ /**
23
+ * Credentials object used for OAuth 2.0 Signed JWT assertion grants.
24
+ */
25
+ class Google_Auth_AssertionCredentials
26
+ {
27
+ const MAX_TOKEN_LIFETIME_SECS = 3600;
28
+
29
+ public $serviceAccountName;
30
+ public $scopes;
31
+ public $privateKey;
32
+ public $privateKeyPassword;
33
+ public $assertionType;
34
+ public $sub;
35
+ /**
36
+ * @deprecated
37
+ * @link http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-06
38
+ */
39
+ public $prn;
40
+ private $useCache;
41
+
42
+ /**
43
+ * @param $serviceAccountName
44
+ * @param $scopes array List of scopes
45
+ * @param $privateKey
46
+ * @param string $privateKeyPassword
47
+ * @param string $assertionType
48
+ * @param bool|string $sub The email address of the user for which the
49
+ * application is requesting delegated access.
50
+ * @param bool useCache Whether to generate a cache key and allow
51
+ * automatic caching of the generated token.
52
+ */
53
+ public function __construct(
54
+ $serviceAccountName,
55
+ $scopes,
56
+ $privateKey,
57
+ $privateKeyPassword = 'notasecret',
58
+ $assertionType = 'http://oauth.net/grant_type/jwt/1.0/bearer',
59
+ $sub = false,
60
+ $useCache = true
61
+ ) {
62
+ $this->serviceAccountName = $serviceAccountName;
63
+ $this->scopes = is_string($scopes) ? $scopes : implode(' ', $scopes);
64
+ $this->privateKey = $privateKey;
65
+ $this->privateKeyPassword = $privateKeyPassword;
66
+ $this->assertionType = $assertionType;
67
+ $this->sub = $sub;
68
+ $this->prn = $sub;
69
+ $this->useCache = $useCache;
70
+ }
71
+
72
+ /**
73
+ * Generate a unique key to represent this credential.
74
+ * @return string
75
+ */
76
+ public function getCacheKey()
77
+ {
78
+ if (!$this->useCache) {
79
+ return false;
80
+ }
81
+ $h = $this->sub;
82
+ $h .= $this->assertionType;
83
+ $h .= $this->privateKey;
84
+ $h .= $this->scopes;
85
+ $h .= $this->serviceAccountName;
86
+ return md5($h);
87
+ }
88
+
89
+ public function generateAssertion()
90
+ {
91
+ $now = time();
92
+
93
+ $jwtParams = array(
94
+ 'aud' => Google_Auth_OAuth2::OAUTH2_TOKEN_URI,
95
+ 'scope' => $this->scopes,
96
+ 'iat' => $now,
97
+ 'exp' => $now + self::MAX_TOKEN_LIFETIME_SECS,
98
+ 'iss' => $this->serviceAccountName,
99
+ );
100
+
101
+ if ($this->sub !== false) {
102
+ $jwtParams['sub'] = $this->sub;
103
+ } else if ($this->prn !== false) {
104
+ $jwtParams['prn'] = $this->prn;
105
+ }
106
+
107
+ return $this->makeSignedJwt($jwtParams);
108
+ }
109
+
110
+ /**
111
+ * Creates a signed JWT.
112
+ * @param array $payload
113
+ * @return string The signed JWT.
114
+ */
115
+ private function makeSignedJwt($payload)
116
+ {
117
+ $header = array('typ' => 'JWT', 'alg' => 'RS256');
118
+
119
+ $payload = json_encode($payload);
120
+ // Handle some overzealous escaping in PHP json that seemed to cause some errors
121
+ // with claimsets.
122
+ $payload = str_replace('\/', '/', $payload);
123
+
124
+ $segments = array(
125
+ Google_Utils::urlSafeB64Encode(json_encode($header)),
126
+ Google_Utils::urlSafeB64Encode($payload)
127
+ );
128
+
129
+ $signingInput = implode('.', $segments);
130
+ $signer = new Google_Signer_P12($this->privateKey, $this->privateKeyPassword);
131
+ $signature = $signer->sign($signingInput);
132
+ $segments[] = Google_Utils::urlSafeB64Encode($signature);
133
+
134
+ return implode(".", $segments);
135
+ }
136
+ }
lib/Google2/Auth/ComputeEngine.php ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2014 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ if (!class_exists('Google_Client')) {
19
+ require_once dirname(__FILE__) . '/../autoload.php';
20
+ }
21
+
22
+ /**
23
+ * Authentication via built-in Compute Engine service accounts.
24
+ * The instance must be pre-configured with a service account
25
+ * and the appropriate scopes.
26
+ * @author Jonathan Parrott <jon.wayne.parrott@gmail.com>
27
+ */
28
+ class Google_Auth_ComputeEngine extends Google_Auth_Abstract
29
+ {
30
+ const METADATA_AUTH_URL =
31
+ 'http://metadata/computeMetadata/v1/instance/service-accounts/default/token';
32
+ private $client;
33
+ private $token;
34
+
35
+ public function __construct(Google_Client $client, $config = null)
36
+ {
37
+ $this->client = $client;
38
+ }
39
+
40
+ /**
41
+ * Perform an authenticated / signed apiHttpRequest.
42
+ * This function takes the apiHttpRequest, calls apiAuth->sign on it
43
+ * (which can modify the request in what ever way fits the auth mechanism)
44
+ * and then calls apiCurlIO::makeRequest on the signed request
45
+ *
46
+ * @param Google_Http_Request $request
47
+ * @return Google_Http_Request The resulting HTTP response including the
48
+ * responseHttpCode, responseHeaders and responseBody.
49
+ */
50
+ public function authenticatedRequest(Google_Http_Request $request)
51
+ {
52
+ $request = $this->sign($request);
53
+ return $this->client->getIo()->makeRequest($request);
54
+ }
55
+
56
+ /**
57
+ * @param string $token
58
+ * @throws Google_Auth_Exception
59
+ */
60
+ public function setAccessToken($token)
61
+ {
62
+ $token = json_decode($token, true);
63
+ if ($token == null) {
64
+ throw new Google_Auth_Exception('Could not json decode the token');
65
+ }
66
+ if (! isset($token['access_token'])) {
67
+ throw new Google_Auth_Exception("Invalid token format");
68
+ }
69
+ $token['created'] = time();
70
+ $this->token = $token;
71
+ }
72
+
73
+ public function getAccessToken()
74
+ {
75
+ return json_encode($this->token);
76
+ }
77
+
78
+ /**
79
+ * Acquires a new access token from the compute engine metadata server.
80
+ * @throws Google_Auth_Exception
81
+ */
82
+ public function acquireAccessToken()
83
+ {
84
+ $request = new Google_Http_Request(
85
+ self::METADATA_AUTH_URL,
86
+ 'GET',
87
+ array(
88
+ 'Metadata-Flavor' => 'Google'
89
+ )
90
+ );
91
+ $request->disableGzip();
92
+ $response = $this->client->getIo()->makeRequest($request);
93
+
94
+ if ($response->getResponseHttpCode() == 200) {
95
+ $this->setAccessToken($response->getResponseBody());
96
+ $this->token['created'] = time();
97
+ return $this->getAccessToken();
98
+ } else {
99
+ throw new Google_Auth_Exception(
100
+ sprintf(
101
+ "Error fetching service account access token, message: '%s'",
102
+ $response->getResponseBody()
103
+ ),
104
+ $response->getResponseHttpCode()
105
+ );
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Include an accessToken in a given apiHttpRequest.
111
+ * @param Google_Http_Request $request
112
+ * @return Google_Http_Request
113
+ * @throws Google_Auth_Exception
114
+ */
115
+ public function sign(Google_Http_Request $request)
116
+ {
117
+ if ($this->isAccessTokenExpired()) {
118
+ $this->acquireAccessToken();
119
+ }
120
+
121
+ $this->client->getLogger()->debug('Compute engine service account authentication');
122
+
123
+ $request->setRequestHeaders(
124
+ array('Authorization' => 'Bearer ' . $this->token['access_token'])
125
+ );
126
+
127
+ return $request;
128
+ }
129
+
130
+ /**
131
+ * Returns if the access_token is expired.
132
+ * @return bool Returns True if the access_token is expired.
133
+ */
134
+ public function isAccessTokenExpired()
135
+ {
136
+ if (!$this->token || !isset($this->token['created'])) {
137
+ return true;
138
+ }
139
+
140
+ // If the token is set to expire in the next 30 seconds.
141
+ $expired = ($this->token['created']
142
+ + ($this->token['expires_in'] - 30)) < time();
143
+
144
+ return $expired;
145
+ }
146
+ }
lib/Google2/Auth/Exception.php ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2013 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ if (!class_exists('Google_Client')) {
19
+ require_once dirname(__FILE__) . '/../autoload.php';
20
+ }
21
+
22
+ class Google_Auth_Exception extends Google_Exception
23
+ {
24
+ }
lib/Google2/Auth/LoginTicket.php ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2011 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ if (!class_exists('Google_Client')) {
19
+ require_once dirname(__FILE__) . '/../autoload.php';
20
+ }
21
+
22
+ /**
23
+ * Class to hold information about an authenticated login.
24
+ *
25
+ * @author Brian Eaton <beaton@google.com>
26
+ */
27
+ class Google_Auth_LoginTicket
28
+ {
29
+ const USER_ATTR = "sub";
30
+
31
+ // Information from id token envelope.
32
+ private $envelope;
33
+
34
+ // Information from id token payload.
35
+ private $payload;
36
+
37
+ /**
38
+ * Creates a user based on the supplied token.
39
+ *
40
+ * @param string $envelope Header from a verified authentication token.
41
+ * @param string $payload Information from a verified authentication token.
42
+ */
43
+ public function __construct($envelope, $payload)
44
+ {
45
+ $this->envelope = $envelope;
46
+ $this->payload = $payload;
47
+ }
48
+
49
+ /**
50
+ * Returns the numeric identifier for the user.
51
+ * @throws Google_Auth_Exception
52
+ * @return
53
+ */
54
+ public function getUserId()
55
+ {
56
+ if (array_key_exists(self::USER_ATTR, $this->payload)) {
57
+ return $this->payload[self::USER_ATTR];
58
+ }
59
+ throw new Google_Auth_Exception("No user_id in token");
60
+ }
61
+
62
+ /**
63
+ * Returns attributes from the login ticket. This can contain
64
+ * various information about the user session.
65
+ * @return array
66
+ */
67
+ public function getAttributes()
68
+ {
69
+ return array("envelope" => $this->envelope, "payload" => $this->payload);
70
+ }
71
+ }
lib/Google2/Auth/OAuth2.php ADDED
@@ -0,0 +1,632 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2008 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ if (!class_exists('Google_Client')) {
19
+ require_once dirname(__FILE__) . '/../autoload.php';
20
+ }
21
+
22
+ /**
23
+ * Authentication class that deals with the OAuth 2 web-server authentication flow
24
+ *
25
+ */
26
+ class Google_Auth_OAuth2 extends Google_Auth_Abstract
27
+ {
28
+ const OAUTH2_REVOKE_URI = 'https://accounts.google.com/o/oauth2/revoke';
29
+ const OAUTH2_TOKEN_URI = 'https://accounts.google.com/o/oauth2/token';
30
+ const OAUTH2_AUTH_URL = 'https://accounts.google.com/o/oauth2/auth';
31
+ const CLOCK_SKEW_SECS = 300; // five minutes in seconds
32
+ const AUTH_TOKEN_LIFETIME_SECS = 300; // five minutes in seconds
33
+ const MAX_TOKEN_LIFETIME_SECS = 86400; // one day in seconds
34
+ const OAUTH2_ISSUER = 'accounts.google.com';
35
+
36
+ /** @var Google_Auth_AssertionCredentials $assertionCredentials */
37
+ private $assertionCredentials;
38
+
39
+ /**
40
+ * @var string The state parameters for CSRF and other forgery protection.
41
+ */
42
+ private $state;
43
+
44
+ /**
45
+ * @var array The token bundle.
46
+ */
47
+ private $token = array();
48
+
49
+ /**
50
+ * @var Google_Client the base client
51
+ */
52
+ private $client;
53
+
54
+ /**
55
+ * Instantiates the class, but does not initiate the login flow, leaving it
56
+ * to the discretion of the caller.
57
+ */
58
+ public function __construct(Google_Client $client)
59
+ {
60
+ $this->client = $client;
61
+ }
62
+
63
+ /**
64
+ * Perform an authenticated / signed apiHttpRequest.
65
+ * This function takes the apiHttpRequest, calls apiAuth->sign on it
66
+ * (which can modify the request in what ever way fits the auth mechanism)
67
+ * and then calls apiCurlIO::makeRequest on the signed request
68
+ *
69
+ * @param Google_Http_Request $request
70
+ * @return Google_Http_Request The resulting HTTP response including the
71
+ * responseHttpCode, responseHeaders and responseBody.
72
+ */
73
+ public function authenticatedRequest(Google_Http_Request $request)
74
+ {
75
+ $request = $this->sign($request);
76
+ return $this->client->getIo()->makeRequest($request);
77
+ }
78
+
79
+ /**
80
+ * @param string $code
81
+ * @throws Google_Auth_Exception
82
+ * @return string
83
+ */
84
+ public function authenticate($code)
85
+ {
86
+ if (strlen($code) == 0) {
87
+ throw new Google_Auth_Exception("Invalid code");
88
+ }
89
+
90
+ // We got here from the redirect from a successful authorization grant,
91
+ // fetch the access token
92
+ $request = new Google_Http_Request(
93
+ self::OAUTH2_TOKEN_URI,
94
+ 'POST',
95
+ array(),
96
+ array(
97
+ 'code' => $code,
98
+ 'grant_type' => 'authorization_code',
99
+ 'redirect_uri' => $this->client->getClassConfig($this, 'redirect_uri'),
100
+ 'client_id' => $this->client->getClassConfig($this, 'client_id'),
101
+ 'client_secret' => $this->client->getClassConfig($this, 'client_secret')
102
+ )
103
+ );
104
+ $request->disableGzip();
105
+ $response = $this->client->getIo()->makeRequest($request);
106
+
107
+ if ($response->getResponseHttpCode() == 200) {
108
+ $this->setAccessToken($response->getResponseBody());
109
+ $this->token['created'] = time();
110
+ return $this->getAccessToken();
111
+ } else {
112
+ $decodedResponse = json_decode($response->getResponseBody(), true);
113
+ if ($decodedResponse != null && $decodedResponse['error']) {
114
+ $errorText = $decodedResponse['error'];
115
+ if (isset($decodedResponse['error_description'])) {
116
+ $errorText .= ": " . $decodedResponse['error_description'];
117
+ }
118
+ }
119
+ throw new Google_Auth_Exception(
120
+ sprintf(
121
+ "Error fetching OAuth2 access token, message: '%s'",
122
+ $errorText
123
+ ),
124
+ $response->getResponseHttpCode()
125
+ );
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Create a URL to obtain user authorization.
131
+ * The authorization endpoint allows the user to first
132
+ * authenticate, and then grant/deny the access request.
133
+ * @param string $scope The scope is expressed as a list of space-delimited strings.
134
+ * @return string
135
+ */
136
+ public function createAuthUrl($scope)
137
+ {
138
+ $params = array(
139
+ 'response_type' => 'code',
140
+ 'redirect_uri' => $this->client->getClassConfig($this, 'redirect_uri'),
141
+ 'client_id' => $this->client->getClassConfig($this, 'client_id'),
142
+ 'scope' => $scope,
143
+ 'access_type' => $this->client->getClassConfig($this, 'access_type'),
144
+ );
145
+
146
+ // Prefer prompt to approval prompt.
147
+ if ($this->client->getClassConfig($this, 'prompt')) {
148
+ $params = $this->maybeAddParam($params, 'prompt');
149
+ } else {
150
+ $params = $this->maybeAddParam($params, 'approval_prompt');
151
+ }
152
+ $params = $this->maybeAddParam($params, 'login_hint');
153
+ $params = $this->maybeAddParam($params, 'hd');
154
+ $params = $this->maybeAddParam($params, 'openid.realm');
155
+ $params = $this->maybeAddParam($params, 'include_granted_scopes');
156
+
157
+ // If the list of scopes contains plus.login, add request_visible_actions
158
+ // to auth URL.
159
+ $rva = $this->client->getClassConfig($this, 'request_visible_actions');
160
+ if (strpos($scope, 'plus.login') && strlen($rva) > 0) {
161
+ $params['request_visible_actions'] = $rva;
162
+ }
163
+
164
+ if (isset($this->state)) {
165
+ $params['state'] = $this->state;
166
+ }
167
+
168
+ return self::OAUTH2_AUTH_URL . "?" . http_build_query($params, '', '&');
169
+ }
170
+
171
+ /**
172
+ * @param string $token
173
+ * @throws Google_Auth_Exception
174
+ */
175
+ public function setAccessToken($token)
176
+ {
177
+ $token = json_decode($token, true);
178
+ if ($token == null) {
179
+ throw new Google_Auth_Exception('Could not json decode the token');
180
+ }
181
+ if (! isset($token['access_token'])) {
182
+ throw new Google_Auth_Exception("Invalid token format");
183
+ }
184
+ $this->token = $token;
185
+ }
186
+
187
+ public function getAccessToken()
188
+ {
189
+ return json_encode($this->token);
190
+ }
191
+
192
+ public function getRefreshToken()
193
+ {
194
+ if (array_key_exists('refresh_token', $this->token)) {
195
+ return $this->token['refresh_token'];
196
+ } else {
197
+ return null;
198
+ }
199
+ }
200
+
201
+ public function setState($state)
202
+ {
203
+ $this->state = $state;
204
+ }
205
+
206
+ public function setAssertionCredentials(Google_Auth_AssertionCredentials $creds)
207
+ {
208
+ $this->assertionCredentials = $creds;
209
+ }
210
+
211
+ /**
212
+ * Include an accessToken in a given apiHttpRequest.
213
+ * @param Google_Http_Request $request
214
+ * @return Google_Http_Request
215
+ * @throws Google_Auth_Exception
216
+ */
217
+ public function sign(Google_Http_Request $request)
218
+ {
219
+ // add the developer key to the request before signing it
220
+ if ($this->client->getClassConfig($this, 'developer_key')) {
221
+ $request->setQueryParam('key', $this->client->getClassConfig($this, 'developer_key'));
222
+ }
223
+
224
+ // Cannot sign the request without an OAuth access token.
225
+ if (null == $this->token && null == $this->assertionCredentials) {
226
+ return $request;
227
+ }
228
+
229
+ // Check if the token is set to expire in the next 30 seconds
230
+ // (or has already expired).
231
+ if ($this->isAccessTokenExpired()) {
232
+ if ($this->assertionCredentials) {
233
+ $this->refreshTokenWithAssertion();
234
+ } else {
235
+ $this->client->getLogger()->debug('OAuth2 access token expired');
236
+ if (! array_key_exists('refresh_token', $this->token)) {
237
+ $error = "The OAuth 2.0 access token has expired,"
238
+ ." and a refresh token is not available. Refresh tokens"
239
+ ." are not returned for responses that were auto-approved.";
240
+
241
+ $this->client->getLogger()->error($error);
242
+ throw new Google_Auth_Exception($error);
243
+ }
244
+ $this->refreshToken($this->token['refresh_token']);
245
+ }
246
+ }
247
+
248
+ $this->client->getLogger()->debug('OAuth2 authentication');
249
+
250
+ // Add the OAuth2 header to the request
251
+ $request->setRequestHeaders(
252
+ array('Authorization' => 'Bearer ' . $this->token['access_token'])
253
+ );
254
+
255
+ return $request;
256
+ }
257
+
258
+ /**
259
+ * Fetches a fresh access token with the given refresh token.
260
+ * @param string $refreshToken
261
+ * @return void
262
+ */
263
+ public function refreshToken($refreshToken)
264
+ {
265
+ $this->refreshTokenRequest(
266
+ array(
267
+ 'client_id' => $this->client->getClassConfig($this, 'client_id'),
268
+ 'client_secret' => $this->client->getClassConfig($this, 'client_secret'),
269
+ 'refresh_token' => $refreshToken,
270
+ 'grant_type' => 'refresh_token'
271
+ )
272
+ );
273
+ }
274
+
275
+ /**
276
+ * Fetches a fresh access token with a given assertion token.
277
+ * @param Google_Auth_AssertionCredentials $assertionCredentials optional.
278
+ * @return void
279
+ */
280
+ public function refreshTokenWithAssertion($assertionCredentials = null)
281
+ {
282
+ if (!$assertionCredentials) {
283
+ $assertionCredentials = $this->assertionCredentials;
284
+ }
285
+
286
+ $cacheKey = $assertionCredentials->getCacheKey();
287
+
288
+ if ($cacheKey) {
289
+ // We can check whether we have a token available in the
290
+ // cache. If it is expired, we can retrieve a new one from
291
+ // the assertion.
292
+ $token = $this->client->getCache()->get($cacheKey);
293
+ if ($token) {
294
+ $this->setAccessToken($token);
295
+ }
296
+ if (!$this->isAccessTokenExpired()) {
297
+ return;
298
+ }
299
+ }
300
+
301
+ $this->client->getLogger()->debug('OAuth2 access token expired');
302
+ $this->refreshTokenRequest(
303
+ array(
304
+ 'grant_type' => 'assertion',
305
+ 'assertion_type' => $assertionCredentials->assertionType,
306
+ 'assertion' => $assertionCredentials->generateAssertion(),
307
+ )
308
+ );
309
+
310
+ if ($cacheKey) {
311
+ // Attempt to cache the token.
312
+ $this->client->getCache()->set(
313
+ $cacheKey,
314
+ $this->getAccessToken()
315
+ );
316
+ }
317
+ }
318
+
319
+ private function refreshTokenRequest($params)
320
+ {
321
+ if (isset($params['assertion'])) {
322
+ $this->client->getLogger()->info(
323
+ 'OAuth2 access token refresh with Signed JWT assertion grants.'
324
+ );
325
+ } else {
326
+ $this->client->getLogger()->info('OAuth2 access token refresh');
327
+ }
328
+
329
+ $http = new Google_Http_Request(
330
+ self::OAUTH2_TOKEN_URI,
331
+ 'POST',
332
+ array(),
333
+ $params
334
+ );
335
+ $http->disableGzip();
336
+ $request = $this->client->getIo()->makeRequest($http);
337
+
338
+ $code = $request->getResponseHttpCode();
339
+ $body = $request->getResponseBody();
340
+ if (200 == $code) {
341
+ $token = json_decode($body, true);
342
+ if ($token == null) {
343
+ throw new Google_Auth_Exception("Could not json decode the access token");
344
+ }
345
+
346
+ if (! isset($token['access_token']) || ! isset($token['expires_in'])) {
347
+ throw new Google_Auth_Exception("Invalid token format");
348
+ }
349
+
350
+ if (isset($token['id_token'])) {
351
+ $this->token['id_token'] = $token['id_token'];
352
+ }
353
+ $this->token['access_token'] = $token['access_token'];
354
+ $this->token['expires_in'] = $token['expires_in'];
355
+ $this->token['created'] = time();
356
+ } else {
357
+ throw new Google_Auth_Exception("Error refreshing the OAuth2 token, message: '$body'", $code);
358
+ }
359
+ }
360
+
361
+ /**
362
+ * Revoke an OAuth2 access token or refresh token. This method will revoke the current access
363
+ * token, if a token isn't provided.
364
+ * @throws Google_Auth_Exception
365
+ * @param string|null $token The token (access token or a refresh token) that should be revoked.
366
+ * @return boolean Returns True if the revocation was successful, otherwise False.
367
+ */
368
+ public function revokeToken($token = null)
369
+ {
370
+ if (!$token) {
371
+ if (!$this->token) {
372
+ // Not initialized, no token to actually revoke
373
+ return false;
374
+ } elseif (array_key_exists('refresh_token', $this->token)) {
375
+ $token = $this->token['refresh_token'];
376
+ } else {
377
+ $token = $this->token['access_token'];
378
+ }
379
+ }
380
+ $request = new Google_Http_Request(
381
+ self::OAUTH2_REVOKE_URI,
382
+ 'POST',
383
+ array(),
384
+ "token=$token"
385
+ );
386
+ $request->disableGzip();
387
+ $response = $this->client->getIo()->makeRequest($request);
388
+ $code = $response->getResponseHttpCode();
389
+ if ($code == 200) {
390
+ $this->token = null;
391
+ return true;
392
+ }
393
+
394
+ return false;
395
+ }
396
+
397
+ /**
398
+ * Returns if the access_token is expired.
399
+ * @return bool Returns True if the access_token is expired.
400
+ */
401
+ public function isAccessTokenExpired()
402
+ {
403
+ if (!$this->token || !isset($this->token['created'])) {
404
+ return true;
405
+ }
406
+
407
+ // If the token is set to expire in the next 30 seconds.
408
+ $expired = ($this->token['created']
409
+ + ($this->token['expires_in'] - 30)) < time();
410
+
411
+ return $expired;
412
+ }
413
+
414
+ // Gets federated sign-on certificates to use for verifying identity tokens.
415
+ // Returns certs as array structure, where keys are key ids, and values
416
+ // are PEM encoded certificates.
417
+ private function getFederatedSignOnCerts()
418
+ {
419
+ return $this->retrieveCertsFromLocation(
420
+ $this->client->getClassConfig($this, 'federated_signon_certs_url')
421
+ );
422
+ }
423
+
424
+ /**
425
+ * Retrieve and cache a certificates file.
426
+ *
427
+ * @param $url string location
428
+ * @throws Google_Auth_Exception
429
+ * @return array certificates
430
+ */
431
+ public function retrieveCertsFromLocation($url)
432
+ {
433
+ // If we're retrieving a local file, just grab it.
434
+ if ("http" != substr($url, 0, 4)) {
435
+ $file = file_get_contents($url);
436
+ if ($file) {
437
+ return json_decode($file, true);
438
+ } else {
439
+ throw new Google_Auth_Exception(
440
+ "Failed to retrieve verification certificates: '" .
441
+ $url . "'."
442
+ );
443
+ }
444
+ }
445
+
446
+ // This relies on makeRequest caching certificate responses.
447
+ $request = $this->client->getIo()->makeRequest(
448
+ new Google_Http_Request(
449
+ $url
450
+ )
451
+ );
452
+ if ($request->getResponseHttpCode() == 200) {
453
+ $certs = json_decode($request->getResponseBody(), true);
454
+ if ($certs) {
455
+ return $certs;
456
+ }
457
+ }
458
+ throw new Google_Auth_Exception(
459
+ "Failed to retrieve verification certificates: '" .
460
+ $request->getResponseBody() . "'.",
461
+ $request->getResponseHttpCode()
462
+ );
463
+ }
464
+
465
+ /**
466
+ * Verifies an id token and returns the authenticated apiLoginTicket.
467
+ * Throws an exception if the id token is not valid.
468
+ * The audience parameter can be used to control which id tokens are
469
+ * accepted. By default, the id token must have been issued to this OAuth2 client.
470
+ *
471
+ * @param $id_token
472
+ * @param $audience
473
+ * @return Google_Auth_LoginTicket
474
+ */
475
+ public function verifyIdToken($id_token = null, $audience = null)
476
+ {
477
+ if (!$id_token) {
478
+ $id_token = $this->token['id_token'];
479
+ }
480
+ $certs = $this->getFederatedSignonCerts();
481
+ if (!$audience) {
482
+ $audience = $this->client->getClassConfig($this, 'client_id');
483
+ }
484
+
485
+ return $this->verifySignedJwtWithCerts($id_token, $certs, $audience, self::OAUTH2_ISSUER);
486
+ }
487
+
488
+ /**
489
+ * Verifies the id token, returns the verified token contents.
490
+ *
491
+ * @param $jwt string the token
492
+ * @param $certs array of certificates
493
+ * @param $required_audience string the expected consumer of the token
494
+ * @param [$issuer] the expected issues, defaults to Google
495
+ * @param [$max_expiry] the max lifetime of a token, defaults to MAX_TOKEN_LIFETIME_SECS
496
+ * @throws Google_Auth_Exception
497
+ * @return mixed token information if valid, false if not
498
+ */
499
+ public function verifySignedJwtWithCerts(
500
+ $jwt,
501
+ $certs,
502
+ $required_audience,
503
+ $issuer = null,
504
+ $max_expiry = null
505
+ ) {
506
+ if (!$max_expiry) {
507
+ // Set the maximum time we will accept a token for.
508
+ $max_expiry = self::MAX_TOKEN_LIFETIME_SECS;
509
+ }
510
+
511
+ $segments = explode(".", $jwt);
512
+ if (count($segments) != 3) {
513
+ throw new Google_Auth_Exception("Wrong number of segments in token: $jwt");
514
+ }
515
+ $signed = $segments[0] . "." . $segments[1];
516
+ $signature = Google_Utils::urlSafeB64Decode($segments[2]);
517
+
518
+ // Parse envelope.
519
+ $envelope = json_decode(Google_Utils::urlSafeB64Decode($segments[0]), true);
520
+ if (!$envelope) {
521
+ throw new Google_Auth_Exception("Can't parse token envelope: " . $segments[0]);
522
+ }
523
+
524
+ // Parse token
525
+ $json_body = Google_Utils::urlSafeB64Decode($segments[1]);
526
+ $payload = json_decode($json_body, true);
527
+ if (!$payload) {
528
+ throw new Google_Auth_Exception("Can't parse token payload: " . $segments[1]);
529
+ }
530
+
531
+ // Check signature
532
+ $verified = false;
533
+ foreach ($certs as $keyName => $pem) {
534
+ $public_key = new Google_Verifier_Pem($pem);
535
+ if ($public_key->verify($signed, $signature)) {
536
+ $verified = true;
537
+ break;
538
+ }
539
+ }
540
+
541
+ if (!$verified) {
542
+ throw new Google_Auth_Exception("Invalid token signature: $jwt");
543
+ }
544
+
545
+ // Check issued-at timestamp
546
+ $iat = 0;
547
+ if (array_key_exists("iat", $payload)) {
548
+ $iat = $payload["iat"];
549
+ }
550
+ if (!$iat) {
551
+ throw new Google_Auth_Exception("No issue time in token: $json_body");
552
+ }
553
+ $earliest = $iat - self::CLOCK_SKEW_SECS;
554
+
555
+ // Check expiration timestamp
556
+ $now = time();
557
+ $exp = 0;
558
+ if (array_key_exists("exp", $payload)) {
559
+ $exp = $payload["exp"];
560
+ }
561
+ if (!$exp) {
562
+ throw new Google_Auth_Exception("No expiration time in token: $json_body");
563
+ }
564
+ if ($exp >= $now + $max_expiry) {
565
+ throw new Google_Auth_Exception(
566
+ sprintf("Expiration time too far in future: %s", $json_body)
567
+ );
568
+ }
569
+
570
+ $latest = $exp + self::CLOCK_SKEW_SECS;
571
+ if ($now < $earliest) {
572
+ throw new Google_Auth_Exception(
573
+ sprintf(
574
+ "Token used too early, %s < %s: %s",
575
+ $now,
576
+ $earliest,
577
+ $json_body
578
+ )
579
+ );
580
+ }
581
+ if ($now > $latest) {
582
+ throw new Google_Auth_Exception(
583
+ sprintf(
584
+ "Token used too late, %s > %s: %s",
585
+ $now,
586
+ $latest,
587
+ $json_body
588
+ )
589
+ );
590
+ }
591
+
592
+ $iss = $payload['iss'];
593
+ if ($issuer && $iss != $issuer) {
594
+ throw new Google_Auth_Exception(
595
+ sprintf(
596
+ "Invalid issuer, %s != %s: %s",
597
+ $iss,
598
+ $issuer,
599
+ $json_body
600
+ )
601
+ );
602
+ }
603
+
604
+ // Check audience
605
+ $aud = $payload["aud"];
606
+ if ($aud != $required_audience) {
607
+ throw new Google_Auth_Exception(
608
+ sprintf(
609
+ "Wrong recipient, %s != %s:",
610
+ $aud,
611
+ $required_audience,
612
+ $json_body
613
+ )
614
+ );
615
+ }
616
+
617
+ // All good.
618
+ return new Google_Auth_LoginTicket($envelope, $payload);
619
+ }
620
+
621
+ /**
622
+ * Add a parameter to the auth params if not empty string.
623
+ */
624
+ private function maybeAddParam($params, $name)
625
+ {
626
+ $param = $this->client->getClassConfig($this, $name);
627
+ if ($param != '') {
628
+ $params[$name] = $param;
629
+ }
630
+ return $params;
631
+ }
632
+ }
lib/Google2/Auth/Simple.php ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2010 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ if (!class_exists('Google_Client')) {
19
+ require_once dirname(__FILE__) . '/../autoload.php';
20
+ }
21
+
22
+ /**
23
+ * Simple API access implementation. Can either be used to make requests
24
+ * completely unauthenticated, or by using a Simple API Access developer
25
+ * key.
26
+ */
27
+ class Google_Auth_Simple extends Google_Auth_Abstract
28
+ {
29
+ private $client;
30
+
31
+ public function __construct(Google_Client $client, $config = null)
32
+ {
33
+ $this->client = $client;
34
+ }
35
+
36
+ /**
37
+ * Perform an authenticated / signed apiHttpRequest.
38
+ * This function takes the apiHttpRequest, calls apiAuth->sign on it
39
+ * (which can modify the request in what ever way fits the auth mechanism)
40
+ * and then calls apiCurlIO::makeRequest on the signed request
41
+ *
42
+ * @param Google_Http_Request $request
43
+ * @return Google_Http_Request The resulting HTTP response including the
44
+ * responseHttpCode, responseHeaders and responseBody.
45
+ */
46
+ public function authenticatedRequest(Google_Http_Request $request)
47
+ {
48
+ $request = $this->sign($request);
49
+ return $this->io->makeRequest($request);
50
+ }
51
+
52
+ public function sign(Google_Http_Request $request)
53
+ {
54
+ $key = $this->client->getClassConfig($this, 'developer_key');
55
+ if ($key) {
56
+ $this->client->getLogger()->debug(
57
+ 'Simple API Access developer key authentication'
58
+ );
59
+ $request->setQueryParam('key', $key);
60
+ }
61
+ return $request;
62
+ }
63
+ }
lib/Google2/Cache/Abstract.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2008 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ /**
19
+ * Abstract storage class
20
+ *
21
+ * @author Chris Chabot <chabotc@google.com>
22
+ */
23
+ abstract class Google_Cache_Abstract
24
+ {
25
+
26
+ abstract public function __construct(Google_Client $client);
27
+
28
+ /**
29
+ * Retrieves the data for the given key, or false if they
30
+ * key is unknown or expired
31
+ *
32
+ * @param String $key The key who's data to retrieve
33
+ * @param boolean|int $expiration Expiration time in seconds
34
+ *
35
+ */
36
+ abstract public function get($key, $expiration = false);
37
+
38
+ /**
39
+ * Store the key => $value set. The $value is serialized
40
+ * by this function so can be of any type
41
+ *
42
+ * @param string $key Key of the data
43
+ * @param string $value data
44
+ */
45
+ abstract public function set($key, $value);
46
+
47
+ /**
48
+ * Removes the key/data pair for the given $key
49
+ *
50
+ * @param String $key
51
+ */
52
+ abstract public function delete($key);
53
+ }
lib/Google2/Cache/Apc.php ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2010 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ if (!class_exists('Google_Client')) {
19
+ require_once dirname(__FILE__) . '/../autoload.php';
20
+ }
21
+
22
+ /**
23
+ * A persistent storage class based on the APC cache, which is not
24
+ * really very persistent, as soon as you restart your web server
25
+ * the storage will be wiped, however for debugging and/or speed
26
+ * it can be useful, and cache is a lot cheaper then storage.
27
+ *
28
+ * @author Chris Chabot <chabotc@google.com>
29
+ */
30
+ class Google_Cache_Apc extends Google_Cache_Abstract
31
+ {
32
+ /**
33
+ * @var Google_Client the current client
34
+ */
35
+ private $client;
36
+
37
+ public function __construct(Google_Client $client)
38
+ {
39
+ if (! function_exists('apc_add') ) {
40
+ $error = "Apc functions not available";
41
+
42
+ $client->getLogger()->error($error);
43
+ throw new Google_Cache_Exception($error);
44
+ }
45
+
46
+ $this->client = $client;
47
+ }
48
+
49
+ /**
50
+ * @inheritDoc
51
+ */
52
+ public function get($key, $expiration = false)
53
+ {
54
+ $ret = apc_fetch($key);
55
+ if ($ret === false) {
56
+ $this->client->getLogger()->debug(
57
+ 'APC cache miss',
58
+ array('key' => $key)
59
+ );
60
+ return false;
61
+ }
62
+ if (is_numeric($expiration) && (time() - $ret['time'] > $expiration)) {
63
+ $this->client->getLogger()->debug(
64
+ 'APC cache miss (expired)',
65
+ array('key' => $key, 'var' => $ret)
66
+ );
67
+ $this->delete($key);
68
+ return false;
69
+ }
70
+
71
+ $this->client->getLogger()->debug(
72
+ 'APC cache hit',
73
+ array('key' => $key, 'var' => $ret)
74
+ );
75
+
76
+ return $ret['data'];
77
+ }
78
+
79
+ /**
80
+ * @inheritDoc
81
+ */
82
+ public function set($key, $value)
83
+ {
84
+ $var = array('time' => time(), 'data' => $value);
85
+ $rc = apc_store($key, $var);
86
+
87
+ if ($rc == false) {
88
+ $this->client->getLogger()->error(
89
+ 'APC cache set failed',
90
+ array('key' => $key, 'var' => $var)
91
+ );
92
+ throw new Google_Cache_Exception("Couldn't store data");
93
+ }
94
+
95
+ $this->client->getLogger()->debug(
96
+ 'APC cache set',
97
+ array('key' => $key, 'var' => $var)
98
+ );
99
+ }
100
+
101
+ /**
102
+ * @inheritDoc
103
+ * @param String $key
104
+ */
105
+ public function delete($key)
106
+ {
107
+ $this->client->getLogger()->debug(
108
+ 'APC cache delete',
109
+ array('key' => $key)
110
+ );
111
+ apc_delete($key);
112
+ }
113
+ }
lib/Google2/Cache/Exception.php ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2013 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ if (!class_exists('Google_Client')) {
19
+ require_once dirname(__FILE__) . '/../autoload.php';
20
+ }
21
+
22
+ class Google_Cache_Exception extends Google_Exception
23
+ {
24
+ }
lib/Google2/Cache/File.php ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2008 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ if (!class_exists('Google_Client')) {
19
+ require_once dirname(__FILE__) . '/../autoload.php';
20
+ }
21
+
22
+ /*
23
+ * This class implements a basic on disk storage. While that does
24
+ * work quite well it's not the most elegant and scalable solution.
25
+ * It will also get you into a heap of trouble when you try to run
26
+ * this in a clustered environment.
27
+ *
28
+ * @author Chris Chabot <chabotc@google.com>
29
+ */
30
+ class Google_Cache_File extends Google_Cache_Abstract
31
+ {
32
+ const MAX_LOCK_RETRIES = 10;
33
+ private $path;
34
+ private $fh;
35
+
36
+ /**
37
+ * @var Google_Client the current client
38
+ */
39
+ private $client;
40
+
41
+ public function __construct(Google_Client $client)
42
+ {
43
+ $this->client = $client;
44
+ $this->path = $this->client->getClassConfig($this, 'directory');
45
+ }
46
+
47
+ public function get($key, $expiration = false)
48
+ {
49
+ $storageFile = $this->getCacheFile($key);
50
+ $data = false;
51
+
52
+ if (!file_exists($storageFile)) {
53
+ $this->client->getLogger()->debug(
54
+ 'File cache miss',
55
+ array('key' => $key, 'file' => $storageFile)
56
+ );
57
+ return false;
58
+ }
59
+
60
+ if ($expiration) {
61
+ $mtime = filemtime($storageFile);
62
+ if ((time() - $mtime) >= $expiration) {
63
+ $this->client->getLogger()->debug(
64
+ 'File cache miss (expired)',
65
+ array('key' => $key, 'file' => $storageFile)
66
+ );
67
+ $this->delete($key);
68
+ return false;
69
+ }
70
+ }
71
+
72
+ if ($this->acquireReadLock($storageFile)) {
73
+ if (filesize($storageFile) > 0) {
74
+ $data = fread($this->fh, filesize($storageFile));
75
+ $data = unserialize($data);
76
+ } else {
77
+ $this->client->getLogger()->debug(
78
+ 'Cache file was empty',
79
+ array('file' => $storageFile)
80
+ );
81
+ }
82
+ $this->unlock($storageFile);
83
+ }
84
+
85
+ $this->client->getLogger()->debug(
86
+ 'File cache hit',
87
+ array('key' => $key, 'file' => $storageFile, 'var' => $data)
88
+ );
89
+
90
+ return $data;
91
+ }
92
+
93
+ public function set($key, $value)
94
+ {
95
+ $storageFile = $this->getWriteableCacheFile($key);
96
+ if ($this->acquireWriteLock($storageFile)) {
97
+ // We serialize the whole request object, since we don't only want the
98
+ // responseContent but also the postBody used, headers, size, etc.
99
+ $data = serialize($value);
100
+ $result = fwrite($this->fh, $data);
101
+ $this->unlock($storageFile);
102
+
103
+ $this->client->getLogger()->debug(
104
+ 'File cache set',
105
+ array('key' => $key, 'file' => $storageFile, 'var' => $value)
106
+ );
107
+ } else {
108
+ $this->client->getLogger()->notice(
109
+ 'File cache set failed',
110
+ array('key' => $key, 'file' => $storageFile)
111
+ );
112
+ }
113
+ }
114
+
115
+ public function delete($key)
116
+ {
117
+ $file = $this->getCacheFile($key);
118
+ if (file_exists($file) && !unlink($file)) {
119
+ $this->client->getLogger()->error(
120
+ 'File cache delete failed',
121
+ array('key' => $key, 'file' => $file)
122
+ );
123
+ throw new Google_Cache_Exception("Cache file could not be deleted");
124
+ }
125
+
126
+ $this->client->getLogger()->debug(
127
+ 'File cache delete',
128
+ array('key' => $key, 'file' => $file)
129
+ );
130
+ }
131
+
132
+ private function getWriteableCacheFile($file)
133
+ {
134
+ return $this->getCacheFile($file, true);
135
+ }
136
+
137
+ private function getCacheFile($file, $forWrite = false)
138
+ {
139
+ return $this->getCacheDir($file, $forWrite) . '/' . md5($file);
140
+ }
141
+
142
+ private function getCacheDir($file, $forWrite)
143
+ {
144
+ // use the first 2 characters of the hash as a directory prefix
145
+ // this should prevent slowdowns due to huge directory listings
146
+ // and thus give some basic amount of scalability
147
+ $storageDir = $this->path . '/' . substr(md5($file), 0, 2);
148
+ if ($forWrite && ! is_dir($storageDir)) {
149
+ if (! mkdir($storageDir, 0755, true)) {
150
+ $this->client->getLogger()->error(
151
+ 'File cache creation failed',
152
+ array('dir' => $storageDir)
153
+ );
154
+ throw new Google_Cache_Exception("Could not create storage directory: $storageDir");
155
+ }
156
+ }
157
+ return $storageDir;
158
+ }
159
+
160
+ private function acquireReadLock($storageFile)
161
+ {
162
+ return $this->acquireLock(LOCK_SH, $storageFile);
163
+ }
164
+
165
+ private function acquireWriteLock($storageFile)
166
+ {
167
+ $rc = $this->acquireLock(LOCK_EX, $storageFile);
168
+ if (!$rc) {
169
+ $this->client->getLogger()->notice(
170
+ 'File cache write lock failed',
171
+ array('file' => $storageFile)
172
+ );
173
+ $this->delete($storageFile);
174
+ }
175
+ return $rc;
176
+ }
177
+
178
+ private function acquireLock($type, $storageFile)
179
+ {
180
+ $mode = $type == LOCK_EX ? "w" : "r";
181
+ $this->fh = fopen($storageFile, $mode);
182
+ if (!$this->fh) {
183
+ $this->client->getLogger()->error(
184
+ 'Failed to open file during lock acquisition',
185
+ array('file' => $storageFile)
186
+ );
187
+ return false;
188
+ }
189
+ $count = 0;
190
+ while (!flock($this->fh, $type | LOCK_NB)) {
191
+ // Sleep for 10ms.
192
+ usleep(10000);
193
+ if (++$count < self::MAX_LOCK_RETRIES) {
194
+ return false;
195
+ }
196
+ }
197
+ return true;
198
+ }
199
+
200
+ public function unlock($storageFile)
201
+ {
202
+ if ($this->fh) {
203
+ flock($this->fh, LOCK_UN);
204
+ }
205
+ }
206
+ }
lib/Google2/Cache/Memcache.php ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2008 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ if (!class_exists('Google_Client')) {
19
+ require_once dirname(__FILE__) . '/../autoload.php';
20
+ }
21
+
22
+ /**
23
+ * A persistent storage class based on the memcache, which is not
24
+ * really very persistent, as soon as you restart your memcache daemon
25
+ * the storage will be wiped.
26
+ *
27
+ * Will use either the memcache or memcached extensions, preferring
28
+ * memcached.
29
+ *
30
+ * @author Chris Chabot <chabotc@google.com>
31
+ */
32
+ class Google_Cache_Memcache extends Google_Cache_Abstract
33
+ {
34
+ private $connection = false;
35
+ private $mc = false;
36
+ private $host;
37
+ private $port;
38
+
39
+ /**
40
+ * @var Google_Client the current client
41
+ */
42
+ private $client;
43
+
44
+ public function __construct(Google_Client $client)
45
+ {
46
+ if (!function_exists('memcache_connect') && !class_exists("Memcached")) {
47
+ $error = "Memcache functions not available";
48
+
49
+ $client->getLogger()->error($error);
50
+ throw new Google_Cache_Exception($error);
51
+ }
52
+
53
+ $this->client = $client;
54
+
55
+ if ($client->isAppEngine()) {
56
+ // No credentials needed for GAE.
57
+ $this->mc = new Memcached();
58
+ $this->connection = true;
59
+ } else {
60
+ $this->host = $client->getClassConfig($this, 'host');
61
+ $this->port = $client->getClassConfig($this, 'port');
62
+ if (empty($this->host) || (empty($this->port) && (string) $this->port != "0")) {
63
+ $error = "You need to supply a valid memcache host and port";
64
+
65
+ $client->getLogger()->error($error);
66
+ throw new Google_Cache_Exception($error);
67
+ }
68
+ }
69
+ }
70
+
71
+ /**
72
+ * @inheritDoc
73
+ */
74
+ public function get($key, $expiration = false)
75
+ {
76
+ $this->connect();
77
+ $ret = false;
78
+ if ($this->mc) {
79
+ $ret = $this->mc->get($key);
80
+ } else {
81
+ $ret = memcache_get($this->connection, $key);
82
+ }
83
+ if ($ret === false) {
84
+ $this->client->getLogger()->debug(
85
+ 'Memcache cache miss',
86
+ array('key' => $key)
87
+ );
88
+ return false;
89
+ }
90
+ if (is_numeric($expiration) && (time() - $ret['time'] > $expiration)) {
91
+ $this->client->getLogger()->debug(
92
+ 'Memcache cache miss (expired)',
93
+ array('key' => $key, 'var' => $ret)
94
+ );
95
+ $this->delete($key);
96
+ return false;
97
+ }
98
+
99
+ $this->client->getLogger()->debug(
100
+ 'Memcache cache hit',
101
+ array('key' => $key, 'var' => $ret)
102
+ );
103
+
104
+ return $ret['data'];
105
+ }
106
+
107
+ /**
108
+ * @inheritDoc
109
+ * @param string $key
110
+ * @param string $value
111
+ * @throws Google_Cache_Exception
112
+ */
113
+ public function set($key, $value)
114
+ {
115
+ $this->connect();
116
+ // we store it with the cache_time default expiration so objects will at
117
+ // least get cleaned eventually.
118
+ $data = array('time' => time(), 'data' => $value);
119
+ $rc = false;
120
+ if ($this->mc) {
121
+ $rc = $this->mc->set($key, $data);
122
+ } else {
123
+ $rc = memcache_set($this->connection, $key, $data, false);
124
+ }
125
+ if ($rc == false) {
126
+ $this->client->getLogger()->error(
127
+ 'Memcache cache set failed',
128
+ array('key' => $key, 'var' => $data)
129
+ );
130
+
131
+ throw new Google_Cache_Exception("Couldn't store data in cache");
132
+ }
133
+
134
+ $this->client->getLogger()->debug(
135
+ 'Memcache cache set',
136
+ array('key' => $key, 'var' => $data)
137
+ );
138
+ }
139
+
140
+ /**
141
+ * @inheritDoc
142
+ * @param String $key
143
+ */
144
+ public function delete($key)
145
+ {
146
+ $this->connect();
147
+ if ($this->mc) {
148
+ $this->mc->delete($key, 0);
149
+ } else {
150
+ memcache_delete($this->connection, $key, 0);
151
+ }
152
+
153
+ $this->client->getLogger()->debug(
154
+ 'Memcache cache delete',
155
+ array('key' => $key)
156
+ );
157
+ }
158
+
159
+ /**
160
+ * Lazy initialiser for memcache connection. Uses pconnect for to take
161
+ * advantage of the persistence pool where possible.
162
+ */
163
+ private function connect()
164
+ {
165
+ if ($this->connection) {
166
+ return;
167
+ }
168
+
169
+ if (class_exists("Memcached")) {
170
+ $this->mc = new Memcached();
171
+ $this->mc->addServer($this->host, $this->port);
172
+ $this->connection = true;
173
+ } else {
174
+ $this->connection = memcache_pconnect($this->host, $this->port);
175
+ }
176
+
177
+ if (! $this->connection) {
178
+ $error = "Couldn't connect to memcache server";
179
+
180
+ $this->client->getLogger()->error($error);
181
+ throw new Google_Cache_Exception($error);
182
+ }
183
+ }
184
+ }
lib/Google2/Cache/Null.php ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2014 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ if (!class_exists('Google_Client')) {
19
+ require_once dirname(__FILE__) . '/../autoload.php';
20
+ }
21
+
22
+ /**
23
+ * A blank storage class, for cases where caching is not
24
+ * required.
25
+ */
26
+ class Google_Cache_Null extends Google_Cache_Abstract
27
+ {
28
+ public function __construct(Google_Client $client)
29
+ {
30
+
31
+ }
32
+
33
+ /**
34
+ * @inheritDoc
35
+ */
36
+ public function get($key, $expiration = false)
37
+ {
38
+ return false;
39
+ }
40
+
41
+ /**
42
+ * @inheritDoc
43
+ */
44
+ public function set($key, $value)
45
+ {
46
+ // Nop.
47
+ }
48
+
49
+ /**
50
+ * @inheritDoc
51
+ * @param String $key
52
+ */
53
+ public function delete($key)
54
+ {
55
+ // Nop.
56
+ }
57
+ }
lib/Google2/Client.php ADDED
@@ -0,0 +1,712 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2010 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ if (!class_exists('Google_Client')) {
19
+ require_once dirname(__FILE__) . '/../autoload.php';
20
+ }
21
+
22
+ /**
23
+ * The Google API Client
24
+ * http://code.google.com/p/google-api-php-client/
25
+ */
26
+ class Google_Client
27
+ {
28
+ const LIBVER = "1.1.4";
29
+ const USER_AGENT_SUFFIX = "google-api-php-client/";
30
+ /**
31
+ * @var Google_Auth_Abstract $auth
32
+ */
33
+ private $auth;
34
+
35
+ /**
36
+ * @var Google_IO_Abstract $io
37
+ */
38
+ private $io;
39
+
40
+ /**
41
+ * @var Google_Cache_Abstract $cache
42
+ */
43
+ private $cache;
44
+
45
+ /**
46
+ * @var Google_Config $config
47
+ */
48
+ private $config;
49
+
50
+ /**
51
+ * @var Google_Logger_Abstract $logger
52
+ */
53
+ private $logger;
54
+
55
+ /**
56
+ * @var boolean $deferExecution
57
+ */
58
+ private $deferExecution = false;
59
+
60
+ /** @var array $scopes */
61
+ // Scopes requested by the client
62
+ protected $requestedScopes = array();
63
+
64
+ // definitions of services that are discovered.
65
+ protected $services = array();
66
+
67
+ // Used to track authenticated state, can't discover services after doing authenticate()
68
+ private $authenticated = false;
69
+
70
+ /**
71
+ * Construct the Google Client.
72
+ *
73
+ * @param $config Google_Config or string for the ini file to load
74
+ */
75
+ public function __construct($config = null)
76
+ {
77
+ if (is_string($config) && strlen($config)) {
78
+ $config = new Google_Config($config);
79
+ } else if ( !($config instanceof Google_Config)) {
80
+ $config = new Google_Config();
81
+
82
+ if ($this->isAppEngine()) {
83
+ // Automatically use Memcache if we're in AppEngine.
84
+ $config->setCacheClass('Google_Cache_Memcache');
85
+ }
86
+
87
+ if (version_compare(phpversion(), "5.3.4", "<=") || $this->isAppEngine()) {
88
+ // Automatically disable compress.zlib, as currently unsupported.
89
+ $config->setClassConfig('Google_Http_Request', 'disable_gzip', true);
90
+ }
91
+ }
92
+
93
+ if ($config->getIoClass() == Google_Config::USE_AUTO_IO_SELECTION) {
94
+ if (function_exists('curl_version') && function_exists('curl_exec')
95
+ && !$this->isAppEngine()) {
96
+ $config->setIoClass("Google_IO_Curl");
97
+ } else {
98
+ $config->setIoClass("Google_IO_Stream");
99
+ }
100
+ }
101
+
102
+ $this->config = $config;
103
+ }
104
+
105
+ /**
106
+ * Get a string containing the version of the library.
107
+ *
108
+ * @return string
109
+ */
110
+ public function getLibraryVersion()
111
+ {
112
+ return self::LIBVER;
113
+ }
114
+
115
+ /**
116
+ * Attempt to exchange a code for an valid authentication token.
117
+ * Helper wrapped around the OAuth 2.0 implementation.
118
+ *
119
+ * @param $code string code from accounts.google.com
120
+ * @return string token
121
+ */
122
+ public function authenticate($code)
123
+ {
124
+ $this->authenticated = true;
125
+ return $this->getAuth()->authenticate($code);
126
+ }
127
+
128
+ /**
129
+ * Loads a service account key and parameters from a JSON
130
+ * file from the Google Developer Console. Uses that and the
131
+ * given array of scopes to return an assertion credential for
132
+ * use with refreshTokenWithAssertionCredential.
133
+ *
134
+ * @param string $jsonLocation File location of the project-key.json.
135
+ * @param array $scopes The scopes to assert.
136
+ * @return Google_Auth_AssertionCredentials.
137
+ * @
138
+ */
139
+ public function loadServiceAccountJson($jsonLocation, $scopes)
140
+ {
141
+ $data = json_decode(file_get_contents($jsonLocation));
142
+ if (isset($data->type) && $data->type == 'service_account') {
143
+ // Service Account format.
144
+ $cred = new Google_Auth_AssertionCredentials(
145
+ $data->client_email,
146
+ $scopes,
147
+ $data->private_key
148
+ );
149
+ return $cred;
150
+ } else {
151
+ throw new Google_Exception("Invalid service account JSON file.");
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Set the auth config from the JSON string provided.
157
+ * This structure should match the file downloaded from
158
+ * the "Download JSON" button on in the Google Developer
159
+ * Console.
160
+ * @param string $json the configuration json
161
+ * @throws Google_Exception
162
+ */
163
+ public function setAuthConfig($json)
164
+ {
165
+ $data = json_decode($json);
166
+ $key = isset($data->installed) ? 'installed' : 'web';
167
+ if (!isset($data->$key)) {
168
+ throw new Google_Exception("Invalid client secret JSON file.");
169
+ }
170
+ $this->setClientId($data->$key->client_id);
171
+ $this->setClientSecret($data->$key->client_secret);
172
+ if (isset($data->$key->redirect_uris)) {
173
+ $this->setRedirectUri($data->$key->redirect_uris[0]);
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Set the auth config from the JSON file in the path
179
+ * provided. This should match the file downloaded from
180
+ * the "Download JSON" button on in the Google Developer
181
+ * Console.
182
+ * @param string $file the file location of the client json
183
+ */
184
+ public function setAuthConfigFile($file)
185
+ {
186
+ $this->setAuthConfig(file_get_contents($file));
187
+ }
188
+
189
+ /**
190
+ * @throws Google_Auth_Exception
191
+ * @return array
192
+ * @visible For Testing
193
+ */
194
+ public function prepareScopes()
195
+ {
196
+ if (empty($this->requestedScopes)) {
197
+ throw new Google_Auth_Exception("No scopes specified");
198
+ }
199
+ $scopes = implode(' ', $this->requestedScopes);
200
+ return $scopes;
201
+ }
202
+
203
+ /**
204
+ * Set the OAuth 2.0 access token using the string that resulted from calling createAuthUrl()
205
+ * or Google_Client#getAccessToken().
206
+ * @param string $accessToken JSON encoded string containing in the following format:
207
+ * {"access_token":"TOKEN", "refresh_token":"TOKEN", "token_type":"Bearer",
208
+ * "expires_in":3600, "id_token":"TOKEN", "created":1320790426}
209
+ */
210
+ public function setAccessToken($accessToken)
211
+ {
212
+ if ($accessToken == 'null') {
213
+ $accessToken = null;
214
+ }
215
+ $this->getAuth()->setAccessToken($accessToken);
216
+ }
217
+
218
+
219
+
220
+ /**
221
+ * Set the authenticator object
222
+ * @param Google_Auth_Abstract $auth
223
+ */
224
+ public function setAuth(Google_Auth_Abstract $auth)
225
+ {
226
+ $this->config->setAuthClass(get_class($auth));
227
+ $this->auth = $auth;
228
+ }
229
+
230
+ /**
231
+ * Set the IO object
232
+ * @param Google_IO_Abstract $io
233
+ */
234
+ public function setIo(Google_IO_Abstract $io)
235
+ {
236
+ $this->config->setIoClass(get_class($io));
237
+ $this->io = $io;
238
+ }
239
+
240
+ /**
241
+ * Set the Cache object
242
+ * @param Google_Cache_Abstract $cache
243
+ */
244
+ public function setCache(Google_Cache_Abstract $cache)
245
+ {
246
+ $this->config->setCacheClass(get_class($cache));
247
+ $this->cache = $cache;
248
+ }
249
+
250
+ /**
251
+ * Set the Logger object
252
+ * @param Google_Logger_Abstract $logger
253
+ */
254
+ public function setLogger(Google_Logger_Abstract $logger)
255
+ {
256
+ $this->config->setLoggerClass(get_class($logger));
257
+ $this->logger = $logger;
258
+ }
259
+
260
+ /**
261
+ * Construct the OAuth 2.0 authorization request URI.
262
+ * @return string
263
+ */
264
+ public function createAuthUrl()
265
+ {
266
+ $scopes = $this->prepareScopes();
267
+ return $this->getAuth()->createAuthUrl($scopes);
268
+ }
269
+
270
+ /**
271
+ * Get the OAuth 2.0 access token.
272
+ * @return string $accessToken JSON encoded string in the following format:
273
+ * {"access_token":"TOKEN", "refresh_token":"TOKEN", "token_type":"Bearer",
274
+ * "expires_in":3600,"id_token":"TOKEN", "created":1320790426}
275
+ */
276
+ public function getAccessToken()
277
+ {
278
+ $token = $this->getAuth()->getAccessToken();
279
+ // The response is json encoded, so could be the string null.
280
+ // It is arguable whether this check should be here or lower
281
+ // in the library.
282
+ return (null == $token || 'null' == $token || '[]' == $token) ? null : $token;
283
+ }
284
+
285
+ /**
286
+ * Get the OAuth 2.0 refresh token.
287
+ * @return string $refreshToken refresh token or null if not available
288
+ */
289
+ public function getRefreshToken()
290
+ {
291
+ return $this->getAuth()->getRefreshToken();
292
+ }
293
+
294
+ /**
295
+ * Returns if the access_token is expired.
296
+ * @return bool Returns True if the access_token is expired.
297
+ */
298
+ public function isAccessTokenExpired()
299
+ {
300
+ return $this->getAuth()->isAccessTokenExpired();
301
+ }
302
+
303
+ /**
304
+ * Set OAuth 2.0 "state" parameter to achieve per-request customization.
305
+ * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-3.1.2.2
306
+ * @param string $state
307
+ */
308
+ public function setState($state)
309
+ {
310
+ $this->getAuth()->setState($state);
311
+ }
312
+
313
+ /**
314
+ * @param string $accessType Possible values for access_type include:
315
+ * {@code "offline"} to request offline access from the user.
316
+ * {@code "online"} to request online access from the user.
317
+ */
318
+ public function setAccessType($accessType)
319
+ {
320
+ $this->config->setAccessType($accessType);
321
+ }
322
+
323
+ /**
324
+ * @param string $approvalPrompt Possible values for approval_prompt include:
325
+ * {@code "force"} to force the approval UI to appear. (This is the default value)
326
+ * {@code "auto"} to request auto-approval when possible.
327
+ */
328
+ public function setApprovalPrompt($approvalPrompt)
329
+ {
330
+ $this->config->setApprovalPrompt($approvalPrompt);
331
+ }
332
+
333
+ /**
334
+ * Set the login hint, email address or sub id.
335
+ * @param string $loginHint
336
+ */
337
+ public function setLoginHint($loginHint)
338
+ {
339
+ $this->config->setLoginHint($loginHint);
340
+ }
341
+
342
+ /**
343
+ * Set the application name, this is included in the User-Agent HTTP header.
344
+ * @param string $applicationName
345
+ */
346
+ public function setApplicationName($applicationName)
347
+ {
348
+ $this->config->setApplicationName($applicationName);
349
+ }
350
+
351
+ /**
352
+ * Set the OAuth 2.0 Client ID.
353
+ * @param string $clientId
354
+ */
355
+ public function setClientId($clientId)
356
+ {
357
+ $this->config->setClientId($clientId);
358
+ }
359
+
360
+ /**
361
+ * Set the OAuth 2.0 Client Secret.
362
+ * @param string $clientSecret
363
+ */
364
+ public function setClientSecret($clientSecret)
365
+ {
366
+ $this->config->setClientSecret($clientSecret);
367
+ }
368
+
369
+ /**
370
+ * Set the OAuth 2.0 Redirect URI.
371
+ * @param string $redirectUri
372
+ */
373
+ public function setRedirectUri($redirectUri)
374
+ {
375
+ $this->config->setRedirectUri($redirectUri);
376
+ }
377
+
378
+ /**
379
+ * If 'plus.login' is included in the list of requested scopes, you can use
380
+ * this method to define types of app activities that your app will write.
381
+ * You can find a list of available types here:
382
+ * @link https://developers.google.com/+/api/moment-types
383
+ *
384
+ * @param array $requestVisibleActions Array of app activity types
385
+ */
386
+ public function setRequestVisibleActions($requestVisibleActions)
387
+ {
388
+ if (is_array($requestVisibleActions)) {
389
+ $requestVisibleActions = join(" ", $requestVisibleActions);
390
+ }
391
+ $this->config->setRequestVisibleActions($requestVisibleActions);
392
+ }
393
+
394
+ /**
395
+ * Set the developer key to use, these are obtained through the API Console.
396
+ * @see http://code.google.com/apis/console-help/#generatingdevkeys
397
+ * @param string $developerKey
398
+ */
399
+ public function setDeveloperKey($developerKey)
400
+ {
401
+ $this->config->setDeveloperKey($developerKey);
402
+ }
403
+
404
+ /**
405
+ * Set the hd (hosted domain) parameter streamlines the login process for
406
+ * Google Apps hosted accounts. By including the domain of the user, you
407
+ * restrict sign-in to accounts at that domain.
408
+ * @param $hd string - the domain to use.
409
+ */
410
+ public function setHostedDomain($hd)
411
+ {
412
+ $this->config->setHostedDomain($hd);
413
+ }
414
+
415
+ /**
416
+ * Set the prompt hint. Valid values are none, consent and select_account.
417
+ * If no value is specified and the user has not previously authorized
418
+ * access, then the user is shown a consent screen.
419
+ * @param $prompt string
420
+ */
421
+ public function setPrompt($prompt)
422
+ {
423
+ $this->config->setPrompt($prompt);
424
+ }
425
+
426
+ /**
427
+ * openid.realm is a parameter from the OpenID 2.0 protocol, not from OAuth
428
+ * 2.0. It is used in OpenID 2.0 requests to signify the URL-space for which
429
+ * an authentication request is valid.
430
+ * @param $realm string - the URL-space to use.
431
+ */
432
+ public function setOpenidRealm($realm)
433
+ {
434
+ $this->config->setOpenidRealm($realm);
435
+ }
436
+
437
+ /**
438
+ * If this is provided with the value true, and the authorization request is
439
+ * granted, the authorization will include any previous authorizations
440
+ * granted to this user/application combination for other scopes.
441
+ * @param $include boolean - the URL-space to use.
442
+ */
443
+ public function setIncludeGrantedScopes($include)
444
+ {
445
+ $this->config->setIncludeGrantedScopes($include);
446
+ }
447
+
448
+ /**
449
+ * Fetches a fresh OAuth 2.0 access token with the given refresh token.
450
+ * @param string $refreshToken
451
+ */
452
+ public function refreshToken($refreshToken)
453
+ {
454
+ $this->getAuth()->refreshToken($refreshToken);
455
+ }
456
+
457
+ /**
458
+ * Revoke an OAuth2 access token or refresh token. This method will revoke the current access
459
+ * token, if a token isn't provided.
460
+ * @throws Google_Auth_Exception
461
+ * @param string|null $token The token (access token or a refresh token) that should be revoked.
462
+ * @return boolean Returns True if the revocation was successful, otherwise False.
463
+ */
464
+ public function revokeToken($token = null)
465
+ {
466
+ return $this->getAuth()->revokeToken($token);
467
+ }
468
+
469
+ /**
470
+ * Verify an id_token. This method will verify the current id_token, if one
471
+ * isn't provided.
472
+ * @throws Google_Auth_Exception
473
+ * @param string|null $token The token (id_token) that should be verified.
474
+ * @return Google_Auth_LoginTicket Returns an apiLoginTicket if the verification was
475
+ * successful.
476
+ */
477
+ public function verifyIdToken($token = null)
478
+ {
479
+ return $this->getAuth()->verifyIdToken($token);
480
+ }
481
+
482
+ /**
483
+ * Verify a JWT that was signed with your own certificates.
484
+ *
485
+ * @param $id_token string The JWT token
486
+ * @param $cert_location array of certificates
487
+ * @param $audience string the expected consumer of the token
488
+ * @param $issuer string the expected issuer, defaults to Google
489
+ * @param [$max_expiry] the max lifetime of a token, defaults to MAX_TOKEN_LIFETIME_SECS
490
+ * @return mixed token information if valid, false if not
491
+ */
492
+ public function verifySignedJwt($id_token, $cert_location, $audience, $issuer, $max_expiry = null)
493
+ {
494
+ $auth = new Google_Auth_OAuth2($this);
495
+ $certs = $auth->retrieveCertsFromLocation($cert_location);
496
+ return $auth->verifySignedJwtWithCerts($id_token, $certs, $audience, $issuer, $max_expiry);
497
+ }
498
+
499
+ /**
500
+ * @param $creds Google_Auth_AssertionCredentials
501
+ */
502
+ public function setAssertionCredentials(Google_Auth_AssertionCredentials $creds)
503
+ {
504
+ $this->getAuth()->setAssertionCredentials($creds);
505
+ }
506
+
507
+ /**
508
+ * Set the scopes to be requested. Must be called before createAuthUrl().
509
+ * Will remove any previously configured scopes.
510
+ * @param array $scopes, ie: array('https://www.googleapis.com/auth/plus.login',
511
+ * 'https://www.googleapis.com/auth/moderator')
512
+ */
513
+ public function setScopes($scopes)
514
+ {
515
+ $this->requestedScopes = array();
516
+ $this->addScope($scopes);
517
+ }
518
+
519
+ /**
520
+ * This functions adds a scope to be requested as part of the OAuth2.0 flow.
521
+ * Will append any scopes not previously requested to the scope parameter.
522
+ * A single string will be treated as a scope to request. An array of strings
523
+ * will each be appended.
524
+ * @param $scope_or_scopes string|array e.g. "profile"
525
+ */
526
+ public function addScope($scope_or_scopes)
527
+ {
528
+ if (is_string($scope_or_scopes) && !in_array($scope_or_scopes, $this->requestedScopes)) {
529
+ $this->requestedScopes[] = $scope_or_scopes;
530
+ } else if (is_array($scope_or_scopes)) {
531
+ foreach ($scope_or_scopes as $scope) {
532
+ $this->addScope($scope);
533
+ }
534
+ }
535
+ }
536
+
537
+ /**
538
+ * Returns the list of scopes requested by the client
539
+ * @return array the list of scopes
540
+ *
541
+ */
542
+ public function getScopes()
543
+ {
544
+ return $this->requestedScopes;
545
+ }
546
+
547
+ /**
548
+ * Declare whether batch calls should be used. This may increase throughput
549
+ * by making multiple requests in one connection.
550
+ *
551
+ * @param boolean $useBatch True if the batch support should
552
+ * be enabled. Defaults to False.
553
+ */
554
+ public function setUseBatch($useBatch)
555
+ {
556
+ // This is actually an alias for setDefer.
557
+ $this->setDefer($useBatch);
558
+ }
559
+
560
+ /**
561
+ * Declare whether making API calls should make the call immediately, or
562
+ * return a request which can be called with ->execute();
563
+ *
564
+ * @param boolean $defer True if calls should not be executed right away.
565
+ */
566
+ public function setDefer($defer)
567
+ {
568
+ $this->deferExecution = $defer;
569
+ }
570
+
571
+ /**
572
+ * Helper method to execute deferred HTTP requests.
573
+ *
574
+ * @param $request Google_Http_Request|Google_Http_Batch
575
+ * @throws Google_Exception
576
+ * @return object of the type of the expected class or array.
577
+ */
578
+ public function execute($request)
579
+ {
580
+ if ($request instanceof Google_Http_Request) {
581
+ $request->setUserAgent(
582
+ $this->getApplicationName()
583
+ . " " . self::USER_AGENT_SUFFIX
584
+ . $this->getLibraryVersion()
585
+ );
586
+ if (!$this->getClassConfig("Google_Http_Request", "disable_gzip")) {
587
+ $request->enableGzip();
588
+ }
589
+ $request->maybeMoveParametersToBody();
590
+ return Google_Http_REST::execute($this, $request);
591
+ } else if ($request instanceof Google_Http_Batch) {
592
+ return $request->execute();
593
+ } else {
594
+ throw new Google_Exception("Do not know how to execute this type of object.");
595
+ }
596
+ }
597
+
598
+ /**
599
+ * Whether or not to return raw requests
600
+ * @return boolean
601
+ */
602
+ public function shouldDefer()
603
+ {
604
+ return $this->deferExecution;
605
+ }
606
+
607
+ /**
608
+ * @return Google_Auth_Abstract Authentication implementation
609
+ */
610
+ public function getAuth()
611
+ {
612
+ if (!isset($this->auth)) {
613
+ $class = $this->config->getAuthClass();
614
+ $this->auth = new $class($this);
615
+ }
616
+ return $this->auth;
617
+ }
618
+
619
+ /**
620
+ * @return Google_IO_Abstract IO implementation
621
+ */
622
+ public function getIo()
623
+ {
624
+ if (!isset($this->io)) {
625
+ $class = $this->config->getIoClass();
626
+ $this->io = new $class($this);
627
+ }
628
+ return $this->io;
629
+ }
630
+
631
+ /**
632
+ * @return Google_Cache_Abstract Cache implementation
633
+ */
634
+ public function getCache()
635
+ {
636
+ if (!isset($this->cache)) {
637
+ $class = $this->config->getCacheClass();
638
+ $this->cache = new $class($this);
639
+ }
640
+ return $this->cache;
641
+ }
642
+
643
+ /**
644
+ * @return Google_Logger_Abstract Logger implementation
645
+ */
646
+ public function getLogger()
647
+ {
648
+ if (!isset($this->logger)) {
649
+ $class = $this->config->getLoggerClass();
650
+ $this->logger = new $class($this);
651
+ }
652
+ return $this->logger;
653
+ }
654
+
655
+ /**
656
+ * Retrieve custom configuration for a specific class.
657
+ * @param $class string|object - class or instance of class to retrieve
658
+ * @param $key string optional - key to retrieve
659
+ * @return array
660
+ */
661
+ public function getClassConfig($class, $key = null)
662
+ {
663
+ if (!is_string($class)) {
664
+ $class = get_class($class);
665
+ }
666
+ return $this->config->getClassConfig($class, $key);
667
+ }
668
+
669
+ /**
670
+ * Set configuration specific to a given class.
671
+ * $config->setClassConfig('Google_Cache_File',
672
+ * array('directory' => '/tmp/cache'));
673
+ * @param $class string|object - The class name for the configuration
674
+ * @param $config string key or an array of configuration values
675
+ * @param $value string optional - if $config is a key, the value
676
+ *
677
+ */
678
+ public function setClassConfig($class, $config, $value = null)
679
+ {
680
+ if (!is_string($class)) {
681
+ $class = get_class($class);
682
+ }
683
+ $this->config->setClassConfig($class, $config, $value);
684
+
685
+ }
686
+
687
+ /**
688
+ * @return string the base URL to use for calls to the APIs
689
+ */
690
+ public function getBasePath()
691
+ {
692
+ return $this->config->getBasePath();
693
+ }
694
+
695
+ /**
696
+ * @return string the name of the application
697
+ */
698
+ public function getApplicationName()
699
+ {
700
+ return $this->config->getApplicationName();
701
+ }
702
+
703
+ /**
704
+ * Are we running in Google AppEngine?
705
+ * return bool
706
+ */
707
+ public function isAppEngine()
708
+ {
709
+ return (isset($_SERVER['SERVER_SOFTWARE']) &&
710
+ strpos($_SERVER['SERVER_SOFTWARE'], 'Google App Engine') !== false);
711
+ }
712
+ }
lib/Google2/Collection.php ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if (!class_exists('Google_Client')) {
4
+ require_once dirname(__FILE__) . '/../autoload.php';
5
+ }
6
+
7
+ /**
8
+ * Extension to the regular Google_Model that automatically
9
+ * exposes the items array for iteration, so you can just
10
+ * iterate over the object rather than a reference inside.
11
+ */
12
+ class Google_Collection extends Google_Model implements Iterator, Countable
13
+ {
14
+ protected $collection_key = 'items';
15
+
16
+ public function rewind()
17
+ {
18
+ if (isset($this->modelData[$this->collection_key])
19
+ && is_array($this->modelData[$this->collection_key])) {
20
+ reset($this->modelData[$this->collection_key]);
21
+ }
22
+ }
23
+
24
+ public function current()
25
+ {
26
+ $this->coerceType($this->key());
27
+ if (is_array($this->modelData[$this->collection_key])) {
28
+ return current($this->modelData[$this->collection_key]);
29
+ }
30
+ }
31
+
32
+ public function key()
33
+ {
34
+ if (isset($this->modelData[$this->collection_key])
35
+ && is_array($this->modelData[$this->collection_key])) {
36
+ return key($this->modelData[$this->collection_key]);
37
+ }
38
+ }
39
+
40
+ public function next()
41
+ {
42
+ return next($this->modelData[$this->collection_key]);
43
+ }
44
+
45
+ public function valid()
46
+ {
47
+ $key = $this->key();
48
+ return $key !== null && $key !== false;
49
+ }
50
+
51
+ public function count()
52
+ {
53
+ if (!isset($this->modelData[$this->collection_key])) {
54
+ return 0;
55
+ }
56
+ return count($this->modelData[$this->collection_key]);
57
+ }
58
+
59
+ public function offsetExists ($offset)
60
+ {
61
+ if (!is_numeric($offset)) {
62
+ return parent::offsetExists($offset);
63
+ }
64
+ return isset($this->modelData[$this->collection_key][$offset]);
65
+ }
66
+
67
+ public function offsetGet($offset)
68
+ {
69
+ if (!is_numeric($offset)) {
70
+ return parent::offsetGet($offset);
71
+ }
72
+ $this->coerceType($offset);
73
+ return $this->modelData[$this->collection_key][$offset];
74
+ }
75
+
76
+ public function offsetSet($offset, $value)
77
+ {
78
+ if (!is_numeric($offset)) {
79
+ return parent::offsetSet($offset, $value);
80
+ }
81
+ $this->modelData[$this->collection_key][$offset] = $value;
82
+ }
83
+
84
+ public function offsetUnset($offset)
85
+ {
86
+ if (!is_numeric($offset)) {
87
+ return parent::offsetUnset($offset);
88
+ }
89
+ unset($this->modelData[$this->collection_key][$offset]);
90
+ }
91
+
92
+ private function coerceType($offset)
93
+ {
94
+ $typeKey = $this->keyType($this->collection_key);
95
+ if (isset($this->$typeKey) && !is_object($this->modelData[$this->collection_key][$offset])) {
96
+ $type = $this->$typeKey;
97
+ $this->modelData[$this->collection_key][$offset] =
98
+ new $type($this->modelData[$this->collection_key][$offset]);
99
+ }
100
+ }
101
+ }
lib/Google2/Config.php ADDED
@@ -0,0 +1,452 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2010 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ /**
19
+ * A class to contain the library configuration for the Google API client.
20
+ */
21
+ class Google_Config
22
+ {
23
+ const GZIP_DISABLED = true;
24
+ const GZIP_ENABLED = false;
25
+ const GZIP_UPLOADS_ENABLED = true;
26
+ const GZIP_UPLOADS_DISABLED = false;
27
+ const USE_AUTO_IO_SELECTION = "auto";
28
+ const TASK_RETRY_NEVER = 0;
29
+ const TASK_RETRY_ONCE = 1;
30
+ const TASK_RETRY_ALWAYS = -1;
31
+ protected $configuration;
32
+
33
+ /**
34
+ * Create a new Google_Config. Can accept an ini file location with the
35
+ * local configuration. For example:
36
+ * application_name="My App"
37
+ *
38
+ * @param [$ini_file_location] - optional - The location of the ini file to load
39
+ */
40
+ public function __construct($ini_file_location = null)
41
+ {
42
+ $this->configuration = array(
43
+ // The application_name is included in the User-Agent HTTP header.
44
+ 'application_name' => '',
45
+
46
+ // Which Authentication, Storage and HTTP IO classes to use.
47
+ 'auth_class' => 'Google_Auth_OAuth2',
48
+ 'io_class' => self::USE_AUTO_IO_SELECTION,
49
+ 'cache_class' => 'Google_Cache_File',
50
+ 'logger_class' => 'Google_Logger_Null',
51
+
52
+ // Don't change these unless you're working against a special development
53
+ // or testing environment.
54
+ 'base_path' => 'https://www.googleapis.com',
55
+
56
+ // Definition of class specific values, like file paths and so on.
57
+ 'classes' => array(
58
+ 'Google_IO_Abstract' => array(
59
+ 'request_timeout_seconds' => 100,
60
+ ),
61
+ 'Google_Logger_Abstract' => array(
62
+ 'level' => 'debug',
63
+ 'log_format' => "[%datetime%] %level%: %message% %context%\n",
64
+ 'date_format' => 'd/M/Y:H:i:s O',
65
+ 'allow_newlines' => true
66
+ ),
67
+ 'Google_Logger_File' => array(
68
+ 'file' => 'php://stdout',
69
+ 'mode' => 0640,
70
+ 'lock' => false,
71
+ ),
72
+ 'Google_Http_Request' => array(
73
+ // Disable the use of gzip on calls if set to true. Defaults to false.
74
+ 'disable_gzip' => self::GZIP_ENABLED,
75
+
76
+ // We default gzip to disabled on uploads even if gzip is otherwise
77
+ // enabled, due to some issues seen with small packet sizes for uploads.
78
+ // Please test with this option before enabling gzip for uploads in
79
+ // a production environment.
80
+ 'enable_gzip_for_uploads' => self::GZIP_UPLOADS_DISABLED,
81
+ ),
82
+ // If you want to pass in OAuth 2.0 settings, they will need to be
83
+ // structured like this.
84
+ 'Google_Auth_OAuth2' => array(
85
+ // Keys for OAuth 2.0 access, see the API console at
86
+ // https://developers.google.com/console
87
+ 'client_id' => '',
88
+ 'client_secret' => '',
89
+ 'redirect_uri' => '',
90
+
91
+ // Simple API access key, also from the API console. Ensure you get
92
+ // a Server key, and not a Browser key.
93
+ 'developer_key' => '',
94
+
95
+ // Other parameters.
96
+ 'hd' => '',
97
+ 'prompt' => '',
98
+ 'openid.realm' => '',
99
+ 'include_granted_scopes' => '',
100
+ 'login_hint' => '',
101
+ 'request_visible_actions' => '',
102
+ 'access_type' => 'online',
103
+ 'approval_prompt' => 'auto',
104
+ 'federated_signon_certs_url' =>
105
+ 'https://www.googleapis.com/oauth2/v1/certs',
106
+ ),
107
+ 'Google_Task_Runner' => array(
108
+ // Delays are specified in seconds
109
+ 'initial_delay' => 1,
110
+ 'max_delay' => 60,
111
+ // Base number for exponential backoff
112
+ 'factor' => 2,
113
+ // A random number between -jitter and jitter will be added to the
114
+ // factor on each iteration to allow for better distribution of
115
+ // retries.
116
+ 'jitter' => .5,
117
+ // Maximum number of retries allowed
118
+ 'retries' => 0
119
+ ),
120
+ 'Google_Service_Exception' => array(
121
+ 'retry_map' => array(
122
+ '500' => self::TASK_RETRY_ALWAYS,
123
+ '503' => self::TASK_RETRY_ALWAYS,
124
+ 'rateLimitExceeded' => self::TASK_RETRY_ALWAYS,
125
+ 'userRateLimitExceeded' => self::TASK_RETRY_ALWAYS
126
+ )
127
+ ),
128
+ 'Google_IO_Exception' => array(
129
+ 'retry_map' => !extension_loaded('curl') ? array() : array(
130
+ CURLE_COULDNT_RESOLVE_HOST => self::TASK_RETRY_ALWAYS,
131
+ CURLE_COULDNT_CONNECT => self::TASK_RETRY_ALWAYS,
132
+ CURLE_OPERATION_TIMEOUTED => self::TASK_RETRY_ALWAYS,
133
+ CURLE_SSL_CONNECT_ERROR => self::TASK_RETRY_ALWAYS,
134
+ CURLE_GOT_NOTHING => self::TASK_RETRY_ALWAYS
135
+ )
136
+ ),
137
+ // Set a default directory for the file cache.
138
+ 'Google_Cache_File' => array(
139
+ 'directory' => sys_get_temp_dir() . '/Google_Client'
140
+ )
141
+ ),
142
+ );
143
+ if ($ini_file_location) {
144
+ $ini = parse_ini_file($ini_file_location, true);
145
+ if (is_array($ini) && count($ini)) {
146
+ $merged_configuration = $ini + $this->configuration;
147
+ if (isset($ini['classes']) && isset($this->configuration['classes'])) {
148
+ $merged_configuration['classes'] = $ini['classes'] + $this->configuration['classes'];
149
+ }
150
+ $this->configuration = $merged_configuration;
151
+ }
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Set configuration specific to a given class.
157
+ * $config->setClassConfig('Google_Cache_File',
158
+ * array('directory' => '/tmp/cache'));
159
+ * @param $class string The class name for the configuration
160
+ * @param $config string key or an array of configuration values
161
+ * @param $value string optional - if $config is a key, the value
162
+ */
163
+ public function setClassConfig($class, $config, $value = null)
164
+ {
165
+ if (!is_array($config)) {
166
+ if (!isset($this->configuration['classes'][$class])) {
167
+ $this->configuration['classes'][$class] = array();
168
+ }
169
+ $this->configuration['classes'][$class][$config] = $value;
170
+ } else {
171
+ $this->configuration['classes'][$class] = $config;
172
+ }
173
+ }
174
+
175
+ public function getClassConfig($class, $key = null)
176
+ {
177
+ if (!isset($this->configuration['classes'][$class])) {
178
+ return null;
179
+ }
180
+ if ($key === null) {
181
+ return $this->configuration['classes'][$class];
182
+ } else {
183
+ return $this->configuration['classes'][$class][$key];
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Return the configured cache class.
189
+ * @return string
190
+ */
191
+ public function getCacheClass()
192
+ {
193
+ return $this->configuration['cache_class'];
194
+ }
195
+
196
+ /**
197
+ * Return the configured logger class.
198
+ * @return string
199
+ */
200
+ public function getLoggerClass()
201
+ {
202
+ return $this->configuration['logger_class'];
203
+ }
204
+
205
+ /**
206
+ * Return the configured Auth class.
207
+ * @return string
208
+ */
209
+ public function getAuthClass()
210
+ {
211
+ return $this->configuration['auth_class'];
212
+ }
213
+
214
+ /**
215
+ * Set the auth class.
216
+ *
217
+ * @param $class string the class name to set
218
+ */
219
+ public function setAuthClass($class)
220
+ {
221
+ $prev = $this->configuration['auth_class'];
222
+ if (!isset($this->configuration['classes'][$class]) &&
223
+ isset($this->configuration['classes'][$prev])) {
224
+ $this->configuration['classes'][$class] =
225
+ $this->configuration['classes'][$prev];
226
+ }
227
+ $this->configuration['auth_class'] = $class;
228
+ }
229
+
230
+ /**
231
+ * Set the IO class.
232
+ *
233
+ * @param $class string the class name to set
234
+ */
235
+ public function setIoClass($class)
236
+ {
237
+ $prev = $this->configuration['io_class'];
238
+ if (!isset($this->configuration['classes'][$class]) &&
239
+ isset($this->configuration['classes'][$prev])) {
240
+ $this->configuration['classes'][$class] =
241
+ $this->configuration['classes'][$prev];
242
+ }
243
+ $this->configuration['io_class'] = $class;
244
+ }
245
+
246
+ /**
247
+ * Set the cache class.
248
+ *
249
+ * @param $class string the class name to set
250
+ */
251
+ public function setCacheClass($class)
252
+ {
253
+ $prev = $this->configuration['cache_class'];
254
+ if (!isset($this->configuration['classes'][$class]) &&
255
+ isset($this->configuration['classes'][$prev])) {
256
+ $this->configuration['classes'][$class] =
257
+ $this->configuration['classes'][$prev];
258
+ }
259
+ $this->configuration['cache_class'] = $class;
260
+ }
261
+
262
+ /**
263
+ * Set the logger class.
264
+ *
265
+ * @param $class string the class name to set
266
+ */
267
+ public function setLoggerClass($class)
268
+ {
269
+ $prev = $this->configuration['logger_class'];
270
+ if (!isset($this->configuration['classes'][$class]) &&
271
+ isset($this->configuration['classes'][$prev])) {
272
+ $this->configuration['classes'][$class] =
273
+ $this->configuration['classes'][$prev];
274
+ }
275
+ $this->configuration['logger_class'] = $class;
276
+ }
277
+
278
+ /**
279
+ * Return the configured IO class.
280
+ *
281
+ * @return string
282
+ */
283
+ public function getIoClass()
284
+ {
285
+ return $this->configuration['io_class'];
286
+ }
287
+
288
+ /**
289
+ * Set the application name, this is included in the User-Agent HTTP header.
290
+ * @param string $name
291
+ */
292
+ public function setApplicationName($name)
293
+ {
294
+ $this->configuration['application_name'] = $name;
295
+ }
296
+
297
+ /**
298
+ * @return string the name of the application
299
+ */
300
+ public function getApplicationName()
301
+ {
302
+ return $this->configuration['application_name'];
303
+ }
304
+
305
+ /**
306
+ * Set the client ID for the auth class.
307
+ * @param $clientId string - the API console client ID
308
+ */
309
+ public function setClientId($clientId)
310
+ {
311
+ $this->setAuthConfig('client_id', $clientId);
312
+ }
313
+
314
+ /**
315
+ * Set the client secret for the auth class.
316
+ * @param $secret string - the API console client secret
317
+ */
318
+ public function setClientSecret($secret)
319
+ {
320
+ $this->setAuthConfig('client_secret', $secret);
321
+ }
322
+
323
+ /**
324
+ * Set the redirect uri for the auth class. Note that if using the
325
+ * Javascript based sign in flow, this should be the string 'postmessage'.
326
+ *
327
+ * @param $uri string - the URI that users should be redirected to
328
+ */
329
+ public function setRedirectUri($uri)
330
+ {
331
+ $this->setAuthConfig('redirect_uri', $uri);
332
+ }
333
+
334
+ /**
335
+ * Set the app activities for the auth class.
336
+ * @param $rva string a space separated list of app activity types
337
+ */
338
+ public function setRequestVisibleActions($rva)
339
+ {
340
+ $this->setAuthConfig('request_visible_actions', $rva);
341
+ }
342
+
343
+ /**
344
+ * Set the the access type requested (offline or online.)
345
+ * @param $access string - the access type
346
+ */
347
+ public function setAccessType($access)
348
+ {
349
+ $this->setAuthConfig('access_type', $access);
350
+ }
351
+
352
+ /**
353
+ * Set when to show the approval prompt (auto or force)
354
+ * @param $approval string - the approval request
355
+ */
356
+ public function setApprovalPrompt($approval)
357
+ {
358
+ $this->setAuthConfig('approval_prompt', $approval);
359
+ }
360
+
361
+ /**
362
+ * Set the login hint (email address or sub identifier)
363
+ * @param $hint string
364
+ */
365
+ public function setLoginHint($hint)
366
+ {
367
+ $this->setAuthConfig('login_hint', $hint);
368
+ }
369
+
370
+ /**
371
+ * Set the developer key for the auth class. Note that this is separate value
372
+ * from the client ID - if it looks like a URL, its a client ID!
373
+ * @param $key string - the API console developer key
374
+ */
375
+ public function setDeveloperKey($key)
376
+ {
377
+ $this->setAuthConfig('developer_key', $key);
378
+ }
379
+
380
+ /**
381
+ * Set the hd (hosted domain) parameter streamlines the login process for
382
+ * Google Apps hosted accounts. By including the domain of the user, you
383
+ * restrict sign-in to accounts at that domain.
384
+ *
385
+ * This should not be used to ensure security on your application - check
386
+ * the hd values within an id token (@see Google_Auth_LoginTicket) after sign
387
+ * in to ensure that the user is from the domain you were expecting.
388
+ *
389
+ * @param $hd string - the domain to use.
390
+ */
391
+ public function setHostedDomain($hd)
392
+ {
393
+ $this->setAuthConfig('hd', $hd);
394
+ }
395
+
396
+ /**
397
+ * Set the prompt hint. Valid values are none, consent and select_account.
398
+ * If no value is specified and the user has not previously authorized
399
+ * access, then the user is shown a consent screen.
400
+ * @param $prompt string
401
+ */
402
+ public function setPrompt($prompt)
403
+ {
404
+ $this->setAuthConfig('prompt', $prompt);
405
+ }
406
+
407
+ /**
408
+ * openid.realm is a parameter from the OpenID 2.0 protocol, not from OAuth
409
+ * 2.0. It is used in OpenID 2.0 requests to signify the URL-space for which
410
+ * an authentication request is valid.
411
+ * @param $realm string - the URL-space to use.
412
+ */
413
+ public function setOpenidRealm($realm)
414
+ {
415
+ $this->setAuthConfig('openid.realm', $realm);
416
+ }
417
+
418
+ /**
419
+ * If this is provided with the value true, and the authorization request is
420
+ * granted, the authorization will include any previous authorizations
421
+ * granted to this user/application combination for other scopes.
422
+ * @param $include boolean - the URL-space to use.
423
+ */
424
+ public function setIncludeGrantedScopes($include)
425
+ {
426
+ $this->setAuthConfig(
427
+ 'include_granted_scopes',
428
+ $include ? "true" : "false"
429
+ );
430
+ }
431
+
432
+ /**
433
+ * @return string the base URL to use for API calls
434
+ */
435
+ public function getBasePath()
436
+ {
437
+ return $this->configuration['base_path'];
438
+ }
439
+
440
+ /**
441
+ * Set the auth configuration for the current auth class.
442
+ * @param $key - the key to set
443
+ * @param $value - the parameter value
444
+ */
445
+ private function setAuthConfig($key, $value)
446
+ {
447
+ if (!isset($this->configuration['classes'][$this->getAuthClass()])) {
448
+ $this->configuration['classes'][$this->getAuthClass()] = array();
449
+ }
450
+ $this->configuration['classes'][$this->getAuthClass()][$key] = $value;
451
+ }
452
+ }
lib/Google2/Exception.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2013 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ class Google_Exception extends Exception
19
+ {
20
+ }
lib/Google2/Http/Batch.php ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2012 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ if (!class_exists('Google_Client')) {
19
+ require_once dirname(__FILE__) . '/../autoload.php';
20
+ }
21
+
22
+ /**
23
+ * Class to handle batched requests to the Google API service.
24
+ */
25
+ class Google_Http_Batch
26
+ {
27
+ /** @var string Multipart Boundary. */
28
+ private $boundary;
29
+
30
+ /** @var array service requests to be executed. */
31
+ private $requests = array();
32
+
33
+ /** @var Google_Client */
34
+ private $client;
35
+
36
+ private $expected_classes = array();
37
+
38
+ private $base_path;
39
+
40
+ public function __construct(Google_Client $client, $boundary = false)
41
+ {
42
+ $this->client = $client;
43
+ $this->base_path = $this->client->getBasePath();
44
+ $this->expected_classes = array();
45
+ $boundary = (false == $boundary) ? mt_rand() : $boundary;
46
+ $this->boundary = str_replace('"', '', $boundary);
47
+ }
48
+
49
+ public function add(Google_Http_Request $request, $key = false)
50
+ {
51
+ if (false == $key) {
52
+ $key = mt_rand();
53
+ }
54
+
55
+ $this->requests[$key] = $request;
56
+ }
57
+
58
+ public function execute()
59
+ {
60
+ $body = '';
61
+
62
+ /** @var Google_Http_Request $req */
63
+ foreach ($this->requests as $key => $req) {
64
+ $body .= "--{$this->boundary}\n";
65
+ $body .= $req->toBatchString($key) . "\n";
66
+ $this->expected_classes["response-" . $key] = $req->getExpectedClass();
67
+ }
68
+
69
+ $body = rtrim($body);
70
+ $body .= "\n--{$this->boundary}--";
71
+
72
+ $url = $this->base_path . '/batch';
73
+ $httpRequest = new Google_Http_Request($url, 'POST');
74
+ $httpRequest->setRequestHeaders(
75
+ array('Content-Type' => 'multipart/mixed; boundary=' . $this->boundary)
76
+ );
77
+
78
+ $httpRequest->setPostBody($body);
79
+ $response = $this->client->getIo()->makeRequest($httpRequest);
80
+
81
+ return $this->parseResponse($response);
82
+ }
83
+
84
+ public function parseResponse(Google_Http_Request $response)
85
+ {
86
+ $contentType = $response->getResponseHeader('content-type');
87
+ $contentType = explode(';', $contentType);
88
+ $boundary = false;
89
+ foreach ($contentType as $part) {
90
+ $part = (explode('=', $part, 2));
91
+ if (isset($part[0]) && 'boundary' == trim($part[0])) {
92
+ $boundary = $part[1];
93
+ }
94
+ }
95
+
96
+ $body = $response->getResponseBody();
97
+ if ($body) {
98
+ $body = str_replace("--$boundary--", "--$boundary", $body);
99
+ $parts = explode("--$boundary", $body);
100
+ $responses = array();
101
+
102
+ foreach ($parts as $part) {
103
+ $part = trim($part);
104
+ if (!empty($part)) {
105
+ list($metaHeaders, $part) = explode("\r\n\r\n", $part, 2);
106
+ $metaHeaders = $this->client->getIo()->getHttpResponseHeaders($metaHeaders);
107
+
108
+ $status = substr($part, 0, strpos($part, "\n"));
109
+ $status = explode(" ", $status);
110
+ $status = $status[1];
111
+
112
+ list($partHeaders, $partBody) = $this->client->getIo()->ParseHttpResponse($part, false);
113
+ $response = new Google_Http_Request("");
114
+ $response->setResponseHttpCode($status);
115
+ $response->setResponseHeaders($partHeaders);
116
+ $response->setResponseBody($partBody);
117
+
118
+ // Need content id.
119
+ $key = $metaHeaders['content-id'];
120
+
121
+ if (isset($this->expected_classes[$key]) &&
122
+ strlen($this->expected_classes[$key]) > 0) {
123
+ $class = $this->expected_classes[$key];
124
+ $response->setExpectedClass($class);
125
+ }
126
+
127
+ try {
128
+ $response = Google_Http_REST::decodeHttpResponse($response, $this->client);
129
+ $responses[$key] = $response;
130
+ } catch (Google_Service_Exception $e) {
131
+ // Store the exception as the response, so successful responses
132
+ // can be processed.
133
+ $responses[$key] = $e;
134
+ }
135
+ }
136
+ }
137
+
138
+ return $responses;
139
+ }
140
+
141
+ return null;
142
+ }
143
+ }
lib/Google2/Http/CacheParser.php ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2012 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ if (!class_exists('Google_Client')) {
19
+ require_once dirname(__FILE__) . '/../autoload.php';
20
+ }
21
+
22
+ /**
23
+ * Implement the caching directives specified in rfc2616. This
24
+ * implementation is guided by the guidance offered in rfc2616-sec13.
25
+ */
26
+ class Google_Http_CacheParser
27
+ {
28
+ public static $CACHEABLE_HTTP_METHODS = array('GET', 'HEAD');
29
+ public static $CACHEABLE_STATUS_CODES = array('200', '203', '300', '301');
30
+
31
+ /**
32
+ * Check if an HTTP request can be cached by a private local cache.
33
+ *
34
+ * @static
35
+ * @param Google_Http_Request $resp
36
+ * @return bool True if the request is cacheable.
37
+ * False if the request is uncacheable.
38
+ */
39
+ public static function isRequestCacheable(Google_Http_Request $resp)
40
+ {
41
+ $method = $resp->getRequestMethod();
42
+ if (! in_array($method, self::$CACHEABLE_HTTP_METHODS)) {
43
+ return false;
44
+ }
45
+
46
+ // Don't cache authorized requests/responses.
47
+ // [rfc2616-14.8] When a shared cache receives a request containing an
48
+ // Authorization field, it MUST NOT return the corresponding response
49
+ // as a reply to any other request...
50
+ if ($resp->getRequestHeader("authorization")) {
51
+ return false;
52
+ }
53
+
54
+ return true;
55
+ }
56
+
57
+ /**
58
+ * Check if an HTTP response can be cached by a private local cache.
59
+ *
60
+ * @static
61
+ * @param Google_Http_Request $resp
62
+ * @return bool True if the response is cacheable.
63
+ * False if the response is un-cacheable.
64
+ */
65
+ public static function isResponseCacheable(Google_Http_Request $resp)
66
+ {
67
+ // First, check if the HTTP request was cacheable before inspecting the
68
+ // HTTP response.
69
+ if (false == self::isRequestCacheable($resp)) {
70
+ return false;
71
+ }
72
+
73
+ $code = $resp->getResponseHttpCode();
74
+ if (! in_array($code, self::$CACHEABLE_STATUS_CODES)) {
75
+ return false;
76
+ }
77
+
78
+ // The resource is uncacheable if the resource is already expired and
79
+ // the resource doesn't have an ETag for revalidation.
80
+ $etag = $resp->getResponseHeader("etag");
81
+ if (self::isExpired($resp) && $etag == false) {
82
+ return false;
83
+ }
84
+
85
+ // [rfc2616-14.9.2] If [no-store is] sent in a response, a cache MUST NOT
86
+ // store any part of either this response or the request that elicited it.
87
+ $cacheControl = $resp->getParsedCacheControl();
88
+ if (isset($cacheControl['no-store'])) {
89
+ return false;
90
+ }
91
+
92
+ // Pragma: no-cache is an http request directive, but is occasionally
93
+ // used as a response header incorrectly.
94
+ $pragma = $resp->getResponseHeader('pragma');
95
+ if ($pragma == 'no-cache' || strpos($pragma, 'no-cache') !== false) {
96
+ return false;
97
+ }
98
+
99
+ // [rfc2616-14.44] Vary: * is extremely difficult to cache. "It implies that
100
+ // a cache cannot determine from the request headers of a subsequent request
101
+ // whether this response is the appropriate representation."
102
+ // Given this, we deem responses with the Vary header as uncacheable.
103
+ $vary = $resp->getResponseHeader('vary');
104
+ if ($vary) {
105
+ return false;
106
+ }
107
+
108
+ return true;
109
+ }
110
+
111
+ /**
112
+ * @static
113
+ * @param Google_Http_Request $resp
114
+ * @return bool True if the HTTP response is considered to be expired.
115
+ * False if it is considered to be fresh.
116
+ */
117
+ public static function isExpired(Google_Http_Request $resp)
118
+ {
119
+ // HTTP/1.1 clients and caches MUST treat other invalid date formats,
120
+ // especially including the value “0”, as in the past.
121
+ $parsedExpires = false;
122
+ $responseHeaders = $resp->getResponseHeaders();
123
+
124
+ if (isset($responseHeaders['expires'])) {
125
+ $rawExpires = $responseHeaders['expires'];
126
+ // Check for a malformed expires header first.
127
+ if (empty($rawExpires) || (is_numeric($rawExpires) && $rawExpires <= 0)) {
128
+ return true;
129
+ }
130
+
131
+ // See if we can parse the expires header.
132
+ $parsedExpires = strtotime($rawExpires);
133
+ if (false == $parsedExpires || $parsedExpires <= 0) {
134
+ return true;
135
+ }
136
+ }
137
+
138
+ // Calculate the freshness of an http response.
139
+ $freshnessLifetime = false;
140
+ $cacheControl = $resp->getParsedCacheControl();
141
+ if (isset($cacheControl['max-age'])) {
142
+ $freshnessLifetime = $cacheControl['max-age'];
143
+ }
144
+
145
+ $rawDate = $resp->getResponseHeader('date');
146
+ $parsedDate = strtotime($rawDate);
147
+
148
+ if (empty($rawDate) || false == $parsedDate) {
149
+ // We can't default this to now, as that means future cache reads
150
+ // will always pass with the logic below, so we will require a
151
+ // date be injected if not supplied.
152
+ throw new Google_Exception("All cacheable requests must have creation dates.");
153
+ }
154
+
155
+ if (false == $freshnessLifetime && isset($responseHeaders['expires'])) {
156
+ $freshnessLifetime = $parsedExpires - $parsedDate;
157
+ }
158
+
159
+ if (false == $freshnessLifetime) {
160
+ return true;
161
+ }
162
+
163
+ // Calculate the age of an http response.
164
+ $age = max(0, time() - $parsedDate);
165
+ if (isset($responseHeaders['age'])) {
166
+ $age = max($age, strtotime($responseHeaders['age']));
167
+ }
168
+
169
+ return $freshnessLifetime <= $age;
170
+ }
171
+
172
+ /**
173
+ * Determine if a cache entry should be revalidated with by the origin.
174
+ *
175
+ * @param Google_Http_Request $response
176
+ * @return bool True if the entry is expired, else return false.
177
+ */
178
+ public static function mustRevalidate(Google_Http_Request $response)
179
+ {
180
+ // [13.3] When a cache has a stale entry that it would like to use as a
181
+ // response to a client's request, it first has to check with the origin
182
+ // server to see if its cached entry is still usable.
183
+ return self::isExpired($response);
184
+ }
185
+ }
lib/Google2/Http/MediaFileUpload.php ADDED
@@ -0,0 +1,306 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Copyright 2012 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ if (!class_exists('Google_Client')) {
19
+ require_once dirname(__FILE__) . '/../autoload.php';
20
+ }
21
+
22
+ /**
23
+ * Manage large file uploads, which may be media but can be any type
24
+ * of sizable data.
25
+ */
26
+ class Google_Http_MediaFileUpload
27
+ {
28
+ const UPLOAD_MEDIA_TYPE = 'media';
29
+ const UPLOAD_MULTIPART_TYPE = 'multipart';
30
+ const UPLOAD_RESUMABLE_TYPE = 'resumable';
31
+
32
+ /** @var string $mimeType */
33
+ private $mimeType;
34
+
35
+ /** @var string $data */
36
+ private $data;
37
+
38
+ /** @var bool $resumable */
39
+ private $resumable;
40
+
41
+ /** @var int $chunkSize */
42
+ private $chunkSize;
43
+
44
+ /** @var int $size */
45
+ private $size;
46
+
47
+ /** @var string $resumeUri */
48
+ // patch
49
+ public $resumeUri;
50
+ // private $resumeUri;
51
+
52
+ /** @var int $progress */
53
+ // patch
54
+ public $progress;
55
+ // private $progress;
56
+
57
+ /** @var Google_Client */
58
+ private $client;
59
+
60
+ /** @var Google_Http_Request */
61
+ private $request;
62
+
63
+ /** @var string */
64
+ private $boundary;
65
+
66
+ /**
67
+ * Result code from last HTTP call
68
+ * @var int
69
+ */
70
+ private $httpResultCode;
71
+
72
+ /**
73
+ * @param $mimeType string
74
+ * @param $data string The bytes you want to upload.
75
+ * @param $resumable bool
76
+ * @param bool $chunkSize File will be uploaded in chunks of this many bytes.
77
+ * only used if resumable=True
78
+ */
79
+ public function __construct(
80
+ Google_Client $client,
81
+ Google_Http_Request $request,
82
+ $mimeType,
83
+ $data,
84
+ $resumable = false,
85
+ $chunkSize = false,
86
+ $boundary = false
87
+ ) {
88
+ $this->client = $client;
89
+ $this->request = $request;
90
+ $this->mimeType = $mimeType;
91
+ $this->data = $data;
92
+ $this->size = strlen($this->data);
93
+ $this->resumable = $resumable;
94
+ if (!$chunkSize) {
95
+ $chunkSize = 256 * 1024;
96
+ }
97
+ $this->chunkSize = $chunkSize;
98
+ $this->progress = 0;
99
+ $this->boundary = $boundary;
100
+
101
+ // Process Media Request
102
+ $this->process();
103
+ }
104
+
105
+ /**
106
+ * Set the size of the file that is being uploaded.
107
+ * @param $size - int file size in bytes
108
+ */
109
+ public function setFileSize($size)
110
+ {
111
+ $this->size = $size;
112
+ }
113
+
114
+ /**
115
+ * Return the progress on the upload
116
+ * @return int progress in bytes uploaded.
117
+ */
118
+ public function getProgress()
119
+ {
120
+ return $this->progress;
121
+ }
122
+
123
+ /**
124
+ * Return the HTTP result code from the last call made.
125
+ * @return int code
126
+ */
127
+ public function getHttpResultCode()
128
+ {
129
+ return $this->httpResultCode;
130
+ }
131
+
132
+ /**
133
+ * Send the next part of the file to upload.
134
+ * @param [$chunk] the next set of bytes to send. If false will used $data passed
135
+ * at construct time.
136
+ */
137
+ public function nextChunk($chunk = false)
138
+ {
139
+ if (false == $this->resumeUri) {
140
+ $this->resumeUri = $this->getResumeUri();
141
+ }
142
+
143
+ if (false == $chunk) {
144
+ $chunk = substr($this->data, $this->progress, $this->chunkSize);
145
+ }
146
+
147
+ $lastBytePos = $this->progress + strlen($chunk) - 1;
148
+ $headers = array(
149
+ 'content-range' => "bytes $this->progress-$lastBytePos/$this->size",
150
+ 'content-type' => $this->request->getRequestHeader('content-type'),
151
+ 'content-length' => $this->chunkSize,
152
+ 'expect' => '',
153
+ );
154
+
155
+ $httpRequest = new Google_Http_Request(
156
+ $this->resumeUri,
157
+ 'PUT',
158
+ $headers,
159
+ $chunk
160
+ );
161
+
162
+ if ($this->client->getClassConfig("Google_Http_Request", "enable_gzip_for_uploads")) {
163
+ $httpRequest->enableGzip();
164
+ } else {
165
+ $httpRequest->disableGzip();
166
+ }
167
+
168
+ $response = $this->client->getIo()->makeRequest($httpRequest);
169
+ $response->setExpectedClass($this->request->getExpectedClass());
170
+ $code = $response->getResponseHttpCode();
171
+ $this->httpResultCode = $code;
172
+
173
+ if (308 == $code) {
174
+ // Track the amount uploaded.
175
+ $range = explode('-', $response->getResponseHeader('range'));
176
+ $this->progress = $range[1] + 1;
177
+
178
+ // Allow for changing upload URLs.
179
+ $location = $response->getResponseHeader('location');
180
+ if ($location) {
181
+ $this->resumeUri = $location;
182
+ }
183
+
184
+ // No problems, but upload not complete.
185
+ return false;
186
+ } else {
187
+ return Google_Http_REST::decodeHttpResponse($response, $this->client);
188
+ }
189
+ }
190
+
191
+ /**
192
+ * @param $meta
193
+ * @param $params
194
+ * @return array|bool
195
+ * @visible for testing
196
+ */
197
+ private function process()
198
+ {
199
+ $postBody = false;
200
+ $contentType = false;
201
+
202
+ $meta = $this->request->getPostBody();
203
+ $meta = is_string($meta) ? json_decode($meta, true) : $meta;
204
+
205
+ $uploadType = $this->getUploadType($meta);
206
+ $this->request->setQueryParam('uploadType', $uploadType);
207
+ $this->transformToUploadUrl();
208
+ $mimeType = $this->mimeType ?
209
+ $this->mimeType :
210
+ $this->request->getRequestHeader('content-type');
211
+
212
+ if (self::UPLOAD_RESUMABLE_TYPE == $uploadType) {
213
+ $contentType = $mimeType;
214
+ $postBody = is_string($meta) ? $meta : json_encode($meta);
215
+ } else if (self::UPLOAD_MEDIA_TYPE == $uploadType) {
216
+ $contentType = $mimeType;
217
+ $postBody = $this->data;
218
+ } else if (self::UPLOAD_MULTIPART_TYPE == $uploadType) {
219
+ // This is a multipart/related upload.
220
+ $boundary = $this->boundary ? $this->boundary : mt_rand();
221
+ $boundary = str_replace('"', '', $boundary);
222
+ $contentType = 'multipart/related; boundary=' . $boundary;
223
+ $related = "--$boundary\r\n";
224
+ $related .= "Content-Type: application/json; charset=UTF-8\r\n";
225
+ $related .= "\r\n" . json_encode($meta) . "\r\n";
226
+ $related .= "--$boundary\r\n";
227
+ $related .= "Content-Type: $mimeType\r\n";
228
+ $related .= "Content-Transfer-Encoding: base64\r\n";
229
+ $related .= "\r\n" . base64_encode($this->data) . "\r\n";
230
+ $related .= "--$boundary--";
231
+ $postBody = $related;
232
+ }
233
+
234
+ $this->request->setPostBody($postBody);
235
+
236
+ if (isset($contentType) && $contentType) {
237
+ $contentTypeHeader['content-type'] = $contentType;
238
+ $this->request->setRequestHeaders($contentTypeHeader);
239
+ }
240
+ }
241
+
242
+ private function transformToUploadUrl()
243
+ {
244
+ $base = $this->request->getBaseComponent();
245
+ $this->request->setBaseComponent($base . '/upload');
246
+ }
247
+
248
+ /**
249
+ * Valid upload types:
250
+ * - resumable (UPLOAD_RESUMABLE_TYPE)
251
+ * - media (UPLOAD_MEDIA_TYPE)
252
+ * - multipart (UPLOAD_MULTIPART_TYPE)
253
+ * @param $meta
254
+ * @return string
255
+ * @visible for testing
256
+ */
257
+ public function getUploadType($meta)
258
+ {
259
+ if ($this->resumable) {
260
+ return self::UPLOAD_RESUMABLE_TYPE;
261
+ }
262
+
263
+ if (false == $meta && $this->data) {
264
+ return self::UPLOAD_MEDIA_TYPE;
265
+ }
266
+
267
+ return self::UPLOAD_MULTIPART_TYPE;
268
+ }
269
+
270
+ private function getResumeUri()
271
+ {
272
+ $result = null;
273
+ $body = $this->request->getPostBody();
274
+ if ($body) {
275
+ $headers = array(
276
+ 'content-type' => 'application/json; charset=UTF-8',
277
+ 'content-length' => Google_Utils::getStrLen($body),
278
+ 'x-upload-content-type' => $this->mimeType,
279
+ 'x-upload-content-length' => $this->size,
280
+ 'expect' => '',
281
+ );
282
+ $this->request->setRequestHeaders($headers);
283
+ }
284
+
285
+ $response = $this->client->getIo()->makeRequest($this->request);
286
+ $location = $response->getResponseHeader('location');
287
+ $code = $response->getResponseHttpCode();
288
+
289
+ if (200 == $code && true == $location) {
290
+ return $location;
291
+ }
292
+ $message = $code;
293
+ $body = @json_decode($response->getResponseBody());
294
+ if (!empty( $body->error->errors ) ) {
295
+ $message .= ': ';
296
+ foreach ($body->error->errors as $error) {
297
+ $message .= "{$error->domain}, {$error->message};";
298
+ }
299
+ $message = rtrim($message, ';');
300
+ }
301
+
302
+ $error = "Failed to start the resumable upload (HTTP {$message})";
303
+ $this->client->getLogger()->error($error);
304
+ throw new Google_Exception($error);
305
+ }
306
+ }
lib/Google2/Http/REST.php ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2010 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ if (!class_exists('Google_Client')) {
19
+ require_once dirname(__FILE__) . '/../autoload.php';
20
+ }
21
+
22
+ /**
23
+ * This class implements the RESTful transport of apiServiceRequest()'s
24
+ */
25
+ class Google_Http_REST
26
+ {
27
+ /**
28
+ * Executes a Google_Http_Request and (if applicable) automatically retries
29
+ * when errors occur.
30
+ *
31
+ * @param Google_Client $client
32
+ * @param Google_Http_Request $req
33
+ * @return array decoded result
34
+ * @throws Google_Service_Exception on server side error (ie: not authenticated,
35
+ * invalid or malformed post body, invalid url)
36
+ */
37
+ public static function execute(Google_Client $client, Google_Http_Request $req)
38
+ {
39
+ $runner = new Google_Task_Runner(
40
+ $client,
41
+ sprintf('%s %s', $req->getRequestMethod(), $req->getUrl()),
42
+ array(get_class(), 'doExecute'),
43
+ array($client, $req)
44
+ );
45
+
46
+ return $runner->run();
47
+ }
48
+
49
+ /**
50
+ * Executes a Google_Http_Request
51
+ *
52
+ * @param Google_Client $client
53
+ * @param Google_Http_Request $req
54
+ * @return array decoded result
55
+ * @throws Google_Service_Exception on server side error (ie: not authenticated,
56
+ * invalid or malformed post body, invalid url)
57
+ */
58
+ public static function doExecute(Google_Client $client, Google_Http_Request $req)
59
+ {
60
+ $httpRequest = $client->getIo()->makeRequest($req);
61
+ $httpRequest->setExpectedClass($req->getExpectedClass());
62
+ return self::decodeHttpResponse($httpRequest, $client);
63
+ }
64
+
65
+ /**
66
+ * Decode an HTTP Response.
67
+ * @static
68
+ * @throws Google_Service_Exception
69
+ * @param Google_Http_Request $response The http response to be decoded.
70
+ * @param Google_Client $client
71
+ * @return mixed|null
72
+ */
73
+ public static function decodeHttpResponse($response, Google_Client $client = null)
74
+ {
75
+ $code = $response->getResponseHttpCode();
76
+ $body = $response->getResponseBody();
77
+ $decoded = null;
78
+
79
+ if ((intVal($code)) >= 300) {
80
+ $decoded = json_decode($body, true);
81
+ $err = 'Error calling ' . $response->getRequestMethod() . ' ' . $response->getUrl();
82
+ if (isset($decoded['error']) &&
83
+ isset($decoded['error']['message']) &&
84
+ isset($decoded['error']['code'])) {
85
+ // if we're getting a json encoded error definition, use that instead of the raw response
86
+ // body for improved readability
87
+ $err .= ": ({$decoded['error']['code']}) {$decoded['error']['message']}";
88
+ } else {
89
+ $err .= ": ($code) $body";
90
+ }
91
+
92
+ $errors = null;
93
+ // Specific check for APIs which don't return error details, such as Blogger.
94
+ if (isset($decoded['error']) && isset($decoded['error']['errors'])) {
95
+ $errors = $decoded['error']['errors'];
96
+ }
97
+
98
+ $map = null;
99
+ if ($client) {
100
+ $client->getLogger()->error(
101
+ $err,
102
+ array('code' => $code, 'errors' => $errors)
103
+ );
104
+
105
+ $map = $client->getClassConfig(
106
+ 'Google_Service_Exception',
107
+ 'retry_map'
108
+ );
109
+ }
110
+ throw new Google_Service_Exception($err, $code, null, $errors, $map);
111
+ }
112
+
113
+ // Only attempt to decode the response, if the response code wasn't (204) 'no content'
114
+ if ($code != '204') {
115
+ if ($response->getExpectedRaw()) {
116
+ return $body;
117
+ }
118
+
119
+ $decoded = json_decode($body, true);
120
+ if ($decoded === null || $decoded === "") {
121
+
122
+ $error = "Invalid json in service response ($code): $body";
123
+ if ($client) {
124
+ $client->getLogger()->error($error);
125
+ }
126
+ throw new Google_Service_Exception($error);
127
+ }
128
+
129
+ if ($response->getExpectedClass()) {
130
+ $class = $response->getExpectedClass();
131
+ $decoded = new $class($decoded);
132
+ }
133
+ }
134
+ return $decoded;
135
+ }
136
+
137
+ /**
138
+ * Parse/expand request parameters and create a fully qualified
139
+ * request uri.
140
+ * @static
141
+ * @param string $servicePath
142
+ * @param string $restPath
143
+ * @param array $params
144
+ * @return string $requestUrl
145
+ */
146
+ public static function createRequestUri($servicePath, $restPath, $params)
147
+ {
148
+ $requestUrl = $servicePath . $restPath;
149
+ $uriTemplateVars = array();
150
+ $queryVars = array();
151
+ foreach ($params as $paramName => $paramSpec) {
152
+ if ($paramSpec['type'] == 'boolean') {
153
+ $paramSpec['value'] = ($paramSpec['value']) ? 'true' : 'false';
154
+ }
155
+ if ($paramSpec['location'] == 'path') {
156
+ $uriTemplateVars[$paramName] = $paramSpec['value'];
157
+ } else if ($paramSpec['location'] == 'query') {
158
+ if (isset($paramSpec['repeated']) && is_array($paramSpec['value'])) {
159
+ foreach ($paramSpec['value'] as $value) {
160
+ $queryVars[] = $paramName . '=' . rawurlencode(rawurldecode($value));
161
+ }
162
+ } else {
163
+ $queryVars[] = $paramName . '=' . rawurlencode(rawurldecode($paramSpec['value']));
164
+ }
165
+ }
166
+ }
167
+
168
+ if (count($uriTemplateVars)) {
169
+ $uriTemplateParser = new Google_Utils_URITemplate();
170
+ $requestUrl = $uriTemplateParser->parse($requestUrl, $uriTemplateVars);
171
+ }
172
+
173
+ if (count($queryVars)) {
174
+ $requestUrl .= '?' . implode($queryVars, '&');
175
+ }
176
+
177
+ return $requestUrl;
178
+ }
179
+ }
lib/Google2/Http/Request.php ADDED
@@ -0,0 +1,504 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2010 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ if (!class_exists('Google_Client')) {
19
+ require_once dirname(__FILE__) . '/../autoload.php';
20
+ }
21
+
22
+ /**
23
+ * HTTP Request to be executed by IO classes. Upon execution, the
24
+ * responseHttpCode, responseHeaders and responseBody will be filled in.
25
+ *
26
+ * @author Chris Chabot <chabotc@google.com>
27
+ * @author Chirag Shah <chirags@google.com>
28
+ *
29
+ */
30
+ class Google_Http_Request
31
+ {
32
+ const GZIP_UA = " (gzip)";
33
+
34
+ private $batchHeaders = array(
35
+ 'Content-Type' => 'application/http',
36
+ 'Content-Transfer-Encoding' => 'binary',
37
+ 'MIME-Version' => '1.0',
38
+ );
39
+
40
+ protected $queryParams;
41
+ protected $requestMethod;
42
+ protected $requestHeaders;
43
+ protected $baseComponent = null;
44
+ protected $path;
45
+ protected $postBody;
46
+ protected $userAgent;
47
+ protected $canGzip = null;
48
+
49
+ protected $responseHttpCode;
50
+ protected $responseHeaders;
51
+ protected $responseBody;
52
+
53
+ protected $expectedClass;
54
+ protected $expectedRaw = false;
55
+
56
+ public $accessKey;
57
+
58
+ public function __construct(
59
+ $url,
60
+ $method = 'GET',
61
+ $headers = array(),
62
+ $postBody = null
63
+ ) {
64
+ $this->setUrl($url);
65
+ $this->setRequestMethod($method);
66
+ $this->setRequestHeaders($headers);
67
+ $this->setPostBody($postBody);
68
+ }
69
+
70
+ /**
71
+ * Misc function that returns the base url component of the $url
72
+ * used by the OAuth signing class to calculate the base string
73
+ * @return string The base url component of the $url.
74
+ */
75
+ public function getBaseComponent()
76
+ {
77
+ return $this->baseComponent;
78
+ }
79
+
80
+ /**
81
+ * Set the base URL that path and query parameters will be added to.
82
+ * @param $baseComponent string
83
+ */
84
+ public function setBaseComponent($baseComponent)
85
+ {
86
+ $this->baseComponent = $baseComponent;
87
+ }
88
+
89
+ /**
90
+ * Enable support for gzipped responses with this request.
91
+ */
92
+ public function enableGzip()
93
+ {
94
+ $this->setRequestHeaders(array("Accept-Encoding" => "gzip"));
95
+ $this->canGzip = true;
96
+ $this->setUserAgent($this->userAgent);
97
+ }
98
+
99
+ /**
100
+ * Disable support for gzip responses with this request.
101
+ */
102
+ public function disableGzip()
103
+ {
104
+ if (
105
+ isset($this->requestHeaders['accept-encoding']) &&
106
+ $this->requestHeaders['accept-encoding'] == "gzip"
107
+ ) {
108
+ unset($this->requestHeaders['accept-encoding']);
109
+ }
110
+ $this->canGzip = false;
111
+ $this->userAgent = str_replace(self::GZIP_UA, "", $this->userAgent);
112
+ }
113
+
114
+ /**
115
+ * Can this request accept a gzip response?
116
+ * @return bool
117
+ */
118
+ public function canGzip()
119
+ {
120
+ return $this->canGzip;
121
+ }
122
+
123
+ /**
124
+ * Misc function that returns an array of the query parameters of the current
125
+ * url used by the OAuth signing class to calculate the signature
126
+ * @return array Query parameters in the query string.
127
+ */
128
+ public function getQueryParams()
129
+ {
130
+ return $this->queryParams;
131
+ }
132
+
133
+ /**
134
+ * Set a new query parameter.
135
+ * @param $key - string to set, does not need to be URL encoded
136
+ * @param $value - string to set, does not need to be URL encoded
137
+ */
138
+ public function setQueryParam($key, $value)
139
+ {
140
+ $this->queryParams[$key] = $value;
141
+ }
142
+
143
+ /**
144
+ * @return string HTTP Response Code.
145
+ */
146
+ public function getResponseHttpCode()
147
+ {
148
+ return (int) $this->responseHttpCode;
149
+ }
150
+
151
+ /**
152
+ * @param int $responseHttpCode HTTP Response Code.
153
+ */
154
+ public function setResponseHttpCode($responseHttpCode)
155
+ {
156
+ $this->responseHttpCode = $responseHttpCode;
157
+ }
158
+
159
+ /**
160
+ * @return $responseHeaders (array) HTTP Response Headers.
161
+ */
162
+ public function getResponseHeaders()
163
+ {
164
+ return $this->responseHeaders;
165
+ }
166
+
167
+ /**
168
+ * @return string HTTP Response Body
169
+ */
170
+ public function getResponseBody()
171
+ {
172
+ return $this->responseBody;
173
+ }
174
+
175
+ /**
176
+ * Set the class the response to this request should expect.
177
+ *
178
+ * @param $class string the class name
179
+ */
180
+ public function setExpectedClass($class)
181
+ {
182
+ $this->expectedClass = $class;
183
+ }
184
+
185
+ /**
186
+ * Retrieve the expected class the response should expect.
187
+ * @return string class name
188
+ */
189
+ public function getExpectedClass()
190
+ {
191
+ return $this->expectedClass;
192
+ }
193
+
194
+ /**
195
+ * Enable expected raw response
196
+ */
197
+ public function enableExpectedRaw()
198
+ {
199
+ $this->expectedRaw = true;
200
+ }
201
+
202
+ /**
203
+ * Disable expected raw response
204
+ */
205
+ public function disableExpectedRaw()
206
+ {
207
+ $this->expectedRaw = false;
208
+ }
209
+
210
+ /**
211
+ * Expected raw response or not.
212
+ * @return boolean expected raw response
213
+ */
214
+ public function getExpectedRaw()
215
+ {
216
+ return $this->expectedRaw;
217
+ }
218
+
219
+ /**
220
+ * @param array $headers The HTTP response headers
221
+ * to be normalized.
222
+ */
223
+ public function setResponseHeaders($headers)
224
+ {
225
+ $headers = Google_Utils::normalize($headers);
226
+ if ($this->responseHeaders) {
227
+ $headers = array_merge($this->responseHeaders, $headers);
228
+ }
229
+
230
+ $this->responseHeaders = $headers;
231
+ }
232
+
233
+ /**
234
+ * @param string $key
235
+ * @return array|boolean Returns the requested HTTP header or
236
+ * false if unavailable.
237
+ */
238
+ public function getResponseHeader($key)
239
+ {
240
+ return isset($this->responseHeaders[$key])
241
+ ? $this->responseHeaders[$key]
242
+ : false;
243
+ }
244
+
245
+ /**
246
+ * @param string $responseBody The HTTP response body.
247
+ */
248
+ public function setResponseBody($responseBody)
249
+ {
250
+ $this->responseBody = $responseBody;
251
+ }
252
+
253
+ /**
254
+ * @return string $url The request URL.
255
+ */
256
+ public function getUrl()
257
+ {
258
+ return $this->baseComponent . $this->path .
259
+ (count($this->queryParams) ?
260
+ "?" . $this->buildQuery($this->queryParams) :
261
+ '');
262
+ }
263
+
264
+ /**
265
+ * @return string $method HTTP Request Method.
266
+ */
267
+ public function getRequestMethod()
268
+ {
269
+ return $this->requestMethod;
270
+ }
271
+
272
+ /**
273
+ * @return array $headers HTTP Request Headers.
274
+ */
275
+ public function getRequestHeaders()
276
+ {
277
+ return $this->requestHeaders;
278
+ }
279
+
280
+ /**
281
+ * @param string $key
282
+ * @return array|boolean Returns the requested HTTP header or
283
+ * false if unavailable.
284
+ */
285
+ public function getRequestHeader($key)
286
+ {
287
+ return isset($this->requestHeaders[$key])
288
+ ? $this->requestHeaders[$key]
289
+ : false;
290
+ }
291
+
292
+ /**
293
+ * @return string $postBody HTTP Request Body.
294
+ */
295
+ public function getPostBody()
296
+ {
297
+ return $this->postBody;
298
+ }
299
+
300
+ /**
301
+ * @param string $url the url to set
302
+ */
303
+ public function setUrl($url)
304
+ {
305
+ if (substr($url, 0, 4) != 'http') {
306
+ // Force the path become relative.
307
+ if (substr($url, 0, 1) !== '/') {
308
+ $url = '/' . $url;
309
+ }
310
+ }
311
+ $parts = parse_url($url);
312
+ if (isset($parts['host'])) {
313
+ $this->baseComponent = sprintf(
314
+ "%s%s%s",
315
+ isset($parts['scheme']) ? $parts['scheme'] . "://" : '',
316
+ isset($parts['host']) ? $parts['host'] : '',
317
+ isset($parts['port']) ? ":" . $parts['port'] : ''
318
+ );
319
+ }
320
+ $this->path = isset($parts['path']) ? $parts['path'] : '';
321
+ $this->queryParams = array();
322
+ if (isset($parts['query'])) {
323
+ $this->queryParams = $this->parseQuery($parts['query']);
324
+ }
325
+ }
326
+
327
+ /**
328
+ * @param string $method Set he HTTP Method and normalize
329
+ * it to upper-case, as required by HTTP.
330
+ *
331
+ */
332
+ public function setRequestMethod($method)
333
+ {
334
+ $this->requestMethod = strtoupper($method);
335
+ }
336
+
337
+ /**
338
+ * @param array $headers The HTTP request headers
339
+ * to be set and normalized.
340
+ */
341
+ public function setRequestHeaders($headers)
342
+ {
343
+ $headers = Google_Utils::normalize($headers);
344
+ if ($this->requestHeaders) {
345
+ $headers = array_merge($this->requestHeaders, $headers);
346
+ }
347
+ $this->requestHeaders = $headers;
348
+ }
349
+
350
+ /**
351
+ * @param string $postBody the postBody to set
352
+ */
353
+ public function setPostBody($postBody)
354
+ {
355
+ $this->postBody = $postBody;
356
+ }
357
+
358
+ /**
359
+ * Set the User-Agent Header.
360
+ * @param string $userAgent The User-Agent.
361
+ */
362
+ public function setUserAgent($userAgent)
363
+ {
364
+ $this->userAgent = $userAgent;
365
+ if ($this->canGzip) {
366
+ $this->userAgent = $userAgent . self::GZIP_UA;
367
+ }
368
+ }
369
+
370
+ /**
371
+ * @return string The User-Agent.
372
+ */
373
+ public function getUserAgent()
374
+ {
375
+ return $this->userAgent;
376
+ }
377
+
378
+ /**
379
+ * Returns a cache key depending on if this was an OAuth signed request
380
+ * in which case it will use the non-signed url and access key to make this
381
+ * cache key unique per authenticated user, else use the plain request url
382
+ * @return string The md5 hash of the request cache key.
383
+ */
384
+ public function getCacheKey()
385
+ {
386
+ $key = $this->getUrl();
387
+
388
+ if (isset($this->accessKey)) {
389
+ $key .= $this->accessKey;
390
+ }
391
+
392
+ if (isset($this->requestHeaders['authorization'])) {
393
+ $key .= $this->requestHeaders['authorization'];
394
+ }
395
+
396
+ return md5($key);
397
+ }
398
+
399
+ public function getParsedCacheControl()
400
+ {
401
+ $parsed = array();
402
+ $rawCacheControl = $this->getResponseHeader('cache-control');
403
+ if ($rawCacheControl) {
404
+ $rawCacheControl = str_replace(', ', '&', $rawCacheControl);
405
+ parse_str($rawCacheControl, $parsed);
406
+ }
407
+
408
+ return $parsed;
409
+ }
410
+
411
+ /**
412
+ * @param string $id
413
+ * @return string A string representation of the HTTP Request.
414
+ */
415
+ public function toBatchString($id)
416
+ {
417
+ $str = '';
418
+ $path = parse_url($this->getUrl(), PHP_URL_PATH) . "?" .
419
+ http_build_query($this->queryParams);
420
+ $str .= $this->getRequestMethod() . ' ' . $path . " HTTP/1.1\n";
421
+
422
+ foreach ($this->getRequestHeaders() as $key => $val) {
423
+ $str .= $key . ': ' . $val . "\n";
424
+ }
425
+
426
+ if ($this->getPostBody()) {
427
+ $str .= "\n";
428
+ $str .= $this->getPostBody();
429
+ }
430
+
431
+ $headers = '';
432
+ foreach ($this->batchHeaders as $key => $val) {
433
+ $headers .= $key . ': ' . $val . "\n";
434
+ }
435
+
436
+ $headers .= "Content-ID: $id\n";
437
+ $str = $headers . "\n" . $str;
438
+
439
+ return $str;
440
+ }
441
+
442
+ /**
443
+ * Our own version of parse_str that allows for multiple variables
444
+ * with the same name.
445
+ * @param $string - the query string to parse
446
+ */
447
+ private function parseQuery($string)
448
+ {
449
+ $return = array();
450
+ $parts = explode("&", $string);
451
+ foreach ($parts as $part) {
452
+ list($key, $value) = explode('=', $part, 2);
453
+ $value = urldecode($value);
454
+ if (isset($return[$key])) {
455
+ if (!is_array($return[$key])) {
456
+ $return[$key] = array($return[$key]);
457
+ }
458
+ $return[$key][] = $value;
459
+ } else {
460
+ $return[$key] = $value;
461
+ }
462
+ }
463
+ return $return;
464
+ }
465
+
466
+ /**
467
+ * A version of build query that allows for multiple
468
+ * duplicate keys.
469
+ * @param $parts array of key value pairs
470
+ */
471
+ private function buildQuery($parts)
472
+ {
473
+ $return = array();
474
+ foreach ($parts as $key => $value) {
475
+ if (is_array($value)) {
476
+ foreach ($value as $v) {
477
+ $return[] = urlencode($key) . "=" . urlencode($v);
478
+ }
479
+ } else {
480
+ $return[] = urlencode($key) . "=" . urlencode($value);
481
+ }
482
+ }
483
+ return implode('&', $return);
484
+ }
485
+
486
+ /**
487
+ * If we're POSTing and have no body to send, we can send the query
488
+ * parameters in there, which avoids length issues with longer query
489
+ * params.
490
+ */
491
+ public function maybeMoveParametersToBody()
492
+ {
493
+ if ($this->getRequestMethod() == "POST" && empty($this->postBody)) {
494
+ $this->setRequestHeaders(
495
+ array(
496
+ "content-type" =>
497
+ "application/x-www-form-urlencoded; charset=UTF-8"
498
+ )
499
+ );
500
+ $this->setPostBody($this->buildQuery($this->queryParams));
501
+ $this->queryParams = array();
502
+ }
503
+ }
504
+ }
lib/Google2/IO/Abstract.php ADDED
@@ -0,0 +1,339 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2013 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ /**
19
+ * Abstract IO base class
20
+ */
21
+
22
+ if (!class_exists('Google_Client')) {
23
+ require_once dirname(__FILE__) . '/../autoload.php';
24
+ }
25
+
26
+ abstract class Google_IO_Abstract
27
+ {
28
+ const UNKNOWN_CODE = 0;
29
+ const FORM_URLENCODED = 'application/x-www-form-urlencoded';
30
+ private static $CONNECTION_ESTABLISHED_HEADERS = array(
31
+ "HTTP/1.0 200 Connection established\r\n\r\n",
32
+ "HTTP/1.1 200 Connection established\r\n\r\n",
33
+ );
34
+ private static $ENTITY_HTTP_METHODS = array("POST" => null, "PUT" => null);
35
+ private static $HOP_BY_HOP = array(
36
+ 'connection' => true,
37
+ 'keep-alive' => true,
38
+ 'proxy-authenticate' => true,
39
+ 'proxy-authorization' => true,
40
+ 'te' => true,
41
+ 'trailers' => true,
42
+ 'transfer-encoding' => true,
43
+ 'upgrade' => true
44
+ );
45
+
46
+
47
+ /** @var Google_Client */
48
+ protected $client;
49
+
50
+ public function __construct(Google_Client $client)
51
+ {
52
+ $this->client = $client;
53
+ $timeout = $client->getClassConfig('Google_IO_Abstract', 'request_timeout_seconds');
54
+ if ($timeout > 0) {
55
+ $this->setTimeout($timeout);
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Executes a Google_Http_Request
61
+ * @param Google_Http_Request $request the http request to be executed
62
+ * @return array containing response headers, body, and http code
63
+ * @throws Google_IO_Exception on curl or IO error
64
+ */
65
+ abstract public function executeRequest(Google_Http_Request $request);
66
+
67
+ /**
68
+ * Set options that update the transport implementation's behavior.
69
+ * @param $options
70
+ */
71
+ abstract public function setOptions($options);
72
+
73
+ /**
74
+ * Set the maximum request time in seconds.
75
+ * @param $timeout in seconds
76
+ */
77
+ abstract public function setTimeout($timeout);
78
+
79
+ /**
80
+ * Get the maximum request time in seconds.
81
+ * @return timeout in seconds
82
+ */
83
+ abstract public function getTimeout();
84
+
85
+ /**
86
+ * Test for the presence of a cURL header processing bug
87
+ *
88
+ * The cURL bug was present in versions prior to 7.30.0 and caused the header
89
+ * length to be miscalculated when a "Connection established" header added by
90
+ * some proxies was present.
91
+ *
92
+ * @return boolean
93
+ */
94
+ abstract protected function needsQuirk();
95
+
96
+ /**
97
+ * @visible for testing.
98
+ * Cache the response to an HTTP request if it is cacheable.
99
+ * @param Google_Http_Request $request
100
+ * @return bool Returns true if the insertion was successful.
101
+ * Otherwise, return false.
102
+ */
103
+ public function setCachedRequest(Google_Http_Request $request)
104
+ {
105
+ // Determine if the request is cacheable.
106
+ if (Google_Http_CacheParser::isResponseCacheable($request)) {
107
+ $this->client->getCache()->set($request->getCacheKey(), $request);
108
+ return true;
109
+ }
110
+
111
+ return false;
112
+ }
113
+
114
+ /**
115
+ * Execute an HTTP Request
116
+ *
117
+ * @param Google_Http_Request $request the http request to be executed
118
+ * @return Google_Http_Request http request with the response http code,
119
+ * response headers and response body filled in
120
+ * @throws Google_IO_Exception on curl or IO error
121
+ */
122
+ public function makeRequest(Google_Http_Request $request)
123
+ {
124
+ // First, check to see if we have a valid cached version.
125
+ $cached = $this->getCachedRequest($request);
126
+ if ($cached !== false && $cached instanceof Google_Http_Request) {
127
+ if (!$this->checkMustRevalidateCachedRequest($cached, $request)) {
128
+ return $cached;
129
+ }
130
+ }
131
+
132
+ if (array_key_exists($request->getRequestMethod(), self::$ENTITY_HTTP_METHODS)) {
133
+ $request = $this->processEntityRequest($request);
134
+ }
135
+
136
+ list($responseData, $responseHeaders, $respHttpCode) = $this->executeRequest($request);
137
+
138
+ if ($respHttpCode == 304 && $cached) {
139
+ // If the server responded NOT_MODIFIED, return the cached request.
140
+ $this->updateCachedRequest($cached, $responseHeaders);
141
+ return $cached;
142
+ }
143
+
144
+ if (!isset($responseHeaders['Date']) && !isset($responseHeaders['date'])) {
145
+ $responseHeaders['date'] = date("r");
146
+ }
147
+
148
+ $request->setResponseHttpCode($respHttpCode);
149
+ $request->setResponseHeaders($responseHeaders);
150
+ $request->setResponseBody($responseData);
151
+ // Store the request in cache (the function checks to see if the request
152
+ // can actually be cached)
153
+ $this->setCachedRequest($request);
154
+ return $request;
155
+ }
156
+
157
+ /**
158
+ * @visible for testing.
159
+ * @param Google_Http_Request $request
160
+ * @return Google_Http_Request|bool Returns the cached object or
161
+ * false if the operation was unsuccessful.
162
+ */
163
+ public function getCachedRequest(Google_Http_Request $request)
164
+ {
165
+ if (false === Google_Http_CacheParser::isRequestCacheable($request)) {
166
+ return false;
167
+ }
168
+
169
+ return $this->client->getCache()->get($request->getCacheKey());
170
+ }
171
+
172
+ /**
173
+ * @visible for testing
174
+ * Process an http request that contains an enclosed entity.
175
+ * @param Google_Http_Request $request
176
+ * @return Google_Http_Request Processed request with the enclosed entity.
177
+ */
178
+ public function processEntityRequest(Google_Http_Request $request)
179
+ {
180
+ $postBody = $request->getPostBody();
181
+ $contentType = $request->getRequestHeader("content-type");
182
+
183
+ // Set the default content-type as application/x-www-form-urlencoded.
184
+ if (false == $contentType) {
185
+ $contentType = self::FORM_URLENCODED;
186
+ $request->setRequestHeaders(array('content-type' => $contentType));
187
+ }
188
+
189
+ // Force the payload to match the content-type asserted in the header.
190
+ if ($contentType == self::FORM_URLENCODED && is_array($postBody)) {
191
+ $postBody = http_build_query($postBody, '', '&');
192
+ $request->setPostBody($postBody);
193
+ }
194
+
195
+ // Make sure the content-length header is set.
196
+ if (!$postBody || is_string($postBody)) {
197
+ $postsLength = strlen($postBody);
198
+ $request->setRequestHeaders(array('content-length' => $postsLength));
199
+ }
200
+
201
+ return $request;
202
+ }
203
+
204
+ /**
205
+ * Check if an already cached request must be revalidated, and if so update
206
+ * the request with the correct ETag headers.
207
+ * @param Google_Http_Request $cached A previously cached response.
208
+ * @param Google_Http_Request $request The outbound request.
209
+ * return bool If the cached object needs to be revalidated, false if it is
210
+ * still current and can be re-used.
211
+ */
212
+ protected function checkMustRevalidateCachedRequest($cached, $request)
213
+ {
214
+ if (Google_Http_CacheParser::mustRevalidate($cached)) {
215
+ $addHeaders = array();
216
+ if ($cached->getResponseHeader('etag')) {
217
+ // [13.3.4] If an entity tag has been provided by the origin server,
218
+ // we must use that entity tag in any cache-conditional request.
219
+ $addHeaders['If-None-Match'] = $cached->getResponseHeader('etag');
220
+ } elseif ($cached->getResponseHeader('date')) {
221
+ $addHeaders['If-Modified-Since'] = $cached->getResponseHeader('date');
222
+ }
223
+
224
+ $request->setRequestHeaders($addHeaders);
225
+ return true;
226
+ } else {
227
+ return false;
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Update a cached request, using the headers from the last response.
233
+ * @param Google_Http_Request $cached A previously cached response.
234
+ * @param mixed Associative array of response headers from the last request.
235
+ */
236
+ protected function updateCachedRequest($cached, $responseHeaders)
237
+ {
238
+ $hopByHop = self::$HOP_BY_HOP;
239
+ if (!empty($responseHeaders['connection'])) {
240
+ $connectionHeaders = array_map(
241
+ 'strtolower',
242
+ array_filter(
243
+ array_map('trim', explode(',', $responseHeaders['connection']))
244
+ )
245
+ );
246
+ $hopByHop += array_fill_keys($connectionHeaders, true);
247
+ }
248
+
249
+ $endToEnd = array_diff_key($responseHeaders, $hopByHop);
250
+ $cached->setResponseHeaders($endToEnd);
251
+ }
252
+
253
+ /**
254
+ * Used by the IO lib and also the batch processing.
255
+ *
256
+ * @param $respData
257
+ * @param $headerSize
258
+ * @return array
259
+ */
260
+ public function parseHttpResponse($respData, $headerSize)
261
+ {
262
+ // check proxy header
263
+ foreach (self::$CONNECTION_ESTABLISHED_HEADERS as $established_header) {
264
+ if (stripos($respData, $established_header) !== false) {
265
+ // existed, remove it
266
+ $respData = str_ireplace($established_header, '', $respData);
267
+ // Subtract the proxy header size unless the cURL bug prior to 7.30.0
268
+ // is present which prevented the proxy header size from being taken into
269
+ // account.
270
+ if (!$this->needsQuirk()) {
271
+ $headerSize -= strlen($established_header);
272
+ }
273
+ break;
274
+ }
275
+ }
276
+
277
+ if ($headerSize) {
278
+ $responseBody = substr($respData, $headerSize);
279
+ $responseHeaders = substr($respData, 0, $headerSize);
280
+ } else {
281
+ $responseSegments = explode("\r\n\r\n", $respData, 2);
282
+ $responseHeaders = $responseSegments[0];
283
+ $responseBody = isset($responseSegments[1]) ? $responseSegments[1] :
284
+ null;
285
+ }
286
+
287
+ $responseHeaders = $this->getHttpResponseHeaders($responseHeaders);
288
+ return array($responseHeaders, $responseBody);
289
+ }
290
+
291
+ /**
292
+ * Parse out headers from raw headers
293
+ * @param rawHeaders array or string
294
+ * @return array
295
+ */
296
+ public function getHttpResponseHeaders($rawHeaders)
297
+ {
298
+ if (is_array($rawHeaders)) {
299
+ return $this->parseArrayHeaders($rawHeaders);
300
+ } else {
301
+ return $this->parseStringHeaders($rawHeaders);
302
+ }
303
+ }
304
+
305
+ private function parseStringHeaders($rawHeaders)
306
+ {
307
+ $headers = array();
308
+ $responseHeaderLines = explode("\r\n", $rawHeaders);
309
+ foreach ($responseHeaderLines as $headerLine) {
310
+ if ($headerLine && strpos($headerLine, ':') !== false) {
311
+ list($header, $value) = explode(': ', $headerLine, 2);
312
+ $header = strtolower($header);
313
+ if (isset($headers[$header])) {
314
+ $headers[$header] .= "\n" . $value;
315
+ } else {
316
+ $headers[$header] = $value;
317
+ }
318
+ }
319
+ }
320
+ return $headers;
321
+ }
322
+
323
+ private function parseArrayHeaders($rawHeaders)
324
+ {
325
+ $header_count = count($rawHeaders);
326
+ $headers = array();
327
+
328
+ for ($i = 0; $i < $header_count; $i++) {
329
+ $header = $rawHeaders[$i];
330
+ // Times will have colons in - so we just want the first match.
331
+ $header_parts = explode(': ', $header, 2);
332
+ if (count($header_parts) == 2) {
333
+ $headers[strtolower($header_parts[0])] = $header_parts[1];
334
+ }
335
+ }
336
+
337
+ return $headers;
338
+ }
339
+ }
lib/Google2/IO/Curl.php ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2014 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ /**
19
+ * Curl based implementation of Google_IO.
20
+ *
21
+ * @author Stuart Langley <slangley@google.com>
22
+ */
23
+
24
+ if (!class_exists('Google_Client')) {
25
+ require_once dirname(__FILE__) . '/../autoload.php';
26
+ }
27
+
28
+ class Google_IO_Curl extends Google_IO_Abstract
29
+ {
30
+ // cURL hex representation of version 7.30.0
31
+ const NO_QUIRK_VERSION = 0x071E00;
32
+
33
+ private $options = array();
34
+
35
+ public function __construct(Google_Client $client)
36
+ {
37
+ if (!extension_loaded('curl')) {
38
+ $error = 'The cURL IO handler requires the cURL extension to be enabled';
39
+ $client->getLogger()->critical($error);
40
+ throw new Google_IO_Exception($error);
41
+ }
42
+
43
+ parent::__construct($client);
44
+ }
45
+
46
+ /**
47
+ * Execute an HTTP Request
48
+ *
49
+ * @param Google_Http_Request $request the http request to be executed
50
+ * @return array containing response headers, body, and http code
51
+ * @throws Google_IO_Exception on curl or IO error
52
+ */
53
+ public function executeRequest(Google_Http_Request $request)
54
+ {
55
+ $curl = curl_init();
56
+
57
+ if ($request->getPostBody()) {
58
+ curl_setopt($curl, CURLOPT_POSTFIELDS, $request->getPostBody());
59
+ }
60
+
61
+ $requestHeaders = $request->getRequestHeaders();
62
+ if ($requestHeaders && is_array($requestHeaders)) {
63
+ $curlHeaders = array();
64
+ foreach ($requestHeaders as $k => $v) {
65
+ $curlHeaders[] = "$k: $v";
66
+ }
67
+ curl_setopt($curl, CURLOPT_HTTPHEADER, $curlHeaders);
68
+ }
69
+ curl_setopt($curl, CURLOPT_URL, $request->getUrl());
70
+
71
+ curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $request->getRequestMethod());
72
+ curl_setopt($curl, CURLOPT_USERAGENT, $request->getUserAgent());
73
+
74
+ curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false);
75
+ curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
76
+ // 1 is CURL_SSLVERSION_TLSv1, which is not always defined in PHP.
77
+ // UpdraftPlus patch
78
+ // The SDK leaves this on the default setting in later releases
79
+ // curl_setopt($curl, CURLOPT_SSLVERSION, 1);
80
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
81
+ curl_setopt($curl, CURLOPT_HEADER, true);
82
+
83
+ if ($request->canGzip()) {
84
+ curl_setopt($curl, CURLOPT_ENCODING, 'gzip,deflate');
85
+ }
86
+
87
+ $options = $this->client->getClassConfig('Google_IO_Curl', 'options');
88
+ if (is_array($options)) {
89
+ $this->setOptions($options);
90
+ }
91
+
92
+ foreach ($this->options as $key => $var) {
93
+ curl_setopt($curl, $key, $var);
94
+ }
95
+
96
+ if (!isset($this->options[CURLOPT_CAINFO])) {
97
+ curl_setopt($curl, CURLOPT_CAINFO, dirname(__FILE__) . '/cacerts.pem');
98
+ }
99
+
100
+ $this->client->getLogger()->debug(
101
+ 'cURL request',
102
+ array(
103
+ 'url' => $request->getUrl(),
104
+ 'method' => $request->getRequestMethod(),
105
+ 'headers' => $requestHeaders,
106
+ 'body' => $request->getPostBody()
107
+ )
108
+ );
109
+
110
+ $response = curl_exec($curl);
111
+ if ($response === false) {
112
+ $error = curl_error($curl);
113
+ $code = curl_errno($curl);
114
+ $map = $this->client->getClassConfig('Google_IO_Exception', 'retry_map');
115
+
116
+ $this->client->getLogger()->error('cURL ' . $error);
117
+ throw new Google_IO_Exception($error, $code, null, $map);
118
+ }
119
+ $headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
120
+
121
+ list($responseHeaders, $responseBody) = $this->parseHttpResponse($response, $headerSize);
122
+ $responseCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
123
+
124
+ $this->client->getLogger()->debug(
125
+ 'cURL response',
126
+ array(
127
+ 'code' => $responseCode,
128
+ 'headers' => $responseHeaders,
129
+ 'body' => $responseBody,
130
+ )
131
+ );
132
+
133
+ return array($responseBody, $responseHeaders, $responseCode);
134
+ }
135
+
136
+ /**
137
+ * Set options that update the transport implementation's behavior.
138
+ * @param $options
139
+ */
140
+ public function setOptions($options)
141
+ {
142
+ $this->options = $options + $this->options;
143
+ }
144
+
145
+ /**
146
+ * Set the maximum request time in seconds.
147
+ * @param $timeout in seconds
148
+ */
149
+ public function setTimeout($timeout)
150
+ {
151
+ // Since this timeout is really for putting a bound on the time
152
+ // we'll set them both to the same. If you need to specify a longer
153
+ // CURLOPT_TIMEOUT, or a higher CONNECTTIMEOUT, the best thing to
154
+ // do is use the setOptions method for the values individually.
155
+ $this->options[CURLOPT_CONNECTTIMEOUT] = $timeout;
156
+ $this->options[CURLOPT_TIMEOUT] = $timeout;
157
+ }
158
+
159
+ /**
160
+ * Get the maximum request time in seconds.
161
+ * @return timeout in seconds
162
+ */
163
+ public function getTimeout()
164
+ {
165
+ return $this->options[CURLOPT_TIMEOUT];
166
+ }
167
+
168
+ /**
169
+ * Test for the presence of a cURL header processing bug
170
+ *
171
+ * {@inheritDoc}
172
+ *
173
+ * @return boolean
174
+ */
175
+ protected function needsQuirk()
176
+ {
177
+ $ver = curl_version();
178
+ $versionNum = $ver['version_number'];
179
+ return $versionNum < Google_IO_Curl::NO_QUIRK_VERSION;
180
+ }
181
+ }
lib/Google2/IO/Exception.php ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2013 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ if (!class_exists('Google_Client')) {
19
+ require_once dirname(__FILE__) . '/../autoload.php';
20
+ }
21
+
22
+ class Google_IO_Exception extends Google_Exception implements Google_Task_Retryable
23
+ {
24
+ /**
25
+ * @var array $retryMap Map of errors with retry counts.
26
+ */
27
+ private $retryMap = array();
28
+
29
+ /**
30
+ * Creates a new IO exception with an optional retry map.
31
+ *
32
+ * @param string $message
33
+ * @param int $code
34
+ * @param Exception|null $previous
35
+ * @param array|null $retryMap Map of errors with retry counts.
36
+ */
37
+ public function __construct(
38
+ $message,
39
+ $code = 0,
40
+ Exception $previous = null,
41
+ array $retryMap = null
42
+ ) {
43
+ if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
44
+ parent::__construct($message, $code, $previous);
45
+ } else {
46
+ parent::__construct($message, $code);
47
+ }
48
+
49
+ if (is_array($retryMap)) {
50
+ $this->retryMap = $retryMap;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Gets the number of times the associated task can be retried.
56
+ *
57
+ * NOTE: -1 is returned if the task can be retried indefinitely
58
+ *
59
+ * @return integer
60
+ */
61
+ public function allowedRetries()
62
+ {
63
+ if (isset($this->retryMap[$this->code])) {
64
+ return $this->retryMap[$this->code];
65
+ }
66
+
67
+ return 0;
68
+ }
69
+ }
lib/Google2/IO/Stream.php ADDED
@@ -0,0 +1,275 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2013 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ /**
19
+ * Http Streams based implementation of Google_IO.
20
+ *
21
+ * @author Stuart Langley <slangley@google.com>
22
+ */
23
+
24
+ if (!class_exists('Google_Client')) {
25
+ require_once dirname(__FILE__) . '/../autoload.php';
26
+ }
27
+
28
+ class Google_IO_Stream extends Google_IO_Abstract
29
+ {
30
+ const TIMEOUT = "timeout";
31
+ const ZLIB = "compress.zlib://";
32
+ private $options = array();
33
+ private $trappedErrorNumber;
34
+ private $trappedErrorString;
35
+
36
+ private static $DEFAULT_HTTP_CONTEXT = array(
37
+ "follow_location" => 0,
38
+ "ignore_errors" => 1,
39
+ );
40
+
41
+ private static $DEFAULT_SSL_CONTEXT = array(
42
+ "verify_peer" => true,
43
+ );
44
+
45
+ public function __construct(Google_Client $client)
46
+ {
47
+ if (!ini_get('allow_url_fopen')) {
48
+ $error = 'The stream IO handler requires the allow_url_fopen runtime ' .
49
+ 'configuration to be enabled';
50
+ $client->getLogger()->critical($error);
51
+ throw new Google_IO_Exception($error);
52
+ }
53
+
54
+ parent::__construct($client);
55
+ }
56
+
57
+ /**
58
+ * Execute an HTTP Request
59
+ *
60
+ * @param Google_Http_Request $request the http request to be executed
61
+ * @return array containing response headers, body, and http code
62
+ * @throws Google_IO_Exception on curl or IO error
63
+ */
64
+ public function executeRequest(Google_Http_Request $request)
65
+ {
66
+ $default_options = stream_context_get_options(stream_context_get_default());
67
+
68
+ $requestHttpContext = array_key_exists('http', $default_options) ?
69
+ $default_options['http'] : array();
70
+
71
+ if ($request->getPostBody()) {
72
+ $requestHttpContext["content"] = $request->getPostBody();
73
+ }
74
+
75
+ $requestHeaders = $request->getRequestHeaders();
76
+ if ($requestHeaders && is_array($requestHeaders)) {
77
+ $headers = "";
78
+ foreach ($requestHeaders as $k => $v) {
79
+ $headers .= "$k: $v\r\n";
80
+ }
81
+ $requestHttpContext["header"] = $headers;
82
+ }
83
+
84
+ $requestHttpContext["method"] = $request->getRequestMethod();
85
+ $requestHttpContext["user_agent"] = $request->getUserAgent();
86
+
87
+ $requestSslContext = array_key_exists('ssl', $default_options) ?
88
+ $default_options['ssl'] : array();
89
+
90
+
91
+
92
+ $url = $request->getUrl();
93
+
94
+ if (preg_match('#^https?://([^/]+)/#', $url, $umatches)) { $cname = $umatches[1]; } else { $cname = false; }
95
+
96
+
97
+ // Added
98
+ if (empty($this->options['disable_verify_peer'])) {
99
+ $requestSslContext['verify_peer'] = true;
100
+ if (version_compare(PHP_VERSION, '5.6.0', '>=')) {
101
+ if (!empty($cname)) $requestSslContext['peer_name'] = $cname;
102
+ } else {
103
+ if (!empty($cname)) {
104
+ $requestSslContext['CN_match'] = $cname;
105
+ $retry_on_fail = true;
106
+ }
107
+ }
108
+ } else {
109
+ $requestSslContext['allow_self_signed'] = true;
110
+ }
111
+ if (!empty($this->options['cafile'])) $requestSslContext['cafile'] = $this->options['cafile'];
112
+
113
+ $options = array(
114
+ "http" => array_merge(
115
+ self::$DEFAULT_HTTP_CONTEXT,
116
+ $requestHttpContext
117
+ ),
118
+ "ssl" => array_merge(
119
+
120
+ $requestSslContext
121
+ )
122
+ );
123
+
124
+ $context = stream_context_create($options);
125
+
126
+
127
+
128
+ if ($request->canGzip()) {
129
+ $url = self::ZLIB . $url;
130
+ }
131
+
132
+ $this->client->getLogger()->debug(
133
+ 'Stream request',
134
+ array(
135
+ 'url' => $url,
136
+ 'method' => $request->getRequestMethod(),
137
+ 'headers' => $requestHeaders,
138
+ 'body' => $request->getPostBody()
139
+ )
140
+ );
141
+
142
+ // We are trapping any thrown errors in this method only and
143
+ // throwing an exception.
144
+ $this->trappedErrorNumber = null;
145
+ $this->trappedErrorString = null;
146
+
147
+ // START - error trap.
148
+ set_error_handler(array($this, 'trapError'));
149
+ $fh = fopen($url, 'r', false, $context);
150
+
151
+ if (!$fh && isset($retry_on_fail) && !empty($cname) && 'www.googleapis.com' == $cname) {
152
+ // Reset
153
+ $this->trappedErrorNumber = null;
154
+ $this->trappedErrorString = null;
155
+ global $iwp_backup_core;
156
+ $iwp_backup_core->log("Using Stream, and fopen failed; retrying different CN match to try to overcome");
157
+ // 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.
158
+ $options['ssl']['CN_match'] = 'www.storage.googleapis.com';
159
+ $context = stream_context_create($options);
160
+ $fh = fopen($url, 'r', false, $context);
161
+ }
162
+
163
+ restore_error_handler();
164
+ // END - error trap.
165
+
166
+ if ($this->trappedErrorNumber) {
167
+ $error = sprintf(
168
+ "HTTP Error: Unable to connect: '%s'",
169
+ $this->trappedErrorString
170
+ );
171
+
172
+ $this->client->getLogger()->error('Stream ' . $error);
173
+ throw new Google_IO_Exception($error, $this->trappedErrorNumber);
174
+ }
175
+
176
+ $response_data = false;
177
+ $respHttpCode = self::UNKNOWN_CODE;
178
+ if ($fh) {
179
+ if (isset($this->options[self::TIMEOUT])) {
180
+ stream_set_timeout($fh, $this->options[self::TIMEOUT]);
181
+ }
182
+
183
+ $response_data = stream_get_contents($fh);
184
+ fclose($fh);
185
+
186
+ $respHttpCode = $this->getHttpResponseCode($http_response_header);
187
+ }
188
+
189
+ if (false === $response_data) {
190
+ $error = sprintf(
191
+ "HTTP Error: Unable to connect: '%s'",
192
+ $respHttpCode
193
+ );
194
+
195
+ $this->client->getLogger()->error('Stream ' . $error);
196
+ throw new Google_IO_Exception($error, $respHttpCode);
197
+ }
198
+
199
+ $responseHeaders = $this->getHttpResponseHeaders($http_response_header);
200
+
201
+ $this->client->getLogger()->debug(
202
+ 'Stream response',
203
+ array(
204
+ 'code' => $respHttpCode,
205
+ 'headers' => $responseHeaders,
206
+ 'body' => $response_data,
207
+ )
208
+ );
209
+
210
+ return array($response_data, $responseHeaders, $respHttpCode);
211
+ }
212
+
213
+ /**
214
+ * Set options that update the transport implementation's behavior.
215
+ * @param $options
216
+ */
217
+ public function setOptions($options)
218
+ {
219
+ $this->options = $options + $this->options;
220
+ }
221
+
222
+ /**
223
+ * Method to handle errors, used for error handling around
224
+ * stream connection methods.
225
+ */
226
+ public function trapError($errno, $errstr)
227
+ {
228
+ $this->trappedErrorNumber = $errno;
229
+ $this->trappedErrorString = $errstr;
230
+ }
231
+
232
+ /**
233
+ * Set the maximum request time in seconds.
234
+ * @param $timeout in seconds
235
+ */
236
+ public function setTimeout($timeout)
237
+ {
238
+ $this->options[self::TIMEOUT] = $timeout;
239
+ }
240
+
241
+ /**
242
+ * Get the maximum request time in seconds.
243
+ * @return timeout in seconds
244
+ */
245
+ public function getTimeout()
246
+ {
247
+ return $this->options[self::TIMEOUT];
248
+ }
249
+
250
+ /**
251
+ * Test for the presence of a cURL header processing bug
252
+ *
253
+ * {@inheritDoc}
254
+ *
255
+ * @return boolean
256
+ */
257
+ protected function needsQuirk()
258
+ {
259
+ return false;
260
+ }
261
+
262
+ protected function getHttpResponseCode($response_headers)
263
+ {
264
+ $header_count = count($response_headers);
265
+
266
+ for ($i = 0; $i < $header_count; $i++) {
267
+ $header = $response_headers[$i];
268
+ if (strncasecmp("HTTP", $header, strlen("HTTP")) == 0) {
269
+ $response = explode(' ', $header);
270
+ return $response[1];
271
+ }
272
+ }
273
+ return self::UNKNOWN_CODE;
274
+ }
275
+ }
lib/Google2/IO/cacerts.pem ADDED
@@ -0,0 +1,2183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Issuer: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc.
2
+ # Subject: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc.
3
+ # Label: "GTE CyberTrust Global Root"
4
+ # Serial: 421
5
+ # MD5 Fingerprint: ca:3d:d3:68:f1:03:5c:d0:32:fa:b8:2b:59:e8:5a:db
6
+ # SHA1 Fingerprint: 97:81:79:50:d8:1c:96:70:cc:34:d8:09:cf:79:44:31:36:7e:f4:74
7
+ # SHA256 Fingerprint: a5:31:25:18:8d:21:10:aa:96:4b:02:c7:b7:c6:da:32:03:17:08:94:e5:fb:71:ff:fb:66:67:d5:e6:81:0a:36
8
+ -----BEGIN CERTIFICATE-----
9
+ MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD
10
+ VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv
11
+ bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv
12
+ b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV
13
+ UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
14
+ cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
15
+ b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH
16
+ iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS
17
+ r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4
18
+ 04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r
19
+ GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9
20
+ 3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P
21
+ lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/
22
+ -----END CERTIFICATE-----
23
+
24
+ # Issuer: CN=Thawte Server CA O=Thawte Consulting cc OU=Certification Services Division
25
+ # Subject: CN=Thawte Server CA O=Thawte Consulting cc OU=Certification Services Division
26
+ # Label: "Thawte Server CA"
27
+ # Serial: 1
28
+ # MD5 Fingerprint: c5:70:c4:a2:ed:53:78:0c:c8:10:53:81:64:cb:d0:1d
29
+ # SHA1 Fingerprint: 23:e5:94:94:51:95:f2:41:48:03:b4:d5:64:d2:a3:a3:f5:d8:8b:8c
30
+ # SHA256 Fingerprint: b4:41:0b:73:e2:e6:ea:ca:47:fb:c4:2f:8f:a4:01:8a:f4:38:1d:c5:4c:fa:a8:44:50:46:1e:ed:09:45:4d:e9
31
+ -----BEGIN CERTIFICATE-----
32
+ MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx
33
+ FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
34
+ VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
35
+ biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm
36
+ MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx
37
+ MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT
38
+ DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3
39
+ dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl
40
+ cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3
41
+ DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD
42
+ gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91
43
+ yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX
44
+ L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj
45
+ EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG
46
+ 7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e
47
+ QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ
48
+ qdq5snUb9kLy78fyGPmJvKP/iiMucEc=
49
+ -----END CERTIFICATE-----
50
+
51
+ # Issuer: CN=Thawte Premium Server CA O=Thawte Consulting cc OU=Certification Services Division
52
+ # Subject: CN=Thawte Premium Server CA O=Thawte Consulting cc OU=Certification Services Division
53
+ # Label: "Thawte Premium Server CA"
54
+ # Serial: 1
55
+ # MD5 Fingerprint: 06:9f:69:79:16:66:90:02:1b:8c:8c:a2:c3:07:6f:3a
56
+ # SHA1 Fingerprint: 62:7f:8d:78:27:65:63:99:d2:7d:7f:90:44:c9:fe:b3:f3:3e:fa:9a
57
+ # SHA256 Fingerprint: ab:70:36:36:5c:71:54:aa:29:c2:c2:9f:5d:41:91:16:3b:16:2a:22:25:01:13:57:d5:6d:07:ff:a7:bc:1f:72
58
+ -----BEGIN CERTIFICATE-----
59
+ MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx
60
+ FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
61
+ VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
62
+ biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy
63
+ dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t
64
+ MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB
65
+ MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG
66
+ A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp
67
+ b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl
68
+ cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv
69
+ bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE
70
+ VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ
71
+ ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR
72
+ uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG
73
+ 9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI
74
+ hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM
75
+ pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg==
76
+ -----END CERTIFICATE-----
77
+
78
+ # Issuer: O=Equifax OU=Equifax Secure Certificate Authority
79
+ # Subject: O=Equifax OU=Equifax Secure Certificate Authority
80
+ # Label: "Equifax Secure CA"
81
+ # Serial: 903804111
82
+ # MD5 Fingerprint: 67:cb:9d:c0:13:24:8a:82:9b:b2:17:1e:d1:1b:ec:d4
83
+ # SHA1 Fingerprint: d2:32:09:ad:23:d3:14:23:21:74:e4:0d:7f:9d:62:13:97:86:63:3a
84
+ # SHA256 Fingerprint: 08:29:7a:40:47:db:a2:36:80:c7:31:db:6e:31:76:53:ca:78:48:e1:be:bd:3a:0b:01:79:a7:07:f9:2c:f1:78
85
+ -----BEGIN CERTIFICATE-----
86
+ MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV
87
+ UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy
88
+ dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1
89
+ MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx
90
+ dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B
91
+ AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f
92
+ BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A
93
+ cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC
94
+ AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ
95
+ MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm
96
+ aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw
97
+ ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj
98
+ IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF
99
+ MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA
100
+ A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y
101
+ 7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh
102
+ 1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4
103
+ -----END CERTIFICATE-----
104
+
105
+ # Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority
106
+ # Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority
107
+ # Label: "Verisign Class 3 Public Primary Certification Authority"
108
+ # Serial: 149843929435818692848040365716851702463
109
+ # MD5 Fingerprint: 10:fc:63:5d:f6:26:3e:0d:f3:25:be:5f:79:cd:67:67
110
+ # SHA1 Fingerprint: 74:2c:31:92:e6:07:e4:24:eb:45:49:54:2b:e1:bb:c5:3e:61:74:e2
111
+ # SHA256 Fingerprint: e7:68:56:34:ef:ac:f6:9a:ce:93:9a:6b:25:5b:7b:4f:ab:ef:42:93:5b:50:a2:65:ac:b5:cb:60:27:e4:4e:70
112
+ -----BEGIN CERTIFICATE-----
113
+ MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG
114
+ A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
115
+ cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
116
+ MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
117
+ BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
118
+ YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
119
+ ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
120
+ BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
121
+ I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
122
+ CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do
123
+ lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc
124
+ AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k
125
+ -----END CERTIFICATE-----
126
+
127
+ # Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network
128
+ # Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network
129
+ # Label: "Verisign Class 3 Public Primary Certification Authority - G2"
130
+ # Serial: 167285380242319648451154478808036881606
131
+ # MD5 Fingerprint: a2:33:9b:4c:74:78:73:d4:6c:e7:c1:f3:8d:cb:5c:e9
132
+ # SHA1 Fingerprint: 85:37:1c:a6:e5:50:14:3d:ce:28:03:47:1b:de:3a:09:e8:f8:77:0f
133
+ # SHA256 Fingerprint: 83:ce:3c:12:29:68:8a:59:3d:48:5f:81:97:3c:0f:91:95:43:1e:da:37:cc:5e:36:43:0e:79:c7:a8:88:63:8b
134
+ -----BEGIN CERTIFICATE-----
135
+ MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ
136
+ BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh
137
+ c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy
138
+ MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp
139
+ emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X
140
+ DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw
141
+ FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg
142
+ UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo
143
+ YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5
144
+ MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB
145
+ AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4
146
+ pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0
147
+ 13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID
148
+ AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk
149
+ U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i
150
+ F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY
151
+ oJ2daZH9
152
+ -----END CERTIFICATE-----
153
+
154
+ # Issuer: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA
155
+ # Subject: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA
156
+ # Label: "GlobalSign Root CA"
157
+ # Serial: 4835703278459707669005204
158
+ # MD5 Fingerprint: 3e:45:52:15:09:51:92:e1:b7:5d:37:9f:b1:87:29:8a
159
+ # SHA1 Fingerprint: b1:bc:96:8b:d4:f4:9d:62:2a:a8:9a:81:f2:15:01:52:a4:1d:82:9c
160
+ # SHA256 Fingerprint: eb:d4:10:40:e4:bb:3e:c7:42:c9:e3:81:d3:1e:f2:a4:1a:48:b6:68:5c:96:e7:ce:f3:c1:df:6c:d4:33:1c:99
161
+ -----BEGIN CERTIFICATE-----
162
+ MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG
163
+ A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
164
+ b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw
165
+ MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
166
+ YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT
167
+ aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ
168
+ jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp
169
+ xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp
170
+ 1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG
171
+ snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ
172
+ U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8
173
+ 9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E
174
+ BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B
175
+ AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz
176
+ yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE
177
+ 38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP
178
+ AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad
179
+ DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME
180
+ HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
181
+ -----END CERTIFICATE-----
182
+
183
+ # Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2
184
+ # Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2
185
+ # Label: "GlobalSign Root CA - R2"
186
+ # Serial: 4835703278459682885658125
187
+ # MD5 Fingerprint: 94:14:77:7e:3e:5e:fd:8f:30:bd:41:b0:cf:e7:d0:30
188
+ # SHA1 Fingerprint: 75:e0:ab:b6:13:85:12:27:1c:04:f8:5f:dd:de:38:e4:b7:24:2e:fe
189
+ # SHA256 Fingerprint: ca:42:dd:41:74:5f:d0:b8:1e:b9:02:36:2c:f9:d8:bf:71:9d:a1:bd:1b:1e:fc:94:6f:5b:4c:99:f4:2c:1b:9e
190
+ -----BEGIN CERTIFICATE-----
191
+ MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G
192
+ A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp
193
+ Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1
194
+ MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG
195
+ A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
196
+ hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL
197
+ v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8
198
+ eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq
199
+ tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd
200
+ C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa
201
+ zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB
202
+ mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH
203
+ V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n
204
+ bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG
205
+ 3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs
206
+ J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO
207
+ 291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS
208
+ ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd
209
+ AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7
210
+ TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==
211
+ -----END CERTIFICATE-----
212
+
213
+ # Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 1 Policy Validation Authority
214
+ # Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 1 Policy Validation Authority
215
+ # Label: "ValiCert Class 1 VA"
216
+ # Serial: 1
217
+ # MD5 Fingerprint: 65:58:ab:15:ad:57:6c:1e:a8:a7:b5:69:ac:bf:ff:eb
218
+ # SHA1 Fingerprint: e5:df:74:3c:b6:01:c4:9b:98:43:dc:ab:8c:e8:6a:81:10:9f:e4:8e
219
+ # SHA256 Fingerprint: f4:c1:49:55:1a:30:13:a3:5b:c7:bf:fe:17:a7:f3:44:9b:c1:ab:5b:5a:0a:e7:4b:06:c2:3b:90:00:4c:01:04
220
+ -----BEGIN CERTIFICATE-----
221
+ MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
222
+ IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
223
+ BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
224
+ aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
225
+ 9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIyMjM0OFoXDTE5MDYy
226
+ NTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
227
+ azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
228
+ YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
229
+ Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
230
+ cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9Y
231
+ LqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIiGQj4/xEjm84H9b9pGib+
232
+ TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCmDuJWBQ8Y
233
+ TfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0
234
+ LBwGlN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLW
235
+ I8sogTLDAHkY7FkXicnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPw
236
+ nXS3qT6gpf+2SQMT2iLM7XGCK5nPOrf1LXLI
237
+ -----END CERTIFICATE-----
238
+
239
+ # Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 2 Policy Validation Authority
240
+ # Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 2 Policy Validation Authority
241
+ # Label: "ValiCert Class 2 VA"
242
+ # Serial: 1
243
+ # MD5 Fingerprint: a9:23:75:9b:ba:49:36:6e:31:c2:db:f2:e7:66:ba:87
244
+ # SHA1 Fingerprint: 31:7a:2a:d0:7f:2b:33:5e:f5:a1:c3:4e:4b:57:e8:b7:d8:f1:fc:a6
245
+ # SHA256 Fingerprint: 58:d0:17:27:9c:d4:dc:63:ab:dd:b1:96:a6:c9:90:6c:30:c4:e0:87:83:ea:e8:c1:60:99:54:d6:93:55:59:6b
246
+ -----BEGIN CERTIFICATE-----
247
+ MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
248
+ IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
249
+ BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
250
+ aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
251
+ 9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy
252
+ NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
253
+ azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
254
+ YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
255
+ Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
256
+ cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY
257
+ dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9
258
+ WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS
259
+ v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v
260
+ UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu
261
+ IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC
262
+ W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd
263
+ -----END CERTIFICATE-----
264
+
265
+ # Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 3 Policy Validation Authority
266
+ # Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 3 Policy Validation Authority
267
+ # Label: "RSA Root Certificate 1"
268
+ # Serial: 1
269
+ # MD5 Fingerprint: a2:6f:53:b7:ee:40:db:4a:68:e7:fa:18:d9:10:4b:72
270
+ # SHA1 Fingerprint: 69:bd:8c:f4:9c:d3:00:fb:59:2e:17:93:ca:55:6a:f3:ec:aa:35:fb
271
+ # SHA256 Fingerprint: bc:23:f9:8a:31:3c:b9:2d:e3:bb:fc:3a:5a:9f:44:61:ac:39:49:4c:4a:e1:5a:9e:9d:f1:31:e9:9b:73:01:9a
272
+ -----BEGIN CERTIFICATE-----
273
+ MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
274
+ IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
275
+ BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
276
+ aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
277
+ 9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMjIzM1oXDTE5MDYy
278
+ NjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
279
+ azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
280
+ YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
281
+ Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
282
+ cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjmFGWHOjVsQaBalfD
283
+ cnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td3zZxFJmP3MKS8edgkpfs
284
+ 2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89HBFx1cQqY
285
+ JJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliE
286
+ Zwgs3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJ
287
+ n0WuPIqpsHEzXcjFV9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/A
288
+ PhmcGcwTTYJBtYze4D1gCCAPRX5ron+jjBXu
289
+ -----END CERTIFICATE-----
290
+
291
+ # Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only
292
+ # Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only
293
+ # Label: "Verisign Class 3 Public Primary Certification Authority - G3"
294
+ # Serial: 206684696279472310254277870180966723415
295
+ # MD5 Fingerprint: cd:68:b6:a7:c7:c4:ce:75:e0:1d:4f:57:44:61:92:09
296
+ # SHA1 Fingerprint: 13:2d:0d:45:53:4b:69:97:cd:b2:d5:c3:39:e2:55:76:60:9b:5c:c6
297
+ # SHA256 Fingerprint: eb:04:cf:5e:b1:f3:9a:fa:76:2f:2b:b1:20:f2:96:cb:a5:20:c1:b9:7d:b1:58:95:65:b8:1c:b9:a1:7b:72:44
298
+ -----BEGIN CERTIFICATE-----
299
+ MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw
300
+ CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
301
+ cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
302
+ LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
303
+ aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
304
+ dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
305
+ VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
306
+ aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
307
+ bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
308
+ IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
309
+ LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b
310
+ N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t
311
+ KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu
312
+ kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm
313
+ CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ
314
+ Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu
315
+ imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te
316
+ 2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe
317
+ DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC
318
+ /Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p
319
+ F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt
320
+ TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ==
321
+ -----END CERTIFICATE-----
322
+
323
+ # Issuer: CN=VeriSign Class 4 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only
324
+ # Subject: CN=VeriSign Class 4 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only
325
+ # Label: "Verisign Class 4 Public Primary Certification Authority - G3"
326
+ # Serial: 314531972711909413743075096039378935511
327
+ # MD5 Fingerprint: db:c8:f2:27:2e:b1:ea:6a:29:23:5d:fe:56:3e:33:df
328
+ # SHA1 Fingerprint: c8:ec:8c:87:92:69:cb:4b:ab:39:e9:8d:7e:57:67:f3:14:95:73:9d
329
+ # SHA256 Fingerprint: e3:89:36:0d:0f:db:ae:b3:d2:50:58:4b:47:30:31:4e:22:2f:39:c1:56:a0:20:14:4e:8d:96:05:61:79:15:06
330
+ -----BEGIN CERTIFICATE-----
331
+ MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQsw
332
+ CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
333
+ cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
334
+ LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
335
+ aWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
336
+ dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
337
+ VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
338
+ aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
339
+ bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
340
+ IENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
341
+ LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK3LpRFpxlmr8Y+1
342
+ GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaStBO3IFsJ
343
+ +mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0Gbd
344
+ U6LM8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLm
345
+ NxdLMEYH5IBtptiWLugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XY
346
+ ufTsgsbSPZUd5cBPhMnZo0QoBmrXRazwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/
347
+ ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAj/ola09b5KROJ1WrIhVZPMq1
348
+ CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXttmhwwjIDLk5Mq
349
+ g6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm
350
+ fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c
351
+ 2NU8Qh0XwRJdRTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/
352
+ bLvSHgCwIe34QWKCudiyxLtGUPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg==
353
+ -----END CERTIFICATE-----
354
+
355
+ # Issuer: CN=Entrust.net Secure Server Certification Authority O=Entrust.net OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited
356
+ # Subject: CN=Entrust.net Secure Server Certification Authority O=Entrust.net OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited
357
+ # Label: "Entrust.net Secure Server CA"
358
+ # Serial: 927650371
359
+ # MD5 Fingerprint: df:f2:80:73:cc:f1:e6:61:73:fc:f5:42:e9:c5:7c:ee
360
+ # SHA1 Fingerprint: 99:a6:9b:e6:1a:fe:88:6b:4d:2b:82:00:7c:b8:54:fc:31:7e:15:39
361
+ # SHA256 Fingerprint: 62:f2:40:27:8c:56:4c:4d:d8:bf:7d:9d:4f:6f:36:6e:a8:94:d2:2f:5f:34:d9:89:a9:83:ac:ec:2f:ff:ed:50
362
+ -----BEGIN CERTIFICATE-----
363
+ MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
364
+ VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
365
+ ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
366
+ KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
367
+ ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1
368
+ MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE
369
+ ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j
370
+ b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF
371
+ bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg
372
+ U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA
373
+ A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/
374
+ I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3
375
+ wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC
376
+ AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb
377
+ oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5
378
+ BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p
379
+ dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk
380
+ MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp
381
+ b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu
382
+ dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0
383
+ MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi
384
+ E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa
385
+ MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI
386
+ hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN
387
+ 95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd
388
+ 2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI=
389
+ -----END CERTIFICATE-----
390
+
391
+ # Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited
392
+ # Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited
393
+ # Label: "Entrust.net Premium 2048 Secure Server CA"
394
+ # Serial: 946059622
395
+ # MD5 Fingerprint: ba:21:ea:20:d6:dd:db:8f:c1:57:8b:40:ad:a1:fc:fc
396
+ # SHA1 Fingerprint: 80:1d:62:d0:7b:44:9d:5c:5c:03:5c:98:ea:61:fa:44:3c:2a:58:fe
397
+ # SHA256 Fingerprint: d1:c3:39:ea:27:84:eb:87:0f:93:4f:c5:63:4e:4a:a9:ad:55:05:01:64:01:f2:64:65:d3:7a:57:46:63:35:9f
398
+ -----BEGIN CERTIFICATE-----
399
+ MIIEXDCCA0SgAwIBAgIEOGO5ZjANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML
400
+ RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp
401
+ bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5
402
+ IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp
403
+ ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0xOTEy
404
+ MjQxODIwNTFaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3
405
+ LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp
406
+ YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG
407
+ A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp
408
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq
409
+ K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe
410
+ sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX
411
+ MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT
412
+ XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/
413
+ HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH
414
+ 4QIDAQABo3QwcjARBglghkgBhvhCAQEEBAMCAAcwHwYDVR0jBBgwFoAUVeSB0RGA
415
+ vtiJuQijMfmhJAkWuXAwHQYDVR0OBBYEFFXkgdERgL7YibkIozH5oSQJFrlwMB0G
416
+ CSqGSIb2fQdBAAQQMA4bCFY1LjA6NC4wAwIEkDANBgkqhkiG9w0BAQUFAAOCAQEA
417
+ WUesIYSKF8mciVMeuoCFGsY8Tj6xnLZ8xpJdGGQC49MGCBFhfGPjK50xA3B20qMo
418
+ oPS7mmNz7W3lKtvtFKkrxjYR0CvrB4ul2p5cGZ1WEvVUKcgF7bISKo30Axv/55IQ
419
+ h7A6tcOdBTcSo8f0FbnVpDkWm1M6I5HxqIKiaohowXkCIryqptau37AUX7iH0N18
420
+ f3v/rxzP5tsHrV7bhZ3QKw0z2wTR5klAEyt2+z7pnIkPFc4YsIV4IU9rTw76NmfN
421
+ B/L/CNDi3tm/Kq+4h4YhPATKt5Rof8886ZjXOP/swNlQ8C5LWK5Gb9Auw2DaclVy
422
+ vUxFnmG6v4SBkgPR0ml8xQ==
423
+ -----END CERTIFICATE-----
424
+
425
+ # Issuer: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust
426
+ # Subject: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust
427
+ # Label: "Baltimore CyberTrust Root"
428
+ # Serial: 33554617
429
+ # MD5 Fingerprint: ac:b6:94:a5:9c:17:e0:d7:91:52:9b:b1:97:06:a6:e4
430
+ # SHA1 Fingerprint: d4:de:20:d0:5e:66:fc:53:fe:1a:50:88:2c:78:db:28:52:ca:e4:74
431
+ # SHA256 Fingerprint: 16:af:57:a9:f6:76:b0:ab:12:60:95:aa:5e:ba:de:f2:2a:b3:11:19:d6:44:ac:95:cd:4b:93:db:f3:f2:6a:eb
432
+ -----BEGIN CERTIFICATE-----
433
+ MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
434
+ RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
435
+ VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX
436
+ DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y
437
+ ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy
438
+ VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr
439
+ mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr
440
+ IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK
441
+ mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu
442
+ XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy
443
+ dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye
444
+ jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1
445
+ BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3
446
+ DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92
447
+ 9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx
448
+ jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0
449
+ Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz
450
+ ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS
451
+ R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
452
+ -----END CERTIFICATE-----
453
+
454
+ # Issuer: CN=Equifax Secure Global eBusiness CA-1 O=Equifax Secure Inc.
455
+ # Subject: CN=Equifax Secure Global eBusiness CA-1 O=Equifax Secure Inc.
456
+ # Label: "Equifax Secure Global eBusiness CA"
457
+ # Serial: 1
458
+ # MD5 Fingerprint: 8f:5d:77:06:27:c4:98:3c:5b:93:78:e7:d7:7d:9b:cc
459
+ # SHA1 Fingerprint: 7e:78:4a:10:1c:82:65:cc:2d:e1:f1:6d:47:b4:40:ca:d9:0a:19:45
460
+ # SHA256 Fingerprint: 5f:0b:62:ea:b5:e3:53:ea:65:21:65:16:58:fb:b6:53:59:f4:43:28:0a:4a:fb:d1:04:d7:7d:10:f9:f0:4c:07
461
+ -----BEGIN CERTIFICATE-----
462
+ MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc
463
+ MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT
464
+ ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw
465
+ MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj
466
+ dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l
467
+ c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC
468
+ UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc
469
+ 58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/
470
+ o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH
471
+ MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr
472
+ aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA
473
+ A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA
474
+ Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv
475
+ 8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV
476
+ -----END CERTIFICATE-----
477
+
478
+ # Issuer: CN=Equifax Secure eBusiness CA-1 O=Equifax Secure Inc.
479
+ # Subject: CN=Equifax Secure eBusiness CA-1 O=Equifax Secure Inc.
480
+ # Label: "Equifax Secure eBusiness CA 1"
481
+ # Serial: 4
482
+ # MD5 Fingerprint: 64:9c:ef:2e:44:fc:c6:8f:52:07:d0:51:73:8f:cb:3d
483
+ # SHA1 Fingerprint: da:40:18:8b:91:89:a3:ed:ee:ae:da:97:fe:2f:9d:f5:b7:d1:8a:41
484
+ # SHA256 Fingerprint: cf:56:ff:46:a4:a1:86:10:9d:d9:65:84:b5:ee:b5:8a:51:0c:42:75:b0:e5:f9:4f:40:bb:ae:86:5e:19:f6:73
485
+ -----BEGIN CERTIFICATE-----
486
+ MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEc
487
+ MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBT
488
+ ZWN1cmUgZUJ1c2luZXNzIENBLTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQw
489
+ MDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5j
490
+ LjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENBLTEwgZ8wDQYJ
491
+ KoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ1MRo
492
+ RvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBu
493
+ WqDZQu4aIZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKw
494
+ Env+j6YDAgMBAAGjZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTAD
495
+ AQH/MB8GA1UdIwQYMBaAFEp4MlIR21kWNl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRK
496
+ eDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQFAAOBgQB1W6ibAxHm6VZM
497
+ zfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5lSE/9dR+
498
+ WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN
499
+ /Bf+KpYrtWKmpj29f5JZzVoqgrI3eQ==
500
+ -----END CERTIFICATE-----
501
+
502
+ # Issuer: O=Equifax Secure OU=Equifax Secure eBusiness CA-2
503
+ # Subject: O=Equifax Secure OU=Equifax Secure eBusiness CA-2
504
+ # Label: "Equifax Secure eBusiness CA 2"
505
+ # Serial: 930140085
506
+ # MD5 Fingerprint: aa:bf:bf:64:97:da:98:1d:6f:c6:08:3a:95:70:33:ca
507
+ # SHA1 Fingerprint: 39:4f:f6:85:0b:06:be:52:e5:18:56:cc:10:e1:80:e8:82:b3:85:cc
508
+ # SHA256 Fingerprint: 2f:27:4e:48:ab:a4:ac:7b:76:59:33:10:17:75:50:6d:c3:0e:e3:8e:f6:ac:d5:c0:49:32:cf:e0:41:23:42:20
509
+ -----BEGIN CERTIFICATE-----
510
+ MIIDIDCCAomgAwIBAgIEN3DPtTANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV
511
+ UzEXMBUGA1UEChMORXF1aWZheCBTZWN1cmUxJjAkBgNVBAsTHUVxdWlmYXggU2Vj
512
+ dXJlIGVCdXNpbmVzcyBDQS0yMB4XDTk5MDYyMzEyMTQ0NVoXDTE5MDYyMzEyMTQ0
513
+ NVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkVxdWlmYXggU2VjdXJlMSYwJAYD
514
+ VQQLEx1FcXVpZmF4IFNlY3VyZSBlQnVzaW5lc3MgQ0EtMjCBnzANBgkqhkiG9w0B
515
+ AQEFAAOBjQAwgYkCgYEA5Dk5kx5SBhsoNviyoynF7Y6yEb3+6+e0dMKP/wXn2Z0G
516
+ vxLIPw7y1tEkshHe0XMJitSxLJgJDR5QRrKDpkWNYmi7hRsgcDKqQM2mll/EcTc/
517
+ BPO3QSQ5BxoeLmFYoBIL5aXfxavqN3HMHMg3OrmXUqesxWoklE6ce8/AatbfIb0C
518
+ AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEX
519
+ MBUGA1UEChMORXF1aWZheCBTZWN1cmUxJjAkBgNVBAsTHUVxdWlmYXggU2VjdXJl
520
+ IGVCdXNpbmVzcyBDQS0yMQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTkw
521
+ NjIzMTIxNDQ1WjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUUJ4L6q9euSBIplBq
522
+ y/3YIHqngnYwHQYDVR0OBBYEFFCeC+qvXrkgSKZQasv92CB6p4J2MAwGA1UdEwQF
523
+ MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA
524
+ A4GBAAyGgq3oThr1jokn4jVYPSm0B482UJW/bsGe68SQsoWou7dC4A8HOd/7npCy
525
+ 0cE+U58DRLB+S/Rv5Hwf5+Kx5Lia78O9zt4LMjTZ3ijtM2vE1Nc9ElirfQkty3D1
526
+ E4qUoSek1nDFbZS1yX2doNLGCEnZZpum0/QL3MUmV+GRMOrN
527
+ -----END CERTIFICATE-----
528
+
529
+ # Issuer: CN=AddTrust Class 1 CA Root O=AddTrust AB OU=AddTrust TTP Network
530
+ # Subject: CN=AddTrust Class 1 CA Root O=AddTrust AB OU=AddTrust TTP Network
531
+ # Label: "AddTrust Low-Value Services Root"
532
+ # Serial: 1
533
+ # MD5 Fingerprint: 1e:42:95:02:33:92:6b:b9:5f:c0:7f:da:d6:b2:4b:fc
534
+ # SHA1 Fingerprint: cc:ab:0e:a0:4c:23:01:d6:69:7b:dd:37:9f:cd:12:eb:24:e3:94:9d
535
+ # SHA256 Fingerprint: 8c:72:09:27:9a:c0:4e:27:5e:16:d0:7f:d3:b7:75:e8:01:54:b5:96:80:46:e3:1f:52:dd:25:76:63:24:e9:a7
536
+ -----BEGIN CERTIFICATE-----
537
+ MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEU
538
+ MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
539
+ b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMw
540
+ MTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
541
+ QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYD
542
+ VQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUA
543
+ A4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ul
544
+ CDtbKRY654eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6n
545
+ tGO0/7Gcrjyvd7ZWxbWroulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyl
546
+ dI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1Zmne3yzxbrww2ywkEtvrNTVokMsAsJch
547
+ PXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJuiGMx1I4S+6+JNM3GOGvDC
548
+ +Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8wHQYDVR0O
549
+ BBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8E
550
+ BTADAQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBl
551
+ MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFk
552
+ ZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENB
553
+ IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxtZBsfzQ3duQH6lmM0MkhHma6X
554
+ 7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0PhiVYrqW9yTkkz
555
+ 43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY
556
+ eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJl
557
+ pz/+0WatC7xrmYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOA
558
+ WiFeIc9TVPC6b4nbqKqVz4vjccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk=
559
+ -----END CERTIFICATE-----
560
+
561
+ # Issuer: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network
562
+ # Subject: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network
563
+ # Label: "AddTrust External Root"
564
+ # Serial: 1
565
+ # MD5 Fingerprint: 1d:35:54:04:85:78:b0:3f:42:42:4d:bf:20:73:0a:3f
566
+ # SHA1 Fingerprint: 02:fa:f3:e2:91:43:54:68:60:78:57:69:4d:f5:e4:5b:68:85:18:68
567
+ # SHA256 Fingerprint: 68:7f:a4:51:38:22:78:ff:f0:c8:b1:1f:8d:43:d5:76:67:1c:6e:b2:bc:ea:b4:13:fb:83:d9:65:d0:6d:2f:f2
568
+ -----BEGIN CERTIFICATE-----
569
+ MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
570
+ MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
571
+ IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
572
+ MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
573
+ FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
574
+ bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
575
+ dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
576
+ H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
577
+ uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
578
+ mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
579
+ a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
580
+ E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
581
+ WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
582
+ VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
583
+ Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
584
+ cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
585
+ IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
586
+ AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
587
+ YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
588
+ 6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
589
+ Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
590
+ c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
591
+ mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
592
+ -----END CERTIFICATE-----
593
+
594
+ # Issuer: CN=AddTrust Public CA Root O=AddTrust AB OU=AddTrust TTP Network
595
+ # Subject: CN=AddTrust Public CA Root O=AddTrust AB OU=AddTrust TTP Network
596
+ # Label: "AddTrust Public Services Root"
597
+ # Serial: 1
598
+ # MD5 Fingerprint: c1:62:3e:23:c5:82:73:9c:03:59:4b:2b:e9:77:49:7f
599
+ # SHA1 Fingerprint: 2a:b6:28:48:5e:78:fb:f3:ad:9e:79:10:dd:6b:df:99:72:2c:96:e5
600
+ # SHA256 Fingerprint: 07:91:ca:07:49:b2:07:82:aa:d3:c7:d7:bd:0c:df:c9:48:58:35:84:3e:b2:d7:99:60:09:ce:43:ab:6c:69:27
601
+ -----BEGIN CERTIFICATE-----
602
+ MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEU
603
+ MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
604
+ b3JrMSAwHgYDVQQDExdBZGRUcnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAx
605
+ MDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtB
606
+ ZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIDAeBgNV
607
+ BAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOC
608
+ AQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV
609
+ 6tsfSlbunyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nX
610
+ GCwwfQ56HmIexkvA/X1id9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnP
611
+ dzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSGAa2Il+tmzV7R/9x98oTaunet3IAIx6eH
612
+ 1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAwHM+A+WD+eeSI8t0A65RF
613
+ 62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0GA1UdDgQW
614
+ BBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUw
615
+ AwEB/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDEL
616
+ MAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRU
617
+ cnVzdCBUVFAgTmV0d29yazEgMB4GA1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJv
618
+ b3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4JNojVhaTdt02KLmuG7jD8WS6
619
+ IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL+YPoRNWyQSW/
620
+ iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao
621
+ GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh
622
+ 4SINhwBk/ox9Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQm
623
+ XiLsks3/QppEIW1cxeMiHV9HEufOX1362KqxMy3ZdvJOOjMMK7MtkAY=
624
+ -----END CERTIFICATE-----
625
+
626
+ # Issuer: CN=AddTrust Qualified CA Root O=AddTrust AB OU=AddTrust TTP Network
627
+ # Subject: CN=AddTrust Qualified CA Root O=AddTrust AB OU=AddTrust TTP Network
628
+ # Label: "AddTrust Qualified Certificates Root"
629
+ # Serial: 1
630
+ # MD5 Fingerprint: 27:ec:39:47:cd:da:5a:af:e2:9a:01:65:21:a9:4c:bb
631
+ # SHA1 Fingerprint: 4d:23:78:ec:91:95:39:b5:00:7f:75:8f:03:3b:21:1e:c5:4d:8b:cf
632
+ # SHA256 Fingerprint: 80:95:21:08:05:db:4b:bc:35:5e:44:28:d8:fd:6e:c2:cd:e3:ab:5f:b9:7a:99:42:98:8e:b8:f4:dc:d0:60:16
633
+ -----BEGIN CERTIFICATE-----
634
+ MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEU
635
+ MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
636
+ b3JrMSMwIQYDVQQDExpBZGRUcnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1
637
+ MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcxCzAJBgNVBAYTAlNFMRQwEgYDVQQK
638
+ EwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIzAh
639
+ BgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG9w0B
640
+ AQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwq
641
+ xBb/4Oxx64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G
642
+ 87B4pfYOQnrjfxvM0PC3KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i
643
+ 2O+tCBGaKZnhqkRFmhJePp1tUvznoD1oL/BLcHwTOK28FSXx1s6rosAx1i+f4P8U
644
+ WfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GRwVY18BTcZTYJbqukB8c1
645
+ 0cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HUMIHRMB0G
646
+ A1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0T
647
+ AQH/BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6Fr
648
+ pGkwZzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQL
649
+ ExRBZGRUcnVzdCBUVFAgTmV0d29yazEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlm
650
+ aWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBABmrder4i2VhlRO6aQTv
651
+ hsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxGGuoYQ992zPlm
652
+ hpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X
653
+ dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3
654
+ P6CxB9bpT9zeRXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9Y
655
+ iQBCYz95OdBEsIJuQRno3eDBiFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5no
656
+ xqE=
657
+ -----END CERTIFICATE-----
658
+
659
+ # Issuer: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc.
660
+ # Subject: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc.
661
+ # Label: "Entrust Root Certification Authority"
662
+ # Serial: 1164660820
663
+ # MD5 Fingerprint: d6:a5:c3:ed:5d:dd:3e:00:c1:3d:87:92:1f:1d:3f:e4
664
+ # SHA1 Fingerprint: b3:1e:b1:b7:40:e3:6c:84:02:da:dc:37:d4:4d:f5:d4:67:49:52:f9
665
+ # SHA256 Fingerprint: 73:c1:76:43:4f:1b:c6:d5:ad:f4:5b:0e:76:e7:27:28:7c:8d:e5:76:16:c1:e6:e6:14:1a:2b:2c:bc:7d:8e:4c
666
+ -----BEGIN CERTIFICATE-----
667
+ MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC
668
+ VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0
669
+ Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW
670
+ KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl
671
+ cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw
672
+ NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw
673
+ NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy
674
+ ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV
675
+ BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ
676
+ KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo
677
+ Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4
678
+ 4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9
679
+ KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI
680
+ rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi
681
+ 94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB
682
+ sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi
683
+ gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo
684
+ kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE
685
+ vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA
686
+ A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t
687
+ O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua
688
+ AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP
689
+ 9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/
690
+ eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m
691
+ 0vdXcDazv/wor3ElhVsT/h5/WrQ8
692
+ -----END CERTIFICATE-----
693
+
694
+ # Issuer: CN=GeoTrust Global CA O=GeoTrust Inc.
695
+ # Subject: CN=GeoTrust Global CA O=GeoTrust Inc.
696
+ # Label: "GeoTrust Global CA"
697
+ # Serial: 144470
698
+ # MD5 Fingerprint: f7:75:ab:29:fb:51:4e:b7:77:5e:ff:05:3c:99:8e:f5
699
+ # SHA1 Fingerprint: de:28:f4:a4:ff:e5:b9:2f:a3:c5:03:d1:a3:49:a7:f9:96:2a:82:12
700
+ # SHA256 Fingerprint: ff:85:6a:2d:25:1d:cd:88:d3:66:56:f4:50:12:67:98:cf:ab:aa:de:40:79:9c:72:2d:e4:d2:b5:db:36:a7:3a
701
+ -----BEGIN CERTIFICATE-----
702
+ MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
703
+ MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
704
+ YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG
705
+ EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg
706
+ R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9
707
+ 9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq
708
+ fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv
709
+ iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU
710
+ 1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+
711
+ bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW
712
+ MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA
713
+ ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l
714
+ uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn
715
+ Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS
716
+ tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF
717
+ PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un
718
+ hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV
719
+ 5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==
720
+ -----END CERTIFICATE-----
721
+
722
+ # Issuer: CN=GeoTrust Global CA 2 O=GeoTrust Inc.
723
+ # Subject: CN=GeoTrust Global CA 2 O=GeoTrust Inc.
724
+ # Label: "GeoTrust Global CA 2"
725
+ # Serial: 1
726
+ # MD5 Fingerprint: 0e:40:a7:6c:de:03:5d:8f:d1:0f:e4:d1:8d:f9:6c:a9
727
+ # SHA1 Fingerprint: a9:e9:78:08:14:37:58:88:f2:05:19:b0:6d:2b:0d:2b:60:16:90:7d
728
+ # SHA256 Fingerprint: ca:2d:82:a0:86:77:07:2f:8a:b6:76:4f:f0:35:67:6c:fe:3e:5e:32:5e:01:21:72:df:3f:92:09:6d:b7:9b:85
729
+ -----BEGIN CERTIFICATE-----
730
+ MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEW
731
+ MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFs
732
+ IENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQG
733
+ EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3Qg
734
+ R2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDvPE1A
735
+ PRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/NTL8
736
+ Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hL
737
+ TytCOb1kLUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL
738
+ 5mkWRxHCJ1kDs6ZgwiFAVvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7
739
+ S4wMcoKK+xfNAGw6EzywhIdLFnopsk/bHdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe
740
+ 2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
741
+ FHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNHK266ZUap
742
+ EBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6td
743
+ EPx7srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv
744
+ /NgdRN3ggX+d6YvhZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywN
745
+ A0ZF66D0f0hExghAzN4bcLUprbqLOzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0
746
+ abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkCx1YAzUm5s2x7UwQa4qjJqhIF
747
+ I8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqFH4z1Ir+rzoPz
748
+ 4iIprn2DQKi6bA==
749
+ -----END CERTIFICATE-----
750
+
751
+ # Issuer: CN=GeoTrust Universal CA O=GeoTrust Inc.
752
+ # Subject: CN=GeoTrust Universal CA O=GeoTrust Inc.
753
+ # Label: "GeoTrust Universal CA"
754
+ # Serial: 1
755
+ # MD5 Fingerprint: 92:65:58:8b:a2:1a:31:72:73:68:5c:b4:a5:7a:07:48
756
+ # SHA1 Fingerprint: e6:21:f3:35:43:79:05:9a:4b:68:30:9d:8a:2f:74:22:15:87:ec:79
757
+ # SHA256 Fingerprint: a0:45:9b:9f:63:b2:25:59:f5:fa:5d:4c:6d:b3:f9:f7:2f:f1:93:42:03:35:78:f0:73:bf:1d:1b:46:cb:b9:12
758
+ -----BEGIN CERTIFICATE-----
759
+ MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW
760
+ MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVy
761
+ c2FsIENBMB4XDTA0MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UE
762
+ BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0
763
+ IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKYV
764
+ VaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9tJPi8
765
+ cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTT
766
+ QjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFh
767
+ F7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v
768
+ c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/w
769
+ mZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xd
770
+ VHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCX
771
+ teGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZ
772
+ f9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfRe
773
+ Bi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+
774
+ nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB
775
+ /wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8GA1UdIwQY
776
+ MBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG
777
+ 9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc
778
+ aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fX
779
+ IwjhmF7DWgh2qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzyn
780
+ ANXH/KttgCJwpQzgXQQpAvvLoJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0z
781
+ uzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqN
782
+ Pnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxFKyDuSN/n3QmOGKja
783
+ QI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2DFKW
784
+ koRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9
785
+ ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt
786
+ DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm
787
+ bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw=
788
+ -----END CERTIFICATE-----
789
+
790
+ # Issuer: CN=GeoTrust Universal CA 2 O=GeoTrust Inc.
791
+ # Subject: CN=GeoTrust Universal CA 2 O=GeoTrust Inc.
792
+ # Label: "GeoTrust Universal CA 2"
793
+ # Serial: 1
794
+ # MD5 Fingerprint: 34:fc:b8:d0:36:db:9e:14:b3:c2:f2:db:8f:e4:94:c7
795
+ # SHA1 Fingerprint: 37:9a:19:7b:41:85:45:35:0c:a6:03:69:f3:3c:2e:af:47:4f:20:79
796
+ # SHA256 Fingerprint: a0:23:4f:3b:c8:52:7c:a5:62:8e:ec:81:ad:5d:69:89:5d:a5:68:0d:c9:1d:1c:b8:47:7f:33:f8:78:b9:5b:0b
797
+ -----BEGIN CERTIFICATE-----
798
+ MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW
799
+ MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy
800
+ c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD
801
+ VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1
802
+ c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
803
+ AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81
804
+ WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG
805
+ FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq
806
+ XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL
807
+ se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb
808
+ KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd
809
+ IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73
810
+ y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt
811
+ hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc
812
+ QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4
813
+ Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV
814
+ HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV
815
+ HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ
816
+ KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z
817
+ dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ
818
+ L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr
819
+ Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo
820
+ ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY
821
+ T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz
822
+ GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m
823
+ 1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV
824
+ OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH
825
+ 6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX
826
+ QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS
827
+ -----END CERTIFICATE-----
828
+
829
+ # Issuer: CN=America Online Root Certification Authority 1 O=America Online Inc.
830
+ # Subject: CN=America Online Root Certification Authority 1 O=America Online Inc.
831
+ # Label: "America Online Root Certification Authority 1"
832
+ # Serial: 1
833
+ # MD5 Fingerprint: 14:f1:08:ad:9d:fa:64:e2:89:e7:1c:cf:a8:ad:7d:5e
834
+ # SHA1 Fingerprint: 39:21:c1:15:c1:5d:0e:ca:5c:cb:5b:c4:f0:7d:21:d8:05:0b:56:6a
835
+ # SHA256 Fingerprint: 77:40:73:12:c6:3a:15:3d:5b:c0:0b:4e:51:75:9c:df:da:c2:37:dc:2a:33:b6:79:46:e9:8e:9b:fa:68:0a:e3
836
+ -----BEGIN CERTIFICATE-----
837
+ MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc
838
+ MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP
839
+ bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2
840
+ MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft
841
+ ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg
842
+ Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP
843
+ ADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lk
844
+ hsmj76CGv2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym
845
+ 1BW32J/X3HGrfpq/m44zDyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsW
846
+ OqMFf6Dch9Wc/HKpoH145LcxVR5lu9RhsCFg7RAycsWSJR74kEoYeEfffjA3PlAb
847
+ 2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP8c9GsEsPPt2IYriMqQko
848
+ O3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0TAQH/BAUw
849
+ AwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAU
850
+ AK3Zo/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB
851
+ BQUAA4IBAQB8itEfGDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkF
852
+ Zu90821fnZmv9ov761KyBZiibyrFVL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAb
853
+ LjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft3OJvx8Fi8eNy1gTIdGcL+oir
854
+ oQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43gKd8hdIaC2y+C
855
+ MMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds
856
+ sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7
857
+ -----END CERTIFICATE-----
858
+
859
+ # Issuer: CN=America Online Root Certification Authority 2 O=America Online Inc.
860
+ # Subject: CN=America Online Root Certification Authority 2 O=America Online Inc.
861
+ # Label: "America Online Root Certification Authority 2"
862
+ # Serial: 1
863
+ # MD5 Fingerprint: d6:ed:3c:ca:e2:66:0f:af:10:43:0d:77:9b:04:09:bf
864
+ # SHA1 Fingerprint: 85:b5:ff:67:9b:0c:79:96:1f:c8:6e:44:22:00:46:13:db:17:92:84
865
+ # SHA256 Fingerprint: 7d:3b:46:5a:60:14:e5:26:c0:af:fc:ee:21:27:d2:31:17:27:ad:81:1c:26:84:2d:00:6a:f3:73:06:cc:80:bd
866
+ -----BEGIN CERTIFICATE-----
867
+ MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc
868
+ MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP
869
+ bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2
870
+ MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft
871
+ ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg
872
+ Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP
873
+ ADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC
874
+ 206B89enfHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFci
875
+ KtZHgVdEglZTvYYUAQv8f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2
876
+ JxhP7JsowtS013wMPgwr38oE18aO6lhOqKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9
877
+ BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JNRvCAOVIyD+OEsnpD8l7e
878
+ Xz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0gBe4lL8B
879
+ PeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67
880
+ Xnfn6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEq
881
+ Z8A9W6Wa6897GqidFEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZ
882
+ o2C7HK2JNDJiuEMhBnIMoVxtRsX6Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3
883
+ +L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnjB453cMor9H124HhnAgMBAAGj
884
+ YzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3OpaaEg5+31IqEj
885
+ FNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE
886
+ AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmn
887
+ xPBUlgtk87FYT15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2
888
+ LHo1YGwRgJfMqZJS5ivmae2p+DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzccc
889
+ obGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXgJXUjhx5c3LqdsKyzadsXg8n33gy8
890
+ CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//ZoyzH1kUQ7rVyZ2OuMe
891
+ IjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgOZtMA
892
+ DjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2F
893
+ AjgQ5ANh1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUX
894
+ Om/9riW99XJZZLF0KjhfGEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPb
895
+ AZO1XB4Y3WRayhgoPmMEEf0cjQAPuDffZ4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQl
896
+ Zvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuPcX/9XhmgD0uRuMRUvAaw
897
+ RY8mkaKO/qk=
898
+ -----END CERTIFICATE-----
899
+
900
+ # Issuer: CN=AAA Certificate Services O=Comodo CA Limited
901
+ # Subject: CN=AAA Certificate Services O=Comodo CA Limited
902
+ # Label: "Comodo AAA Services root"
903
+ # Serial: 1
904
+ # MD5 Fingerprint: 49:79:04:b0:eb:87:19:ac:47:b0:bc:11:51:9b:74:d0
905
+ # SHA1 Fingerprint: d1:eb:23:a4:6d:17:d6:8f:d9:25:64:c2:f1:f1:60:17:64:d8:e3:49
906
+ # SHA256 Fingerprint: d7:a7:a0:fb:5d:7e:27:31:d7:71:e9:48:4e:bc:de:f7:1d:5f:0c:3e:0a:29:48:78:2b:c8:3e:e0:ea:69:9e:f4
907
+ -----BEGIN CERTIFICATE-----
908
+ MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb
909
+ MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
910
+ GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj
911
+ YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL
912
+ MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
913
+ BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM
914
+ GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP
915
+ ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua
916
+ BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe
917
+ 3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4
918
+ YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR
919
+ rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm
920
+ ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU
921
+ oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
922
+ MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v
923
+ QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t
924
+ b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF
925
+ AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q
926
+ GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz
927
+ Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2
928
+ G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi
929
+ l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3
930
+ smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==
931
+ -----END CERTIFICATE-----
932
+
933
+ # Issuer: CN=Secure Certificate Services O=Comodo CA Limited
934
+ # Subject: CN=Secure Certificate Services O=Comodo CA Limited
935
+ # Label: "Comodo Secure Services root"
936
+ # Serial: 1
937
+ # MD5 Fingerprint: d3:d9:bd:ae:9f:ac:67:24:b3:c8:1b:52:e1:b9:a9:bd
938
+ # SHA1 Fingerprint: 4a:65:d5:f4:1d:ef:39:b8:b8:90:4a:4a:d3:64:81:33:cf:c7:a1:d1
939
+ # SHA256 Fingerprint: bd:81:ce:3b:4f:65:91:d1:1a:67:b5:fc:7a:47:fd:ef:25:52:1b:f9:aa:4e:18:b9:e3:df:2e:34:a7:80:3b:e8
940
+ -----BEGIN CERTIFICATE-----
941
+ MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEb
942
+ MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
943
+ GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRp
944
+ ZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVow
945
+ fjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
946
+ A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAiBgNV
947
+ BAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEB
948
+ BQADggEPADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPM
949
+ cm3ye5drswfxdySRXyWP9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3S
950
+ HpR7LZQdqnXXs5jLrLxkU0C8j6ysNstcrbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996
951
+ CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rCoznl2yY4rYsK7hljxxwk
952
+ 3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3Vp6ea5EQz
953
+ 6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNV
954
+ HQ4EFgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1Ud
955
+ EwEB/wQFMAMBAf8wgYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2Rv
956
+ Y2EuY29tL1NlY3VyZUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRw
957
+ Oi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmww
958
+ DQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm4J4oqF7Tt/Q0
959
+ 5qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj
960
+ Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtI
961
+ gKvcnDe4IRRLDXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJ
962
+ aD61JlfutuC23bkpgHl9j6PwpCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDl
963
+ izeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1HRR3B7Hzs/Sk=
964
+ -----END CERTIFICATE-----
965
+
966
+ # Issuer: CN=Trusted Certificate Services O=Comodo CA Limited
967
+ # Subject: CN=Trusted Certificate Services O=Comodo CA Limited
968
+ # Label: "Comodo Trusted Services root"
969
+ # Serial: 1
970
+ # MD5 Fingerprint: 91:1b:3f:6e:cd:9e:ab:ee:07:fe:1f:71:d2:b3:61:27
971
+ # SHA1 Fingerprint: e1:9f:e3:0e:8b:84:60:9e:80:9b:17:0d:72:a8:c5:ba:6e:14:09:bd
972
+ # SHA256 Fingerprint: 3f:06:e5:56:81:d4:96:f5:be:16:9e:b5:38:9f:9f:2b:8f:f6:1e:17:08:df:68:81:72:48:49:cd:5d:27:cb:69
973
+ -----BEGIN CERTIFICATE-----
974
+ MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEb
975
+ MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
976
+ GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0
977
+ aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEwMDAwMDBaFw0yODEyMzEyMzU5NTla
978
+ MH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
979
+ BgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUwIwYD
980
+ VQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0B
981
+ AQEFAAOCAQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWW
982
+ fnJSoBVC21ndZHoa0Lh73TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMt
983
+ TGo87IvDktJTdyR0nAducPy9C1t2ul/y/9c3S0pgePfw+spwtOpZqqPOSC+pw7IL
984
+ fhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6juljatEPmsbS9Is6FARW
985
+ 1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsSivnkBbA7
986
+ kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0G
987
+ A1UdDgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYD
988
+ VR0TAQH/BAUwAwEB/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21v
989
+ ZG9jYS5jb20vVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRo
990
+ dHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMu
991
+ Y3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8NtwuleGFTQQuS9/
992
+ HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32
993
+ pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxIS
994
+ jBc/lDb+XbDABHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+
995
+ xqFx7D+gIIxmOom0jtTYsU0lR+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/Atyjcn
996
+ dBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O9y5Xt5hwXsjEeLBi
997
+ -----END CERTIFICATE-----
998
+
999
+ # Issuer: CN=UTN - DATACorp SGC O=The USERTRUST Network OU=http://www.usertrust.com
1000
+ # Subject: CN=UTN - DATACorp SGC O=The USERTRUST Network OU=http://www.usertrust.com
1001
+ # Label: "UTN DATACorp SGC Root CA"
1002
+ # Serial: 91374294542884689855167577680241077609
1003
+ # MD5 Fingerprint: b3:a5:3e:77:21:6d:ac:4a:c0:c9:fb:d5:41:3d:ca:06
1004
+ # SHA1 Fingerprint: 58:11:9f:0e:12:82:87:ea:50:fd:d9:87:45:6f:4f:78:dc:fa:d6:d4
1005
+ # SHA256 Fingerprint: 85:fb:2f:91:dd:12:27:5a:01:45:b6:36:53:4f:84:02:4a:d6:8b:69:b8:ee:88:68:4f:f7:11:37:58:05:b3:48
1006
+ -----BEGIN CERTIFICATE-----
1007
+ MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCB
1008
+ kzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
1009
+ Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
1010
+ dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZBgNVBAMTElVUTiAtIERBVEFDb3Jw
1011
+ IFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBaMIGTMQswCQYDVQQG
1012
+ EwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYD
1013
+ VQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cu
1014
+ dXNlcnRydXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjAN
1015
+ BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6
1016
+ E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ysraP6LnD43m77VkIVni5c7yPeIbkFdicZ
1017
+ D0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlowHDyUwDAXlCCpVZvNvlK
1018
+ 4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA9P4yPykq
1019
+ lXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulW
1020
+ bfXv33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQAB
1021
+ o4GrMIGoMAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRT
1022
+ MtGzz3/64PGgXYVOktKeRR20TzA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3Js
1023
+ LnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dDLmNybDAqBgNVHSUEIzAhBggr
1024
+ BgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3DQEBBQUAA4IB
1025
+ AQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft
1026
+ Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyj
1027
+ j98C5OBxOvG0I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVH
1028
+ KWss5nbZqSl9Mt3JNjy9rjXxEZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv
1029
+ 2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwPDPafepE39peC4N1xaf92P2BNPM/3
1030
+ mfnGV/TJVTl4uix5yaaIK/QI
1031
+ -----END CERTIFICATE-----
1032
+
1033
+ # Issuer: CN=UTN-USERFirst-Hardware O=The USERTRUST Network OU=http://www.usertrust.com
1034
+ # Subject: CN=UTN-USERFirst-Hardware O=The USERTRUST Network OU=http://www.usertrust.com
1035
+ # Label: "UTN USERFirst Hardware Root CA"
1036
+ # Serial: 91374294542884704022267039221184531197
1037
+ # MD5 Fingerprint: 4c:56:41:e5:0d:bb:2b:e8:ca:a3:ed:18:08:ad:43:39
1038
+ # SHA1 Fingerprint: 04:83:ed:33:99:ac:36:08:05:87:22:ed:bc:5e:46:00:e3:be:f9:d7
1039
+ # SHA256 Fingerprint: 6e:a5:47:41:d0:04:66:7e:ed:1b:48:16:63:4a:a3:a7:9e:6e:4b:96:95:0f:82:79:da:fc:8d:9b:d8:81:21:37
1040
+ -----BEGIN CERTIFICATE-----
1041
+ MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB
1042
+ lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
1043
+ Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
1044
+ dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt
1045
+ SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG
1046
+ A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe
1047
+ MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v
1048
+ d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh
1049
+ cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn
1050
+ 0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ
1051
+ M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a
1052
+ MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd
1053
+ oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI
1054
+ DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy
1055
+ oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD
1056
+ VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0
1057
+ dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy
1058
+ bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF
1059
+ BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM
1060
+ //bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli
1061
+ CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE
1062
+ CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t
1063
+ 3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS
1064
+ KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA==
1065
+ -----END CERTIFICATE-----
1066
+
1067
+ # Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com
1068
+ # Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com
1069
+ # Label: "XRamp Global CA Root"
1070
+ # Serial: 107108908803651509692980124233745014957
1071
+ # MD5 Fingerprint: a1:0b:44:b3:ca:10:d8:00:6e:9d:0f:d8:0f:92:0a:d1
1072
+ # SHA1 Fingerprint: b8:01:86:d1:eb:9c:86:a5:41:04:cf:30:54:f3:4c:52:b7:e5:58:c6
1073
+ # SHA256 Fingerprint: ce:cd:dc:90:50:99:d8:da:df:c5:b1:d2:09:b7:37:cb:e2:c1:8c:fb:2c:10:c0:ff:0b:cf:0d:32:86:fc:1a:a2
1074
+ -----BEGIN CERTIFICATE-----
1075
+ MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB
1076
+ gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk
1077
+ MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY
1078
+ UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx
1079
+ NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3
1080
+ dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy
1081
+ dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB
1082
+ dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6
1083
+ 38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP
1084
+ KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q
1085
+ DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4
1086
+ qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa
1087
+ JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi
1088
+ PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P
1089
+ BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs
1090
+ jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0
1091
+ eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD
1092
+ ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR
1093
+ vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt
1094
+ qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa
1095
+ IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy
1096
+ i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ
1097
+ O+7ETPTsJ3xCwnR8gooJybQDJbw=
1098
+ -----END CERTIFICATE-----
1099
+
1100
+ # Issuer: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority
1101
+ # Subject: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority
1102
+ # Label: "Go Daddy Class 2 CA"
1103
+ # Serial: 0
1104
+ # MD5 Fingerprint: 91:de:06:25:ab:da:fd:32:17:0c:bb:25:17:2a:84:67
1105
+ # SHA1 Fingerprint: 27:96:ba:e6:3f:18:01:e2:77:26:1b:a0:d7:77:70:02:8f:20:ee:e4
1106
+ # SHA256 Fingerprint: c3:84:6b:f2:4b:9e:93:ca:64:27:4c:0e:c6:7c:1e:cc:5e:02:4f:fc:ac:d2:d7:40:19:35:0e:81:fe:54:6a:e4
1107
+ -----BEGIN CERTIFICATE-----
1108
+ MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh
1109
+ MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE
1110
+ YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3
1111
+ MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo
1112
+ ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg
1113
+ MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN
1114
+ ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA
1115
+ PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w
1116
+ wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi
1117
+ EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY
1118
+ avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+
1119
+ YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE
1120
+ sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h
1121
+ /t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5
1122
+ IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj
1123
+ YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD
1124
+ ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy
1125
+ OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P
1126
+ TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
1127
+ HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER
1128
+ dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf
1129
+ ReYNnyicsbkqWletNw+vHX/bvZ8=
1130
+ -----END CERTIFICATE-----
1131
+
1132
+ # Issuer: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority
1133
+ # Subject: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority
1134
+ # Label: "Starfield Class 2 CA"
1135
+ # Serial: 0
1136
+ # MD5 Fingerprint: 32:4a:4b:bb:c8:63:69:9b:be:74:9a:c6:dd:1d:46:24
1137
+ # SHA1 Fingerprint: ad:7e:1c:28:b0:64:ef:8f:60:03:40:20:14:c3:d0:e3:37:0e:b5:8a
1138
+ # SHA256 Fingerprint: 14:65:fa:20:53:97:b8:76:fa:a6:f0:a9:95:8e:55:90:e4:0f:cc:7f:aa:4f:b7:c2:c8:67:75:21:fb:5f:b6:58
1139
+ -----BEGIN CERTIFICATE-----
1140
+ MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl
1141
+ MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp
1142
+ U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw
1143
+ NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE
1144
+ ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp
1145
+ ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3
1146
+ DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf
1147
+ 8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN
1148
+ +lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0
1149
+ X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa
1150
+ K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA
1151
+ 1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G
1152
+ A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR
1153
+ zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0
1154
+ YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD
1155
+ bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w
1156
+ DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3
1157
+ L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D
1158
+ eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
1159
+ xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp
1160
+ VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY
1161
+ WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q=
1162
+ -----END CERTIFICATE-----
1163
+
1164
+ # Issuer: CN=StartCom Certification Authority O=StartCom Ltd. OU=Secure Digital Certificate Signing
1165
+ # Subject: CN=StartCom Certification Authority O=StartCom Ltd. OU=Secure Digital Certificate Signing
1166
+ # Label: "StartCom Certification Authority"
1167
+ # Serial: 1
1168
+ # MD5 Fingerprint: 22:4d:8f:8a:fc:f7:35:c2:bb:57:34:90:7b:8b:22:16
1169
+ # SHA1 Fingerprint: 3e:2b:f7:f2:03:1b:96:f3:8c:e6:c4:d8:a8:5d:3e:2d:58:47:6a:0f
1170
+ # SHA256 Fingerprint: c7:66:a9:be:f2:d4:07:1c:86:3a:31:aa:49:20:e8:13:b2:d1:98:60:8c:b7:b7:cf:e2:11:43:b8:36:df:09:ea
1171
+ -----BEGIN CERTIFICATE-----
1172
+ MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW
1173
+ MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg
1174
+ Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh
1175
+ dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM2WhcNMzYwOTE3MTk0NjM2WjB9
1176
+ MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi
1177
+ U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh
1178
+ cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA
1179
+ A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk
1180
+ pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf
1181
+ OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C
1182
+ Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT
1183
+ Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi
1184
+ HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM
1185
+ Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w
1186
+ +2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+
1187
+ Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3
1188
+ Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B
1189
+ 26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID
1190
+ AQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE
1191
+ FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9j
1192
+ ZXJ0LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3Js
1193
+ LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFM
1194
+ BgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUHAgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0
1195
+ Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRwOi8vY2VydC5zdGFy
1196
+ dGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYgU3Rh
1197
+ cnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlh
1198
+ YmlsaXR5LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2Yg
1199
+ dGhlIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFp
1200
+ bGFibGUgYXQgaHR0cDovL2NlcnQuc3RhcnRjb20ub3JnL3BvbGljeS5wZGYwEQYJ
1201
+ YIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNT
1202
+ TCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOCAgEAFmyZ
1203
+ 9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8
1204
+ jhvh3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUW
1205
+ FjgKXlf2Ysd6AgXmvB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJz
1206
+ ewT4F+irsfMuXGRuczE6Eri8sxHkfY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1
1207
+ ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3fsNrarnDy0RLrHiQi+fHLB5L
1208
+ EUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZEoalHmdkrQYu
1209
+ L6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq
1210
+ yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuC
1211
+ O3NJo2pXh5Tl1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6V
1212
+ um0ABj6y6koQOdjQK/W/7HW/lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkySh
1213
+ NOsF/5oirpt9P/FlUQqmMGqz9IgcgA38corog14=
1214
+ -----END CERTIFICATE-----
1215
+
1216
+ # Issuer: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com
1217
+ # Subject: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com
1218
+ # Label: "DigiCert Assured ID Root CA"
1219
+ # Serial: 17154717934120587862167794914071425081
1220
+ # MD5 Fingerprint: 87:ce:0b:7b:2a:0e:49:00:e1:58:71:9b:37:a8:93:72
1221
+ # SHA1 Fingerprint: 05:63:b8:63:0d:62:d7:5a:bb:c8:ab:1e:4b:df:b5:a8:99:b2:4d:43
1222
+ # SHA256 Fingerprint: 3e:90:99:b5:01:5e:8f:48:6c:00:bc:ea:9d:11:1e:e7:21:fa:ba:35:5a:89:bc:f1:df:69:56:1e:3d:c6:32:5c
1223
+ -----BEGIN CERTIFICATE-----
1224
+ MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl
1225
+ MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
1226
+ d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
1227
+ b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG
1228
+ EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
1229
+ cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi
1230
+ MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c
1231
+ JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP
1232
+ mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+
1233
+ wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4
1234
+ VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/
1235
+ AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB
1236
+ AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
1237
+ BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun
1238
+ pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC
1239
+ dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf
1240
+ fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm
1241
+ NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx
1242
+ H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe
1243
+ +o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==
1244
+ -----END CERTIFICATE-----
1245
+
1246
+ # Issuer: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com
1247
+ # Subject: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com
1248
+ # Label: "DigiCert Global Root CA"
1249
+ # Serial: 10944719598952040374951832963794454346
1250
+ # MD5 Fingerprint: 79:e4:a9:84:0d:7d:3a:96:d7:c0:4f:e2:43:4c:89:2e
1251
+ # SHA1 Fingerprint: a8:98:5d:3a:65:e5:e5:c4:b2:d7:d6:6d:40:c6:dd:2f:b1:9c:54:36
1252
+ # SHA256 Fingerprint: 43:48:a0:e9:44:4c:78:cb:26:5e:05:8d:5e:89:44:b4:d8:4f:96:62:bd:26:db:25:7f:89:34:a4:43:c7:01:61
1253
+ -----BEGIN CERTIFICATE-----
1254
+ MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
1255
+ MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
1256
+ d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
1257
+ QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
1258
+ MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
1259
+ b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
1260
+ 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
1261
+ CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
1262
+ nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
1263
+ 43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
1264
+ T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
1265
+ gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
1266
+ BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
1267
+ TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
1268
+ DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
1269
+ hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
1270
+ 06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
1271
+ PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
1272
+ YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
1273
+ CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
1274
+ -----END CERTIFICATE-----
1275
+
1276
+ # Issuer: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com
1277
+ # Subject: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com
1278
+ # Label: "DigiCert High Assurance EV Root CA"
1279
+ # Serial: 3553400076410547919724730734378100087
1280
+ # MD5 Fingerprint: d4:74:de:57:5c:39:b2:d3:9c:85:83:c5:c0:65:49:8a
1281
+ # SHA1 Fingerprint: 5f:b7:ee:06:33:e2:59:db:ad:0c:4c:9a:e6:d3:8f:1a:61:c7:dc:25
1282
+ # SHA256 Fingerprint: 74:31:e5:f4:c3:c1:ce:46:90:77:4f:0b:61:e0:54:40:88:3b:a9:a0:1e:d0:0b:a6:ab:d7:80:6e:d3:b1:18:cf
1283
+ -----BEGIN CERTIFICATE-----
1284
+ MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
1285
+ MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
1286
+ d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
1287
+ ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
1288
+ MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
1289
+ LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
1290
+ RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
1291
+ +9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
1292
+ PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
1293
+ xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
1294
+ Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
1295
+ hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
1296
+ EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
1297
+ MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
1298
+ FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
1299
+ nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
1300
+ eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
1301
+ hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
1302
+ Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
1303
+ vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
1304
+ +OkuE6N36B9K
1305
+ -----END CERTIFICATE-----
1306
+
1307
+ # Issuer: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc.
1308
+ # Subject: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc.
1309
+ # Label: "GeoTrust Primary Certification Authority"
1310
+ # Serial: 32798226551256963324313806436981982369
1311
+ # MD5 Fingerprint: 02:26:c3:01:5e:08:30:37:43:a9:d0:7d:cf:37:e6:bf
1312
+ # SHA1 Fingerprint: 32:3c:11:8e:1b:f7:b8:b6:52:54:e2:e2:10:0d:d6:02:90:37:f0:96
1313
+ # SHA256 Fingerprint: 37:d5:10:06:c5:12:ea:ab:62:64:21:f1:ec:8c:92:01:3f:c5:f8:2a:e9:8e:e5:33:eb:46:19:b8:de:b4:d0:6c
1314
+ -----BEGIN CERTIFICATE-----
1315
+ MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY
1316
+ MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo
1317
+ R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx
1318
+ MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK
1319
+ Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp
1320
+ ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
1321
+ AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9
1322
+ AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA
1323
+ ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0
1324
+ 7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W
1325
+ kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI
1326
+ mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G
1327
+ A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ
1328
+ KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1
1329
+ 6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl
1330
+ 4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K
1331
+ oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj
1332
+ UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU
1333
+ AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk=
1334
+ -----END CERTIFICATE-----
1335
+
1336
+ # Issuer: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only
1337
+ # Subject: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only
1338
+ # Label: "thawte Primary Root CA"
1339
+ # Serial: 69529181992039203566298953787712940909
1340
+ # MD5 Fingerprint: 8c:ca:dc:0b:22:ce:f5:be:72:ac:41:1a:11:a8:d8:12
1341
+ # SHA1 Fingerprint: 91:c6:d6:ee:3e:8a:c8:63:84:e5:48:c2:99:29:5c:75:6c:81:7b:81
1342
+ # SHA256 Fingerprint: 8d:72:2f:81:a9:c1:13:c0:79:1d:f1:36:a2:96:6d:b2:6c:95:0a:97:1d:b4:6b:41:99:f4:ea:54:b7:8b:fb:9f
1343
+ -----BEGIN CERTIFICATE-----
1344
+ MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB
1345
+ qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
1346
+ Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
1347
+ MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV
1348
+ BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw
1349
+ NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j
1350
+ LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG
1351
+ A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
1352
+ IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG
1353
+ SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs
1354
+ W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta
1355
+ 3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk
1356
+ 6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6
1357
+ Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J
1358
+ NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA
1359
+ MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP
1360
+ r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU
1361
+ DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz
1362
+ YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX
1363
+ xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2
1364
+ /qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/
1365
+ LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7
1366
+ jVaMaA==
1367
+ -----END CERTIFICATE-----
1368
+
1369
+ # Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only
1370
+ # Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only
1371
+ # Label: "VeriSign Class 3 Public Primary Certification Authority - G5"
1372
+ # Serial: 33037644167568058970164719475676101450
1373
+ # MD5 Fingerprint: cb:17:e4:31:67:3e:e2:09:fe:45:57:93:f3:0a:fa:1c
1374
+ # SHA1 Fingerprint: 4e:b6:d5:78:49:9b:1c:cf:5f:58:1e:ad:56:be:3d:9b:67:44:a5:e5
1375
+ # SHA256 Fingerprint: 9a:cf:ab:7e:43:c8:d8:80:d0:6b:26:2a:94:de:ee:e4:b4:65:99:89:c3:d0:ca:f1:9b:af:64:05:e4:1a:b7:df
1376
+ -----BEGIN CERTIFICATE-----
1377
+ MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB
1378
+ yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
1379
+ ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
1380
+ U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
1381
+ ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
1382
+ aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL
1383
+ MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
1384
+ ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln
1385
+ biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp
1386
+ U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y
1387
+ aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1
1388
+ nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex
1389
+ t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz
1390
+ SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG
1391
+ BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+
1392
+ rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/
1393
+ NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E
1394
+ BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH
1395
+ BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy
1396
+ aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv
1397
+ MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE
1398
+ p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y
1399
+ 5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK
1400
+ WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ
1401
+ 4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N
1402
+ hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq
1403
+ -----END CERTIFICATE-----
1404
+
1405
+ # Issuer: CN=COMODO Certification Authority O=COMODO CA Limited
1406
+ # Subject: CN=COMODO Certification Authority O=COMODO CA Limited
1407
+ # Label: "COMODO Certification Authority"
1408
+ # Serial: 104350513648249232941998508985834464573
1409
+ # MD5 Fingerprint: 5c:48:dc:f7:42:72:ec:56:94:6d:1c:cc:71:35:80:75
1410
+ # SHA1 Fingerprint: 66:31:bf:9e:f7:4f:9e:b6:c9:d5:a6:0c:ba:6a:be:d1:f7:bd:ef:7b
1411
+ # SHA256 Fingerprint: 0c:2c:d6:3d:f7:80:6f:a3:99:ed:e8:09:11:6b:57:5b:f8:79:89:f0:65:18:f9:80:8c:86:05:03:17:8b:af:66
1412
+ -----BEGIN CERTIFICATE-----
1413
+ MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB
1414
+ gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
1415
+ A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV
1416
+ BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw
1417
+ MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl
1418
+ YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P
1419
+ RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0
1420
+ aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3
1421
+ UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI
1422
+ 2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8
1423
+ Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp
1424
+ +2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+
1425
+ DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O
1426
+ nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW
1427
+ /zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g
1428
+ PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u
1429
+ QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY
1430
+ SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv
1431
+ IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/
1432
+ RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4
1433
+ zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd
1434
+ BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB
1435
+ ZQ==
1436
+ -----END CERTIFICATE-----
1437
+
1438
+ # Issuer: CN=Network Solutions Certificate Authority O=Network Solutions L.L.C.
1439
+ # Subject: CN=Network Solutions Certificate Authority O=Network Solutions L.L.C.
1440
+ # Label: "Network Solutions Certificate Authority"
1441
+ # Serial: 116697915152937497490437556386812487904
1442
+ # MD5 Fingerprint: d3:f3:a6:16:c0:fa:6b:1d:59:b1:2d:96:4d:0e:11:2e
1443
+ # SHA1 Fingerprint: 74:f8:a3:c3:ef:e7:b3:90:06:4b:83:90:3c:21:64:60:20:e5:df:ce
1444
+ # SHA256 Fingerprint: 15:f0:ba:00:a3:ac:7a:f3:ac:88:4c:07:2b:10:11:a0:77:bd:77:c0:97:f4:01:64:b2:f8:59:8a:bd:83:86:0c
1445
+ -----BEGIN CERTIFICATE-----
1446
+ MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi
1447
+ MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu
1448
+ MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp
1449
+ dHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJV
1450
+ UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO
1451
+ ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG
1452
+ SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwz
1453
+ c7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPP
1454
+ OCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl
1455
+ mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnF
1456
+ BgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4
1457
+ qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcw
1458
+ gZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB
1459
+ BjAPBgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwu
1460
+ bmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3Jp
1461
+ dHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc8
1462
+ 6fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVRDuwduIj/
1463
+ h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH
1464
+ /nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv
1465
+ wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN
1466
+ pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey
1467
+ -----END CERTIFICATE-----
1468
+
1469
+ # Issuer: CN=COMODO ECC Certification Authority O=COMODO CA Limited
1470
+ # Subject: CN=COMODO ECC Certification Authority O=COMODO CA Limited
1471
+ # Label: "COMODO ECC Certification Authority"
1472
+ # Serial: 41578283867086692638256921589707938090
1473
+ # MD5 Fingerprint: 7c:62:ff:74:9d:31:53:5e:68:4a:d5:78:aa:1e:bf:23
1474
+ # SHA1 Fingerprint: 9f:74:4e:9f:2b:4d:ba:ec:0f:31:2c:50:b6:56:3b:8e:2d:93:c3:11
1475
+ # SHA256 Fingerprint: 17:93:92:7a:06:14:54:97:89:ad:ce:2f:8f:34:f7:f0:b6:6d:0f:3a:e3:a3:b8:4d:21:ec:15:db:ba:4f:ad:c7
1476
+ -----BEGIN CERTIFICATE-----
1477
+ MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL
1478
+ MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
1479
+ BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT
1480
+ IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw
1481
+ MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy
1482
+ ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N
1483
+ T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv
1484
+ biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR
1485
+ FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J
1486
+ cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW
1487
+ BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
1488
+ BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm
1489
+ fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv
1490
+ GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=
1491
+ -----END CERTIFICATE-----
1492
+
1493
+ # Issuer: CN=TC TrustCenter Class 2 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 2 CA
1494
+ # Subject: CN=TC TrustCenter Class 2 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 2 CA
1495
+ # Label: "TC TrustCenter Class 2 CA II"
1496
+ # Serial: 941389028203453866782103406992443
1497
+ # MD5 Fingerprint: ce:78:33:5c:59:78:01:6e:18:ea:b9:36:a0:b9:2e:23
1498
+ # SHA1 Fingerprint: ae:50:83:ed:7c:f4:5c:bc:8f:61:c6:21:fe:68:5d:79:42:21:15:6e
1499
+ # SHA256 Fingerprint: e6:b8:f8:76:64:85:f8:07:ae:7f:8d:ac:16:70:46:1f:07:c0:a1:3e:ef:3a:1f:f7:17:53:8d:7a:ba:d3:91:b4
1500
+ -----BEGIN CERTIFICATE-----
1501
+ MIIEqjCCA5KgAwIBAgIOLmoAAQACH9dSISwRXDswDQYJKoZIhvcNAQEFBQAwdjEL
1502
+ MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV
1503
+ BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDIgQ0ExJTAjBgNVBAMTHFRDIFRydXN0
1504
+ Q2VudGVyIENsYXNzIDIgQ0EgSUkwHhcNMDYwMTEyMTQzODQzWhcNMjUxMjMxMjI1
1505
+ OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i
1506
+ SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQTElMCMGA1UEAxMc
1507
+ VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD
1508
+ ggEPADCCAQoCggEBAKuAh5uO8MN8h9foJIIRszzdQ2Lu+MNF2ujhoF/RKrLqk2jf
1509
+ tMjWQ+nEdVl//OEd+DFwIxuInie5e/060smp6RQvkL4DUsFJzfb95AhmC1eKokKg
1510
+ uNV/aVyQMrKXDcpK3EY+AlWJU+MaWss2xgdW94zPEfRMuzBwBJWl9jmM/XOBCH2J
1511
+ XjIeIqkiRUuwZi4wzJ9l/fzLganx4Duvo4bRierERXlQXa7pIXSSTYtZgo+U4+lK
1512
+ 8edJsBTj9WLL1XK9H7nSn6DNqPoByNkN39r8R52zyFTfSUrxIan+GE7uSNQZu+99
1513
+ 5OKdy1u2bv/jzVrndIIFuoAlOMvkaZ6vQaoahPUCAwEAAaOCATQwggEwMA8GA1Ud
1514
+ EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTjq1RMgKHbVkO3
1515
+ kUrL84J6E1wIqzCB7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRy
1516
+ dXN0Y2VudGVyLmRlL2NybC92Mi90Y19jbGFzc18yX2NhX0lJLmNybIaBn2xkYXA6
1517
+ Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBUcnVzdENlbnRlciUyMENsYXNz
1518
+ JTIwMiUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21iSCxPVT1yb290
1519
+ Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
1520
+ TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEAjNfffu4bgBCzg/XbEeprS6iS
1521
+ GNn3Bzn1LL4GdXpoUxUc6krtXvwjshOg0wn/9vYua0Fxec3ibf2uWWuFHbhOIprt
1522
+ ZjluS5TmVfwLG4t3wVMTZonZKNaL80VKY7f9ewthXbhtvsPcW3nS7Yblok2+XnR8
1523
+ au0WOB9/WIFaGusyiC2y8zl3gK9etmF1KdsjTYjKUCjLhdLTEKJZbtOTVAB6okaV
1524
+ hgWcqRmY5TFyDADiZ9lA4CQze28suVyrZZ0srHbqNZn1l7kPJOzHdiEoZa5X6AeI
1525
+ dUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfkvQ==
1526
+ -----END CERTIFICATE-----
1527
+
1528
+ # Issuer: CN=TC TrustCenter Class 3 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 3 CA
1529
+ # Subject: CN=TC TrustCenter Class 3 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 3 CA
1530
+ # Label: "TC TrustCenter Class 3 CA II"
1531
+ # Serial: 1506523511417715638772220530020799
1532
+ # MD5 Fingerprint: 56:5f:aa:80:61:12:17:f6:67:21:e6:2b:6d:61:56:8e
1533
+ # SHA1 Fingerprint: 80:25:ef:f4:6e:70:c8:d4:72:24:65:84:fe:40:3b:8a:8d:6a:db:f5
1534
+ # SHA256 Fingerprint: 8d:a0:84:fc:f9:9c:e0:77:22:f8:9b:32:05:93:98:06:fa:5c:b8:11:e1:c8:13:f6:a1:08:c7:d3:36:b3:40:8e
1535
+ -----BEGIN CERTIFICATE-----
1536
+ MIIEqjCCA5KgAwIBAgIOSkcAAQAC5aBd1j8AUb8wDQYJKoZIhvcNAQEFBQAwdjEL
1537
+ MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV
1538
+ BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDMgQ0ExJTAjBgNVBAMTHFRDIFRydXN0
1539
+ Q2VudGVyIENsYXNzIDMgQ0EgSUkwHhcNMDYwMTEyMTQ0MTU3WhcNMjUxMjMxMjI1
1540
+ OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i
1541
+ SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQTElMCMGA1UEAxMc
1542
+ VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD
1543
+ ggEPADCCAQoCggEBALTgu1G7OVyLBMVMeRwjhjEQY0NVJz/GRcekPewJDRoeIMJW
1544
+ Ht4bNwcwIi9v8Qbxq63WyKthoy9DxLCyLfzDlml7forkzMA5EpBCYMnMNWju2l+Q
1545
+ Vl/NHE1bWEnrDgFPZPosPIlY2C8u4rBo6SI7dYnWRBpl8huXJh0obazovVkdKyT2
1546
+ 1oQDZogkAHhg8fir/gKya/si+zXmFtGt9i4S5Po1auUZuV3bOx4a+9P/FRQI2Alq
1547
+ ukWdFHlgfa9Aigdzs5OW03Q0jTo3Kd5c7PXuLjHCINy+8U9/I1LZW+Jk2ZyqBwi1
1548
+ Rb3R0DHBq1SfqdLDYmAD8bs5SpJKPQq5ncWg/jcCAwEAAaOCATQwggEwMA8GA1Ud
1549
+ EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTUovyfs8PYA9NX
1550
+ XAek0CSnwPIA1DCB7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRy
1551
+ dXN0Y2VudGVyLmRlL2NybC92Mi90Y19jbGFzc18zX2NhX0lJLmNybIaBn2xkYXA6
1552
+ Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBUcnVzdENlbnRlciUyMENsYXNz
1553
+ JTIwMyUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21iSCxPVT1yb290
1554
+ Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
1555
+ TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEANmDkcPcGIEPZIxpC8vijsrlN
1556
+ irTzwppVMXzEO2eatN9NDoqTSheLG43KieHPOh6sHfGcMrSOWXaiQYUlN6AT0PV8
1557
+ TtXqluJucsG7Kv5sbviRmEb8yRtXW+rIGjs/sFGYPAfaLFkB2otE6OF0/ado3VS6
1558
+ g0bsyEa1+K+XwDsJHI/OcpY9M1ZwvJbL2NV9IJqDnxrcOfHFcqMRA/07QlIp2+gB
1559
+ 95tejNaNhk4Z+rwcvsUhpYeeeC422wlxo3I0+GzjBgnyXlal092Y+tTmBvTwtiBj
1560
+ S+opvaqCZh77gaqnN60TGOaSw4HBM7uIHqHn4rS9MWwOUT1v+5ZWgOI2F9Hc5A==
1561
+ -----END CERTIFICATE-----
1562
+
1563
+ # Issuer: CN=TC TrustCenter Universal CA I O=TC TrustCenter GmbH OU=TC TrustCenter Universal CA
1564
+ # Subject: CN=TC TrustCenter Universal CA I O=TC TrustCenter GmbH OU=TC TrustCenter Universal CA
1565
+ # Label: "TC TrustCenter Universal CA I"
1566
+ # Serial: 601024842042189035295619584734726
1567
+ # MD5 Fingerprint: 45:e1:a5:72:c5:a9:36:64:40:9e:f5:e4:58:84:67:8c
1568
+ # SHA1 Fingerprint: 6b:2f:34:ad:89:58:be:62:fd:b0:6b:5c:ce:bb:9d:d9:4f:4e:39:f3
1569
+ # SHA256 Fingerprint: eb:f3:c0:2a:87:89:b1:fb:7d:51:19:95:d6:63:b7:29:06:d9:13:ce:0d:5e:10:56:8a:8a:77:e2:58:61:67:e7
1570
+ -----BEGIN CERTIFICATE-----
1571
+ MIID3TCCAsWgAwIBAgIOHaIAAQAC7LdggHiNtgYwDQYJKoZIhvcNAQEFBQAweTEL
1572
+ MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNV
1573
+ BAsTG1RDIFRydXN0Q2VudGVyIFVuaXZlcnNhbCBDQTEmMCQGA1UEAxMdVEMgVHJ1
1574
+ c3RDZW50ZXIgVW5pdmVyc2FsIENBIEkwHhcNMDYwMzIyMTU1NDI4WhcNMjUxMjMx
1575
+ MjI1OTU5WjB5MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIg
1576
+ R21iSDEkMCIGA1UECxMbVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBMSYwJAYD
1577
+ VQQDEx1UQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0EgSTCCASIwDQYJKoZIhvcN
1578
+ AQEBBQADggEPADCCAQoCggEBAKR3I5ZEr5D0MacQ9CaHnPM42Q9e3s9B6DGtxnSR
1579
+ JJZ4Hgmgm5qVSkr1YnwCqMqs+1oEdjneX/H5s7/zA1hV0qq34wQi0fiU2iIIAI3T
1580
+ fCZdzHd55yx4Oagmcw6iXSVphU9VDprvxrlE4Vc93x9UIuVvZaozhDrzznq+VZeu
1581
+ jRIPFDPiUHDDSYcTvFHe15gSWu86gzOSBnWLknwSaHtwag+1m7Z3W0hZneTvWq3z
1582
+ wZ7U10VOylY0Ibw+F1tvdwxIAUMpsN0/lm7mlaoMwCC2/T42J5zjXM9OgdwZu5GQ
1583
+ fezmlwQek8wiSdeXhrYTCjxDI3d+8NzmzSQfO4ObNDqDNOMCAwEAAaNjMGEwHwYD
1584
+ VR0jBBgwFoAUkqR1LKSevoFE63n8isWVpesQdXMwDwYDVR0TAQH/BAUwAwEB/zAO
1585
+ BgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFJKkdSyknr6BROt5/IrFlaXrEHVzMA0G
1586
+ CSqGSIb3DQEBBQUAA4IBAQAo0uCG1eb4e/CX3CJrO5UUVg8RMKWaTzqwOuAGy2X1
1587
+ 7caXJ/4l8lfmXpWMPmRgFVp/Lw0BxbFg/UU1z/CyvwbZ71q+s2IhtNerNXxTPqYn
1588
+ 8aEt2hojnczd7Dwtnic0XQ/CNnm8yUpiLe1r2X1BQ3y2qsrtYbE3ghUJGooWMNjs
1589
+ ydZHcnhLEEYUjl8Or+zHL6sQ17bxbuyGssLoDZJz3KL0Dzq/YSMQiZxIQG5wALPT
1590
+ ujdEWBF6AmqI8Dc08BnprNRlc/ZpjGSUOnmFKbAWKwyCPwacx/0QK54PLLae4xW/
1591
+ 2TYcuiUaUj0a7CIMHOCkoj3w6DnPgcB77V0fb8XQC9eY
1592
+ -----END CERTIFICATE-----
1593
+
1594
+ # Issuer: CN=Cybertrust Global Root O=Cybertrust, Inc
1595
+ # Subject: CN=Cybertrust Global Root O=Cybertrust, Inc
1596
+ # Label: "Cybertrust Global Root"
1597
+ # Serial: 4835703278459682877484360
1598
+ # MD5 Fingerprint: 72:e4:4a:87:e3:69:40:80:77:ea:bc:e3:f4:ff:f0:e1
1599
+ # SHA1 Fingerprint: 5f:43:e5:b1:bf:f8:78:8c:ac:1c:c7:ca:4a:9a:c6:22:2b:cc:34:c6
1600
+ # SHA256 Fingerprint: 96:0a:df:00:63:e9:63:56:75:0c:29:65:dd:0a:08:67:da:0b:9c:bd:6e:77:71:4a:ea:fb:23:49:ab:39:3d:a3
1601
+ -----BEGIN CERTIFICATE-----
1602
+ MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYG
1603
+ A1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2Jh
1604
+ bCBSb290MB4XDTA2MTIxNTA4MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UE
1605
+ ChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBS
1606
+ b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+Mi8vRRQZhP/8NN5
1607
+ 7CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW0ozS
1608
+ J8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2y
1609
+ HLtgwEZLAfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iP
1610
+ t3sMpTjr3kfb1V05/Iin89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNz
1611
+ FtApD0mpSPCzqrdsxacwOUBdrsTiXSZT8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAY
1612
+ XSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/
1613
+ MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2MDSgMqAw
1614
+ hi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3Js
1615
+ MB8GA1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUA
1616
+ A4IBAQBW7wojoFROlZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMj
1617
+ Wqd8BfP9IjsO0QbE2zZMcwSO5bAi5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUx
1618
+ XOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2hO0j9n0Hq0V+09+zv+mKts2o
1619
+ omcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+TX3EJIrduPuoc
1620
+ A06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW
1621
+ WL1WMRJOEcgh4LMRkWXbtKaIOM5V
1622
+ -----END CERTIFICATE-----
1623
+
1624
+ # Issuer: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only
1625
+ # Subject: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only
1626
+ # Label: "GeoTrust Primary Certification Authority - G3"
1627
+ # Serial: 28809105769928564313984085209975885599
1628
+ # MD5 Fingerprint: b5:e8:34:36:c9:10:44:58:48:70:6d:2e:83:d4:b8:05
1629
+ # SHA1 Fingerprint: 03:9e:ed:b8:0b:e7:a0:3c:69:53:89:3b:20:d2:d9:32:3a:4c:2a:fd
1630
+ # SHA256 Fingerprint: b4:78:b8:12:25:0d:f8:78:63:5c:2a:a7:ec:7d:15:5e:aa:62:5e:e8:29:16:e2:cd:29:43:61:88:6c:d1:fb:d4
1631
+ -----BEGIN CERTIFICATE-----
1632
+ MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB
1633
+ mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT
1634
+ MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s
1635
+ eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv
1636
+ cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ
1637
+ BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg
1638
+ MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0
1639
+ BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
1640
+ LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz
1641
+ +uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm
1642
+ hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn
1643
+ 5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W
1644
+ JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL
1645
+ DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC
1646
+ huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
1647
+ HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB
1648
+ AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB
1649
+ zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN
1650
+ kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD
1651
+ AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH
1652
+ SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G
1653
+ spki4cErx5z481+oghLrGREt
1654
+ -----END CERTIFICATE-----
1655
+
1656
+ # Issuer: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only
1657
+ # Subject: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only
1658
+ # Label: "thawte Primary Root CA - G2"
1659
+ # Serial: 71758320672825410020661621085256472406
1660
+ # MD5 Fingerprint: 74:9d:ea:60:24:c4:fd:22:53:3e:cc:3a:72:d9:29:4f
1661
+ # SHA1 Fingerprint: aa:db:bc:22:23:8f:c4:01:a1:27:bb:38:dd:f4:1d:db:08:9e:f0:12
1662
+ # SHA256 Fingerprint: a4:31:0d:50:af:18:a6:44:71:90:37:2a:86:af:af:8b:95:1f:fb:43:1d:83:7f:1e:56:88:b4:59:71:ed:15:57
1663
+ -----BEGIN CERTIFICATE-----
1664
+ MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL
1665
+ MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp
1666
+ IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi
1667
+ BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw
1668
+ MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh
1669
+ d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig
1670
+ YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v
1671
+ dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/
1672
+ BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6
1673
+ papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E
1674
+ BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K
1675
+ DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3
1676
+ KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox
1677
+ XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg==
1678
+ -----END CERTIFICATE-----
1679
+
1680
+ # Issuer: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only
1681
+ # Subject: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only
1682
+ # Label: "thawte Primary Root CA - G3"
1683
+ # Serial: 127614157056681299805556476275995414779
1684
+ # MD5 Fingerprint: fb:1b:5d:43:8a:94:cd:44:c6:76:f2:43:4b:47:e7:31
1685
+ # SHA1 Fingerprint: f1:8b:53:8d:1b:e9:03:b6:a6:f0:56:43:5b:17:15:89:ca:f3:6b:f2
1686
+ # SHA256 Fingerprint: 4b:03:f4:58:07:ad:70:f2:1b:fc:2c:ae:71:c9:fd:e4:60:4c:06:4c:f5:ff:b6:86:ba:e5:db:aa:d7:fd:d3:4c
1687
+ -----BEGIN CERTIFICATE-----
1688
+ MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB
1689
+ rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
1690
+ Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
1691
+ MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV
1692
+ BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa
1693
+ Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl
1694
+ LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u
1695
+ MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl
1696
+ ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz
1697
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm
1698
+ gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8
1699
+ YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf
1700
+ b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9
1701
+ 9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S
1702
+ zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk
1703
+ OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV
1704
+ HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA
1705
+ 2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW
1706
+ oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu
1707
+ t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c
1708
+ KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM
1709
+ m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu
1710
+ MdRAGmI0Nj81Aa6sY6A=
1711
+ -----END CERTIFICATE-----
1712
+
1713
+ # Issuer: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only
1714
+ # Subject: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only
1715
+ # Label: "GeoTrust Primary Certification Authority - G2"
1716
+ # Serial: 80682863203381065782177908751794619243
1717
+ # MD5 Fingerprint: 01:5e:d8:6b:bd:6f:3d:8e:a1:31:f8:12:e0:98:73:6a
1718
+ # SHA1 Fingerprint: 8d:17:84:d5:37:f3:03:7d:ec:70:fe:57:8b:51:9a:99:e6:10:d7:b0
1719
+ # SHA256 Fingerprint: 5e:db:7a:c4:3b:82:a0:6a:87:61:e8:d7:be:49:79:eb:f2:61:1f:7d:d7:9b:f9:1c:1c:6b:56:6a:21:9e:d7:66
1720
+ -----BEGIN CERTIFICATE-----
1721
+ MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL
1722
+ MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj
1723
+ KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2
1724
+ MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0
1725
+ eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV
1726
+ BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw
1727
+ NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV
1728
+ BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH
1729
+ MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL
1730
+ So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal
1731
+ tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO
1732
+ BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG
1733
+ CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT
1734
+ qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz
1735
+ rD6ogRLQy7rQkgu2npaqBA+K
1736
+ -----END CERTIFICATE-----
1737
+
1738
+ # Issuer: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only
1739
+ # Subject: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only
1740
+ # Label: "VeriSign Universal Root Certification Authority"
1741
+ # Serial: 85209574734084581917763752644031726877
1742
+ # MD5 Fingerprint: 8e:ad:b5:01:aa:4d:81:e4:8c:1d:d1:e1:14:00:95:19
1743
+ # SHA1 Fingerprint: 36:79:ca:35:66:87:72:30:4d:30:a5:fb:87:3b:0f:a7:7b:b7:0d:54
1744
+ # SHA256 Fingerprint: 23:99:56:11:27:a5:71:25:de:8c:ef:ea:61:0d:df:2f:a0:78:b5:c8:06:7f:4e:82:82:90:bf:b8:60:e8:4b:3c
1745
+ -----BEGIN CERTIFICATE-----
1746
+ MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB
1747
+ vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
1748
+ ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp
1749
+ U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W
1750
+ ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
1751
+ Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX
1752
+ MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0
1753
+ IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y
1754
+ IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh
1755
+ bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF
1756
+ AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF
1757
+ 9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH
1758
+ H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H
1759
+ LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN
1760
+ /BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT
1761
+ rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud
1762
+ EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw
1763
+ WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs
1764
+ exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud
1765
+ DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4
1766
+ sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+
1767
+ seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz
1768
+ 4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+
1769
+ BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR
1770
+ lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3
1771
+ 7M2CYfE45k+XmCpajQ==
1772
+ -----END CERTIFICATE-----
1773
+
1774
+ # Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only
1775
+ # Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only
1776
+ # Label: "VeriSign Class 3 Public Primary Certification Authority - G4"
1777
+ # Serial: 63143484348153506665311985501458640051
1778
+ # MD5 Fingerprint: 3a:52:e1:e7:fd:6f:3a:e3:6f:f3:6f:99:1b:f9:22:41
1779
+ # SHA1 Fingerprint: 22:d5:d8:df:8f:02:31:d1:8d:f7:9d:b7:cf:8a:2d:64:c9:3f:6c:3a
1780
+ # SHA256 Fingerprint: 69:dd:d7:ea:90:bb:57:c9:3e:13:5d:c8:5e:a6:fc:d5:48:0b:60:32:39:bd:c4:54:fc:75:8b:2a:26:cf:7f:79
1781
+ -----BEGIN CERTIFICATE-----
1782
+ MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL
1783
+ MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
1784
+ ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln
1785
+ biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp
1786
+ U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y
1787
+ aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG
1788
+ A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp
1789
+ U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg
1790
+ SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln
1791
+ biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5
1792
+ IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm
1793
+ GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve
1794
+ fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw
1795
+ AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ
1796
+ aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj
1797
+ aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW
1798
+ kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC
1799
+ 4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga
1800
+ FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA==
1801
+ -----END CERTIFICATE-----
1802
+
1803
+ # Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority
1804
+ # Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority
1805
+ # Label: "Verisign Class 3 Public Primary Certification Authority"
1806
+ # Serial: 80507572722862485515306429940691309246
1807
+ # MD5 Fingerprint: ef:5a:f1:33:ef:f1:cd:bb:51:02:ee:12:14:4b:96:c4
1808
+ # SHA1 Fingerprint: a1:db:63:93:91:6f:17:e4:18:55:09:40:04:15:c7:02:40:b0:ae:6b
1809
+ # SHA256 Fingerprint: a4:b6:b3:99:6f:c2:f3:06:b3:fd:86:81:bd:63:41:3d:8c:50:09:cc:4f:a3:29:c2:cc:f0:e2:fa:1b:14:03:05
1810
+ -----BEGIN CERTIFICATE-----
1811
+ MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG
1812
+ A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
1813
+ cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
1814
+ MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
1815
+ BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
1816
+ YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
1817
+ ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
1818
+ BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
1819
+ I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
1820
+ CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i
1821
+ 2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ
1822
+ 2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ
1823
+ -----END CERTIFICATE-----
1824
+
1825
+ # Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3
1826
+ # Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3
1827
+ # Label: "GlobalSign Root CA - R3"
1828
+ # Serial: 4835703278459759426209954
1829
+ # MD5 Fingerprint: c5:df:b8:49:ca:05:13:55:ee:2d:ba:1a:c3:3e:b0:28
1830
+ # SHA1 Fingerprint: d6:9b:56:11:48:f0:1c:77:c5:45:78:c1:09:26:df:5b:85:69:76:ad
1831
+ # SHA256 Fingerprint: cb:b5:22:d7:b7:f1:27:ad:6a:01:13:86:5b:df:1c:d4:10:2e:7d:07:59:af:63:5a:7c:f4:72:0d:c9:63:c5:3b
1832
+ -----BEGIN CERTIFICATE-----
1833
+ MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G
1834
+ A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp
1835
+ Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4
1836
+ MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG
1837
+ A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
1838
+ hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8
1839
+ RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT
1840
+ gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm
1841
+ KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd
1842
+ QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn