UpdraftPlus WordPress Backup Plugin - Version 1.0.7

Version Description

  • 12/20/2012 =
  • Fixed bug that set 1Tb (instead of 1Mb) chunk sizes for Google Drive uploads
Download this release

Release Info

Developer DavidAnderson
Plugin Icon 128x128 UpdraftPlus WordPress Backup Plugin
Version 1.0.7
Comparing to
See all releases

Code changes from version 1.0.6 to 1.0.7

readme.txt CHANGED
@@ -3,7 +3,7 @@ Contributors: David Anderson
3
  Tags: backup, restore, database, cloud, amazon, s3, Amazon S3, google drive, google, gdrive, ftp, cloud, updraft, back up
4
  Requires at least: 3.2
5
  Tested up to: 3.5
6
- Stable tag: 1.0.5
7
  Donate link: http://david.dw-perspective.org.uk/donate
8
  License: GPLv3 or later
9
 
@@ -81,6 +81,9 @@ No, there's no warranty or guarantee, etc. It's completely up to you to verify t
81
 
82
  == Changelog ==
83
 
 
 
 
84
  = 1.0.5 - 12/13/2012 =
85
  * Tweaked default Google Drive options
86
 
3
  Tags: backup, restore, database, cloud, amazon, s3, Amazon S3, google drive, google, gdrive, ftp, cloud, updraft, back up
4
  Requires at least: 3.2
5
  Tested up to: 3.5
6
+ Stable tag: 1.0.7
7
  Donate link: http://david.dw-perspective.org.uk/donate
8
  License: GPLv3 or later
9
 
81
 
82
  == Changelog ==
83
 
84
+ = 1.0.7 - 12/20/2012 =
85
+ * Fixed bug that set 1Tb (instead of 1Mb) chunk sizes for Google Drive uploads
86
+
87
  = 1.0.5 - 12/13/2012 =
88
  * Tweaked default Google Drive options
89
 
trunk/example-decrypt.php ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+
5
+ To dump the decrypted file using the given key on stdout, call:
6
+
7
+ rijndael_decrypt_file( '../path/to/file.crypt' , 'mykey' );
8
+
9
+ Thus, here are the easy instructions:
10
+
11
+ 1) Add a line like the above into this PHP file (not inside these comments, but outside)
12
+ e.g.
13
+ rijndael_decrypt_file( '/home/myself/myfile.crypt' , 'MYKEY' );
14
+
15
+ 2) Run this file (and make sure that includes/Rijndael.php is available, if you are moving this file around)
16
+ e.g.
17
+ php /home/myself/example-decrypt.php >output.sql.gz
18
+
19
+ 3) You may then want to gunzip the resulting file to have a standard SQL file.
20
+ e.g.
21
+ gunzip output.sql.gz
22
+
23
+ */
24
+
25
+ function rijndael_decrypt_file($file, $key) {
26
+
27
+ require_once(dirname(__FILE__).'/includes/Rijndael.php');
28
+
29
+ $rijndael = new Crypt_Rijndael();
30
+ $rijndael->setKey($key);
31
+ $in_handle = fopen($file,'r');
32
+ $ciphertext = "";
33
+ while (!feof ($in_handle)) {
34
+ $ciphertext .= fread($in_handle, 16384);
35
+ }
36
+ fclose ($in_handle);
37
+ print $rijndael->decrypt($ciphertext);
38
+
39
+ }
40
+
41
+ ?>
trunk/includes/Rijndael.php ADDED
@@ -0,0 +1,1424 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3
+
4
+ /**
5
+ * Pure-PHP implementation of Rijndael.
6
+ *
7
+ * Does not use mcrypt, even when available, for reasons that are explained below.
8
+ *
9
+ * PHP versions 4 and 5
10
+ *
11
+ * If {@link Crypt_Rijndael::setBlockLength() setBlockLength()} isn't called, it'll be assumed to be 128 bits. If
12
+ * {@link Crypt_Rijndael::setKeyLength() setKeyLength()} isn't called, it'll be calculated from
13
+ * {@link Crypt_Rijndael::setKey() setKey()}. ie. if the key is 128-bits, the key length will be 128-bits. If it's
14
+ * 136-bits it'll be null-padded to 160-bits and 160 bits will be the key length until
15
+ * {@link Crypt_Rijndael::setKey() setKey()} is called, again, at which point, it'll be recalculated.
16
+ *
17
+ * Not all Rijndael implementations may support 160-bits or 224-bits as the block length / key length. mcrypt, for example,
18
+ * does not. AES, itself, only supports block lengths of 128 and key lengths of 128, 192, and 256.
19
+ * {@link http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=10 Rijndael-ammended.pdf#page=10} defines the
20
+ * algorithm for block lengths of 192 and 256 but not for block lengths / key lengths of 160 and 224. Indeed, 160 and 224
21
+ * are first defined as valid key / block lengths in
22
+ * {@link http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=44 Rijndael-ammended.pdf#page=44}:
23
+ * Extensions: Other block and Cipher Key lengths.
24
+ *
25
+ * {@internal The variable names are the same as those in
26
+ * {@link http://www.csrc.nist.gov/publications/fips/fips197/fips-197.pdf#page=10 fips-197.pdf#page=10}.}}
27
+ *
28
+ * Here's a short example of how to use this library:
29
+ * <code>
30
+ * <?php
31
+ * include('Crypt/Rijndael.php');
32
+ *
33
+ * $rijndael = new Crypt_Rijndael();
34
+ *
35
+ * $rijndael->setKey('abcdefghijklmnop');
36
+ *
37
+ * $size = 10 * 1024;
38
+ * $plaintext = '';
39
+ * for ($i = 0; $i < $size; $i++) {
40
+ * $plaintext.= 'a';
41
+ * }
42
+ *
43
+ * echo $rijndael->decrypt($rijndael->encrypt($plaintext));
44
+ * ?>
45
+ * </code>
46
+ *
47
+ * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy
48
+ * of this software and associated documentation files (the "Software"), to deal
49
+ * in the Software without restriction, including without limitation the rights
50
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
51
+ * copies of the Software, and to permit persons to whom the Software is
52
+ * furnished to do so, subject to the following conditions:
53
+ *
54
+ * The above copyright notice and this permission notice shall be included in
55
+ * all copies or substantial portions of the Software.
56
+ *
57
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
58
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
59
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
60
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
61
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
62
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
63
+ * THE SOFTWARE.
64
+ *
65
+ * @category Crypt
66
+ * @package Crypt_Rijndael
67
+ * @author Jim Wigginton <terrafrost@php.net>
68
+ * @copyright MMVIII Jim Wigginton
69
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
70
+ * @version $Id: Rijndael.php,v 1.12 2010/02/09 06:10:26 terrafrost Exp $
71
+ * @link http://phpseclib.sourceforge.net
72
+ */
73
+
74
+ /**#@+
75
+ * @access public
76
+ * @see Crypt_Rijndael::encrypt()
77
+ * @see Crypt_Rijndael::decrypt()
78
+ */
79
+ /**
80
+ * Encrypt / decrypt using the Counter mode.
81
+ *
82
+ * Set to -1 since that's what Crypt/Random.php uses to index the CTR mode.
83
+ *
84
+ * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Counter_.28CTR.29
85
+ */
86
+ define('CRYPT_RIJNDAEL_MODE_CTR', -1);
87
+ /**
88
+ * Encrypt / decrypt using the Electronic Code Book mode.
89
+ *
90
+ * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29
91
+ */
92
+ define('CRYPT_RIJNDAEL_MODE_ECB', 1);
93
+ /**
94
+ * Encrypt / decrypt using the Code Book Chaining mode.
95
+ *
96
+ * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29
97
+ */
98
+ define('CRYPT_RIJNDAEL_MODE_CBC', 2);
99
+ /**
100
+ * Encrypt / decrypt using the Cipher Feedback mode.
101
+ *
102
+ * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29
103
+ */
104
+ define('CRYPT_RIJNDAEL_MODE_CFB', 3);
105
+ /**
106
+ * Encrypt / decrypt using the Cipher Feedback mode.
107
+ *
108
+ * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Output_feedback_.28OFB.29
109
+ */
110
+ define('CRYPT_RIJNDAEL_MODE_OFB', 4);
111
+ /**#@-*/
112
+
113
+ /**#@+
114
+ * @access private
115
+ * @see Crypt_Rijndael::Crypt_Rijndael()
116
+ */
117
+ /**
118
+ * Toggles the internal implementation
119
+ */
120
+ define('CRYPT_RIJNDAEL_MODE_INTERNAL', 1);
121
+ /**
122
+ * Toggles the mcrypt implementation
123
+ */
124
+ define('CRYPT_RIJNDAEL_MODE_MCRYPT', 2);
125
+ /**#@-*/
126
+
127
+ /**
128
+ * Pure-PHP implementation of Rijndael.
129
+ *
130
+ * @author Jim Wigginton <terrafrost@php.net>
131
+ * @version 0.1.0
132
+ * @access public
133
+ * @package Crypt_Rijndael
134
+ */
135
+ class Crypt_Rijndael {
136
+ /**
137
+ * The Encryption Mode
138
+ *
139
+ * @see Crypt_Rijndael::Crypt_Rijndael()
140
+ * @var Integer
141
+ * @access private
142
+ */
143
+ var $mode;
144
+
145
+ /**
146
+ * The Key
147
+ *
148
+ * @see Crypt_Rijndael::setKey()
149
+ * @var String
150
+ * @access private
151
+ */
152
+ var $key = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
153
+
154
+ /**
155
+ * The Initialization Vector
156
+ *
157
+ * @see Crypt_Rijndael::setIV()
158
+ * @var String
159
+ * @access private
160
+ */
161
+ var $iv = '';
162
+
163
+ /**
164
+ * A "sliding" Initialization Vector
165
+ *
166
+ * @see Crypt_Rijndael::enableContinuousBuffer()
167
+ * @var String
168
+ * @access private
169
+ */
170
+ var $encryptIV = '';
171
+
172
+ /**
173
+ * A "sliding" Initialization Vector
174
+ *
175
+ * @see Crypt_Rijndael::enableContinuousBuffer()
176
+ * @var String
177
+ * @access private
178
+ */
179
+ var $decryptIV = '';
180
+
181
+ /**
182
+ * Continuous Buffer status
183
+ *
184
+ * @see Crypt_Rijndael::enableContinuousBuffer()
185
+ * @var Boolean
186
+ * @access private
187
+ */
188
+ var $continuousBuffer = false;
189
+
190
+ /**
191
+ * Padding status
192
+ *
193
+ * @see Crypt_Rijndael::enablePadding()
194
+ * @var Boolean
195
+ * @access private
196
+ */
197
+ var $padding = true;
198
+
199
+ /**
200
+ * Does the key schedule need to be (re)calculated?
201
+ *
202
+ * @see setKey()
203
+ * @see setBlockLength()
204
+ * @see setKeyLength()
205
+ * @var Boolean
206
+ * @access private
207
+ */
208
+ var $changed = true;
209
+
210
+ /**
211
+ * Has the key length explicitly been set or should it be derived from the key, itself?
212
+ *
213
+ * @see setKeyLength()
214
+ * @var Boolean
215
+ * @access private
216
+ */
217
+ var $explicit_key_length = false;
218
+
219
+ /**
220
+ * The Key Schedule
221
+ *
222
+ * @see _setup()
223
+ * @var Array
224
+ * @access private
225
+ */
226
+ var $w;
227
+
228
+ /**
229
+ * The Inverse Key Schedule
230
+ *
231
+ * @see _setup()
232
+ * @var Array
233
+ * @access private
234
+ */
235
+ var $dw;
236
+
237
+ /**
238
+ * The Block Length
239
+ *
240
+ * @see setBlockLength()
241
+ * @var Integer
242
+ * @access private
243
+ * @internal The max value is 32, the min value is 16. All valid values are multiples of 4. Exists in conjunction with
244
+ * $Nb because we need this value and not $Nb to pad strings appropriately.
245
+ */
246
+ var $block_size = 16;
247
+
248
+ /**
249
+ * The Block Length divided by 32
250
+ *
251
+ * @see setBlockLength()
252
+ * @var Integer
253
+ * @access private
254
+ * @internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4. Exists in conjunction with $block_size
255
+ * because the encryption / decryption / key schedule creation requires this number and not $block_size. We could
256
+ * derive this from $block_size or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu
257
+ * of that, we'll just precompute it once.
258
+ *
259
+ */
260
+ var $Nb = 4;
261
+
262
+ /**
263
+ * The Key Length
264
+ *
265
+ * @see setKeyLength()
266
+ * @var Integer
267
+ * @access private
268
+ * @internal The max value is 256 / 8 = 32, the min value is 128 / 8 = 16. Exists in conjunction with $key_size
269
+ * because the encryption / decryption / key schedule creation requires this number and not $key_size. We could
270
+ * derive this from $key_size or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu
271
+ * of that, we'll just precompute it once.
272
+ */
273
+ var $key_size = 16;
274
+
275
+ /**
276
+ * The Key Length divided by 32
277
+ *
278
+ * @see setKeyLength()
279
+ * @var Integer
280
+ * @access private
281
+ * @internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4
282
+ */
283
+ var $Nk = 4;
284
+
285
+ /**
286
+ * The Number of Rounds
287
+ *
288
+ * @var Integer
289
+ * @access private
290
+ * @internal The max value is 14, the min value is 10.
291
+ */
292
+ var $Nr;
293
+
294
+ /**
295
+ * Shift offsets
296
+ *
297
+ * @var Array
298
+ * @access private
299
+ */
300
+ var $c;
301
+
302
+ /**
303
+ * Precomputed mixColumns table
304
+ *
305
+ * @see Crypt_Rijndael()
306
+ * @var Array
307
+ * @access private
308
+ */
309
+ var $t0;
310
+
311
+ /**
312
+ * Precomputed mixColumns table
313
+ *
314
+ * @see Crypt_Rijndael()
315
+ * @var Array
316
+ * @access private
317
+ */
318
+ var $t1;
319
+
320
+ /**
321
+ * Precomputed mixColumns table
322
+ *
323
+ * @see Crypt_Rijndael()
324
+ * @var Array
325
+ * @access private
326
+ */
327
+ var $t2;
328
+
329
+ /**
330
+ * Precomputed mixColumns table
331
+ *
332
+ * @see Crypt_Rijndael()
333
+ * @var Array
334
+ * @access private
335
+ */
336
+ var $t3;
337
+
338
+ /**
339
+ * Precomputed invMixColumns table
340
+ *
341
+ * @see Crypt_Rijndael()
342
+ * @var Array
343
+ * @access private
344
+ */
345
+ var $dt0;
346
+
347
+ /**
348
+ * Precomputed invMixColumns table
349
+ *
350
+ * @see Crypt_Rijndael()
351
+ * @var Array
352
+ * @access private
353
+ */
354
+ var $dt1;
355
+
356
+ /**
357
+ * Precomputed invMixColumns table
358
+ *
359
+ * @see Crypt_Rijndael()
360
+ * @var Array
361
+ * @access private
362
+ */
363
+ var $dt2;
364
+
365
+ /**
366
+ * Precomputed invMixColumns table
367
+ *
368
+ * @see Crypt_Rijndael()
369
+ * @var Array
370
+ * @access private
371
+ */
372
+ var $dt3;
373
+
374
+ /**
375
+ * Is the mode one that is paddable?
376
+ *
377
+ * @see Crypt_Rijndael::Crypt_Rijndael()
378
+ * @var Boolean
379
+ * @access private
380
+ */
381
+ var $paddable = false;
382
+
383
+ /**
384
+ * Encryption buffer for CTR, OFB and CFB modes
385
+ *
386
+ * @see Crypt_Rijndael::encrypt()
387
+ * @var String
388
+ * @access private
389
+ */
390
+ var $enbuffer = array('encrypted' => '', 'xor' => '');
391
+
392
+ /**
393
+ * Decryption buffer for CTR, OFB and CFB modes
394
+ *
395
+ * @see Crypt_Rijndael::decrypt()
396
+ * @var String
397
+ * @access private
398
+ */
399
+ var $debuffer = array('ciphertext' => '');
400
+
401
+ /**
402
+ * Default Constructor.
403
+ *
404
+ * Determines whether or not the mcrypt extension should be used. $mode should only, at present, be
405
+ * CRYPT_RIJNDAEL_MODE_ECB or CRYPT_RIJNDAEL_MODE_CBC. If not explictly set, CRYPT_RIJNDAEL_MODE_CBC will be used.
406
+ *
407
+ * @param optional Integer $mode
408
+ * @return Crypt_Rijndael
409
+ * @access public
410
+ */
411
+ function Crypt_Rijndael($mode = CRYPT_RIJNDAEL_MODE_CBC)
412
+ {
413
+ switch ($mode) {
414
+ case CRYPT_RIJNDAEL_MODE_ECB:
415
+ case CRYPT_RIJNDAEL_MODE_CBC:
416
+ $this->paddable = true;
417
+ $this->mode = $mode;
418
+ break;
419
+ case CRYPT_RIJNDAEL_MODE_CTR:
420
+ case CRYPT_RIJNDAEL_MODE_CFB:
421
+ case CRYPT_RIJNDAEL_MODE_OFB:
422
+ $this->mode = $mode;
423
+ break;
424
+ default:
425
+ $this->paddable = true;
426
+ $this->mode = CRYPT_RIJNDAEL_MODE_CBC;
427
+ }
428
+
429
+ $t3 = &$this->t3;
430
+ $t2 = &$this->t2;
431
+ $t1 = &$this->t1;
432
+ $t0 = &$this->t0;
433
+
434
+ $dt3 = &$this->dt3;
435
+ $dt2 = &$this->dt2;
436
+ $dt1 = &$this->dt1;
437
+ $dt0 = &$this->dt0;
438
+
439
+ // according to <http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=19> (section 5.2.1),
440
+ // precomputed tables can be used in the mixColumns phase. in that example, they're assigned t0...t3, so
441
+ // those are the names we'll use.
442
+ $t3 = array(
443
+ 0x6363A5C6, 0x7C7C84F8, 0x777799EE, 0x7B7B8DF6, 0xF2F20DFF, 0x6B6BBDD6, 0x6F6FB1DE, 0xC5C55491,
444
+ 0x30305060, 0x01010302, 0x6767A9CE, 0x2B2B7D56, 0xFEFE19E7, 0xD7D762B5, 0xABABE64D, 0x76769AEC,
445
+ 0xCACA458F, 0x82829D1F, 0xC9C94089, 0x7D7D87FA, 0xFAFA15EF, 0x5959EBB2, 0x4747C98E, 0xF0F00BFB,
446
+ 0xADADEC41, 0xD4D467B3, 0xA2A2FD5F, 0xAFAFEA45, 0x9C9CBF23, 0xA4A4F753, 0x727296E4, 0xC0C05B9B,
447
+ 0xB7B7C275, 0xFDFD1CE1, 0x9393AE3D, 0x26266A4C, 0x36365A6C, 0x3F3F417E, 0xF7F702F5, 0xCCCC4F83,
448
+ 0x34345C68, 0xA5A5F451, 0xE5E534D1, 0xF1F108F9, 0x717193E2, 0xD8D873AB, 0x31315362, 0x15153F2A,
449
+ 0x04040C08, 0xC7C75295, 0x23236546, 0xC3C35E9D, 0x18182830, 0x9696A137, 0x05050F0A, 0x9A9AB52F,
450
+ 0x0707090E, 0x12123624, 0x80809B1B, 0xE2E23DDF, 0xEBEB26CD, 0x2727694E, 0xB2B2CD7F, 0x75759FEA,
451
+ 0x09091B12, 0x83839E1D, 0x2C2C7458, 0x1A1A2E34, 0x1B1B2D36, 0x6E6EB2DC, 0x5A5AEEB4, 0xA0A0FB5B,
452
+ 0x5252F6A4, 0x3B3B4D76, 0xD6D661B7, 0xB3B3CE7D, 0x29297B52, 0xE3E33EDD, 0x2F2F715E, 0x84849713,
453
+ 0x5353F5A6, 0xD1D168B9, 0x00000000, 0xEDED2CC1, 0x20206040, 0xFCFC1FE3, 0xB1B1C879, 0x5B5BEDB6,
454
+ 0x6A6ABED4, 0xCBCB468D, 0xBEBED967, 0x39394B72, 0x4A4ADE94, 0x4C4CD498, 0x5858E8B0, 0xCFCF4A85,
455
+ 0xD0D06BBB, 0xEFEF2AC5, 0xAAAAE54F, 0xFBFB16ED, 0x4343C586, 0x4D4DD79A, 0x33335566, 0x85859411,
456
+ 0x4545CF8A, 0xF9F910E9, 0x02020604, 0x7F7F81FE, 0x5050F0A0, 0x3C3C4478, 0x9F9FBA25, 0xA8A8E34B,
457
+ 0x5151F3A2, 0xA3A3FE5D, 0x4040C080, 0x8F8F8A05, 0x9292AD3F, 0x9D9DBC21, 0x38384870, 0xF5F504F1,
458
+ 0xBCBCDF63, 0xB6B6C177, 0xDADA75AF, 0x21216342, 0x10103020, 0xFFFF1AE5, 0xF3F30EFD, 0xD2D26DBF,
459
+ 0xCDCD4C81, 0x0C0C1418, 0x13133526, 0xECEC2FC3, 0x5F5FE1BE, 0x9797A235, 0x4444CC88, 0x1717392E,
460
+ 0xC4C45793, 0xA7A7F255, 0x7E7E82FC, 0x3D3D477A, 0x6464ACC8, 0x5D5DE7BA, 0x19192B32, 0x737395E6,
461
+ 0x6060A0C0, 0x81819819, 0x4F4FD19E, 0xDCDC7FA3, 0x22226644, 0x2A2A7E54, 0x9090AB3B, 0x8888830B,
462
+ 0x4646CA8C, 0xEEEE29C7, 0xB8B8D36B, 0x14143C28, 0xDEDE79A7, 0x5E5EE2BC, 0x0B0B1D16, 0xDBDB76AD,
463
+ 0xE0E03BDB, 0x32325664, 0x3A3A4E74, 0x0A0A1E14, 0x4949DB92, 0x06060A0C, 0x24246C48, 0x5C5CE4B8,
464
+ 0xC2C25D9F, 0xD3D36EBD, 0xACACEF43, 0x6262A6C4, 0x9191A839, 0x9595A431, 0xE4E437D3, 0x79798BF2,
465
+ 0xE7E732D5, 0xC8C8438B, 0x3737596E, 0x6D6DB7DA, 0x8D8D8C01, 0xD5D564B1, 0x4E4ED29C, 0xA9A9E049,
466
+ 0x6C6CB4D8, 0x5656FAAC, 0xF4F407F3, 0xEAEA25CF, 0x6565AFCA, 0x7A7A8EF4, 0xAEAEE947, 0x08081810,
467
+ 0xBABAD56F, 0x787888F0, 0x25256F4A, 0x2E2E725C, 0x1C1C2438, 0xA6A6F157, 0xB4B4C773, 0xC6C65197,
468
+ 0xE8E823CB, 0xDDDD7CA1, 0x74749CE8, 0x1F1F213E, 0x4B4BDD96, 0xBDBDDC61, 0x8B8B860D, 0x8A8A850F,
469
+ 0x707090E0, 0x3E3E427C, 0xB5B5C471, 0x6666AACC, 0x4848D890, 0x03030506, 0xF6F601F7, 0x0E0E121C,
470
+ 0x6161A3C2, 0x35355F6A, 0x5757F9AE, 0xB9B9D069, 0x86869117, 0xC1C15899, 0x1D1D273A, 0x9E9EB927,
471
+ 0xE1E138D9, 0xF8F813EB, 0x9898B32B, 0x11113322, 0x6969BBD2, 0xD9D970A9, 0x8E8E8907, 0x9494A733,
472
+ 0x9B9BB62D, 0x1E1E223C, 0x87879215, 0xE9E920C9, 0xCECE4987, 0x5555FFAA, 0x28287850, 0xDFDF7AA5,
473
+ 0x8C8C8F03, 0xA1A1F859, 0x89898009, 0x0D0D171A, 0xBFBFDA65, 0xE6E631D7, 0x4242C684, 0x6868B8D0,
474
+ 0x4141C382, 0x9999B029, 0x2D2D775A, 0x0F0F111E, 0xB0B0CB7B, 0x5454FCA8, 0xBBBBD66D, 0x16163A2C
475
+ );
476
+
477
+ $dt3 = array(
478
+ 0xF4A75051, 0x4165537E, 0x17A4C31A, 0x275E963A, 0xAB6BCB3B, 0x9D45F11F, 0xFA58ABAC, 0xE303934B,
479
+ 0x30FA5520, 0x766DF6AD, 0xCC769188, 0x024C25F5, 0xE5D7FC4F, 0x2ACBD7C5, 0x35448026, 0x62A38FB5,
480
+ 0xB15A49DE, 0xBA1B6725, 0xEA0E9845, 0xFEC0E15D, 0x2F7502C3, 0x4CF01281, 0x4697A38D, 0xD3F9C66B,
481
+ 0x8F5FE703, 0x929C9515, 0x6D7AEBBF, 0x5259DA95, 0xBE832DD4, 0x7421D358, 0xE0692949, 0xC9C8448E,
482
+ 0xC2896A75, 0x8E7978F4, 0x583E6B99, 0xB971DD27, 0xE14FB6BE, 0x88AD17F0, 0x20AC66C9, 0xCE3AB47D,
483
+ 0xDF4A1863, 0x1A3182E5, 0x51336097, 0x537F4562, 0x6477E0B1, 0x6BAE84BB, 0x81A01CFE, 0x082B94F9,
484
+ 0x48685870, 0x45FD198F, 0xDE6C8794, 0x7BF8B752, 0x73D323AB, 0x4B02E272, 0x1F8F57E3, 0x55AB2A66,
485
+ 0xEB2807B2, 0xB5C2032F, 0xC57B9A86, 0x3708A5D3, 0x2887F230, 0xBFA5B223, 0x036ABA02, 0x16825CED,
486
+ 0xCF1C2B8A, 0x79B492A7, 0x07F2F0F3, 0x69E2A14E, 0xDAF4CD65, 0x05BED506, 0x34621FD1, 0xA6FE8AC4,
487
+ 0x2E539D34, 0xF355A0A2, 0x8AE13205, 0xF6EB75A4, 0x83EC390B, 0x60EFAA40, 0x719F065E, 0x6E1051BD,
488
+ 0x218AF93E, 0xDD063D96, 0x3E05AEDD, 0xE6BD464D, 0x548DB591, 0xC45D0571, 0x06D46F04, 0x5015FF60,
489
+ 0x98FB2419, 0xBDE997D6, 0x4043CC89, 0xD99E7767, 0xE842BDB0, 0x898B8807, 0x195B38E7, 0xC8EEDB79,
490
+ 0x7C0A47A1, 0x420FE97C, 0x841EC9F8, 0x00000000, 0x80868309, 0x2BED4832, 0x1170AC1E, 0x5A724E6C,
491
+ 0x0EFFFBFD, 0x8538560F, 0xAED51E3D, 0x2D392736, 0x0FD9640A, 0x5CA62168, 0x5B54D19B, 0x362E3A24,
492
+ 0x0A67B10C, 0x57E70F93, 0xEE96D2B4, 0x9B919E1B, 0xC0C54F80, 0xDC20A261, 0x774B695A, 0x121A161C,
493
+ 0x93BA0AE2, 0xA02AE5C0, 0x22E0433C, 0x1B171D12, 0x090D0B0E, 0x8BC7ADF2, 0xB6A8B92D, 0x1EA9C814,
494
+ 0xF1198557, 0x75074CAF, 0x99DDBBEE, 0x7F60FDA3, 0x01269FF7, 0x72F5BC5C, 0x663BC544, 0xFB7E345B,
495
+ 0x4329768B, 0x23C6DCCB, 0xEDFC68B6, 0xE4F163B8, 0x31DCCAD7, 0x63851042, 0x97224013, 0xC6112084,
496
+ 0x4A247D85, 0xBB3DF8D2, 0xF93211AE, 0x29A16DC7, 0x9E2F4B1D, 0xB230F3DC, 0x8652EC0D, 0xC1E3D077,
497
+ 0xB3166C2B, 0x70B999A9, 0x9448FA11, 0xE9642247, 0xFC8CC4A8, 0xF03F1AA0, 0x7D2CD856, 0x3390EF22,
498
+ 0x494EC787, 0x38D1C1D9, 0xCAA2FE8C, 0xD40B3698, 0xF581CFA6, 0x7ADE28A5, 0xB78E26DA, 0xADBFA43F,
499
+ 0x3A9DE42C, 0x78920D50, 0x5FCC9B6A, 0x7E466254, 0x8D13C2F6, 0xD8B8E890, 0x39F75E2E, 0xC3AFF582,
500
+ 0x5D80BE9F, 0xD0937C69, 0xD52DA96F, 0x2512B3CF, 0xAC993BC8, 0x187DA710, 0x9C636EE8, 0x3BBB7BDB,
501
+ 0x267809CD, 0x5918F46E, 0x9AB701EC, 0x4F9AA883, 0x956E65E6, 0xFFE67EAA, 0xBCCF0821, 0x15E8E6EF,
502
+ 0xE79BD9BA, 0x6F36CE4A, 0x9F09D4EA, 0xB07CD629, 0xA4B2AF31, 0x3F23312A, 0xA59430C6, 0xA266C035,
503
+ 0x4EBC3774, 0x82CAA6FC, 0x90D0B0E0, 0xA7D81533, 0x04984AF1, 0xECDAF741, 0xCD500E7F, 0x91F62F17,
504
+ 0x4DD68D76, 0xEFB04D43, 0xAA4D54CC, 0x9604DFE4, 0xD1B5E39E, 0x6A881B4C, 0x2C1FB8C1, 0x65517F46,
505
+ 0x5EEA049D, 0x8C355D01, 0x877473FA, 0x0B412EFB, 0x671D5AB3, 0xDBD25292, 0x105633E9, 0xD647136D,
506
+ 0xD7618C9A, 0xA10C7A37, 0xF8148E59, 0x133C89EB, 0xA927EECE, 0x61C935B7, 0x1CE5EDE1, 0x47B13C7A,
507
+ 0xD2DF599C, 0xF2733F55, 0x14CE7918, 0xC737BF73, 0xF7CDEA53, 0xFDAA5B5F, 0x3D6F14DF, 0x44DB8678,
508
+ 0xAFF381CA, 0x68C43EB9, 0x24342C38, 0xA3405FC2, 0x1DC37216, 0xE2250CBC, 0x3C498B28, 0x0D9541FF,
509
+ 0xA8017139, 0x0CB3DE08, 0xB4E49CD8, 0x56C19064, 0xCB84617B, 0x32B670D5, 0x6C5C7448, 0xB85742D0
510
+ );
511
+
512
+ for ($i = 0; $i < 256; $i++) {
513
+ $t2[$i << 8] = (($t3[$i] << 8) & 0xFFFFFF00) | (($t3[$i] >> 24) & 0x000000FF);
514
+ $t1[$i << 16] = (($t3[$i] << 16) & 0xFFFF0000) | (($t3[$i] >> 16) & 0x0000FFFF);
515
+ $t0[$i << 24] = (($t3[$i] << 24) & 0xFF000000) | (($t3[$i] >> 8) & 0x00FFFFFF);
516
+
517
+ $dt2[$i << 8] = (($this->dt3[$i] << 8) & 0xFFFFFF00) | (($dt3[$i] >> 24) & 0x000000FF);
518
+ $dt1[$i << 16] = (($this->dt3[$i] << 16) & 0xFFFF0000) | (($dt3[$i] >> 16) & 0x0000FFFF);
519
+ $dt0[$i << 24] = (($this->dt3[$i] << 24) & 0xFF000000) | (($dt3[$i] >> 8) & 0x00FFFFFF);
520
+ }
521
+ }
522
+
523
+ /**
524
+ * Sets the key.
525
+ *
526
+ * Keys can be of any length. Rijndael, itself, requires the use of a key that's between 128-bits and 256-bits long and
527
+ * whose length is a multiple of 32. If the key is less than 256-bits and the key length isn't set, we round the length
528
+ * up to the closest valid key length, padding $key with null bytes. If the key is more than 256-bits, we trim the
529
+ * excess bits.
530
+ *
531
+ * If the key is not explicitly set, it'll be assumed to be all null bytes.
532
+ *
533
+ * @access public
534
+ * @param String $key
535
+ */
536
+ function setKey($key)
537
+ {
538
+ $this->key = $key;
539
+ $this->changed = true;
540
+ }
541
+
542
+ /**
543
+ * Sets the initialization vector. (optional)
544
+ *
545
+ * SetIV is not required when CRYPT_RIJNDAEL_MODE_ECB is being used. If not explictly set, it'll be assumed
546
+ * to be all zero's.
547
+ *
548
+ * @access public
549
+ * @param String $iv
550
+ */
551
+ function setIV($iv)
552
+ {
553
+ $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($iv, 0, $this->block_size), $this->block_size, chr(0));;
554
+ }
555
+
556
+ /**
557
+ * Sets the key length
558
+ *
559
+ * Valid key lengths are 128, 160, 192, 224, and 256. If the length is less than 128, it will be rounded up to
560
+ * 128. If the length is greater then 128 and invalid, it will be rounded down to the closest valid amount.
561
+ *
562
+ * @access public
563
+ * @param Integer $length
564
+ */
565
+ function setKeyLength($length)
566
+ {
567
+ $length >>= 5;
568
+ if ($length > 8) {
569
+ $length = 8;
570
+ } else if ($length < 4) {
571
+ $length = 4;
572
+ }
573
+ $this->Nk = $length;
574
+ $this->key_size = $length << 2;
575
+
576
+ $this->explicit_key_length = true;
577
+ $this->changed = true;
578
+ }
579
+
580
+ /**
581
+ * Sets the block length
582
+ *
583
+ * Valid block lengths are 128, 160, 192, 224, and 256. If the length is less than 128, it will be rounded up to
584
+ * 128. If the length is greater then 128 and invalid, it will be rounded down to the closest valid amount.
585
+ *
586
+ * @access public
587
+ * @param Integer $length
588
+ */
589
+ function setBlockLength($length)
590
+ {
591
+ $length >>= 5;
592
+ if ($length > 8) {
593
+ $length = 8;
594
+ } else if ($length < 4) {
595
+ $length = 4;
596
+ }
597
+ $this->Nb = $length;
598
+ $this->block_size = $length << 2;
599
+ $this->changed = true;
600
+ }
601
+
602
+ /**
603
+ * Generate CTR XOR encryption key
604
+ *
605
+ * Encrypt the output of this and XOR it against the ciphertext / plaintext to get the
606
+ * plaintext / ciphertext in CTR mode.
607
+ *
608
+ * @see Crypt_Rijndael::decrypt()
609
+ * @see Crypt_Rijndael::encrypt()
610
+ * @access public
611
+ * @param Integer $length
612
+ * @param String $iv
613
+ */
614
+ function _generate_xor($length, &$iv)
615
+ {
616
+ $xor = '';
617
+ $block_size = $this->block_size;
618
+ $num_blocks = floor(($length + ($block_size - 1)) / $block_size);
619
+ for ($i = 0; $i < $num_blocks; $i++) {
620
+ $xor.= $iv;
621
+ for ($j = 4; $j <= $block_size; $j+=4) {
622
+ $temp = substr($iv, -$j, 4);
623
+ switch ($temp) {
624
+ case "\xFF\xFF\xFF\xFF":
625
+ $iv = substr_replace($iv, "\x00\x00\x00\x00", -$j, 4);
626
+ break;
627
+ case "\x7F\xFF\xFF\xFF":
628
+ $iv = substr_replace($iv, "\x80\x00\x00\x00", -$j, 4);
629
+ break 2;
630
+ default:
631
+ extract(unpack('Ncount', $temp));
632
+ $iv = substr_replace($iv, pack('N', $count + 1), -$j, 4);
633
+ break 2;
634
+ }
635
+ }
636
+ }
637
+
638
+ return $xor;
639
+ }
640
+
641
+ /**
642
+ * Encrypts a message.
643
+ *
644
+ * $plaintext will be padded with additional bytes such that it's length is a multiple of the block size. Other Rjindael
645
+ * implementations may or may not pad in the same manner. Other common approaches to padding and the reasons why it's
646
+ * necessary are discussed in the following
647
+ * URL:
648
+ *
649
+ * {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html}
650
+ *
651
+ * An alternative to padding is to, separately, send the length of the file. This is what SSH, in fact, does.
652
+ * strlen($plaintext) will still need to be a multiple of 8, however, arbitrary values can be added to make it that
653
+ * length.
654
+ *
655
+ * @see Crypt_Rijndael::decrypt()
656
+ * @access public
657
+ * @param String $plaintext
658
+ */
659
+ function encrypt($plaintext)
660
+ {
661
+ $this->_setup();
662
+ if ($this->paddable) {
663
+ $plaintext = $this->_pad($plaintext);
664
+ }
665
+
666
+ $block_size = $this->block_size;
667
+ $buffer = &$this->enbuffer;
668
+ $continuousBuffer = $this->continuousBuffer;
669
+ $ciphertext = '';
670
+ switch ($this->mode) {
671
+ case CRYPT_RIJNDAEL_MODE_ECB:
672
+ for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
673
+ $ciphertext.= $this->_encryptBlock(substr($plaintext, $i, $block_size));
674
+ }
675
+ break;
676
+ case CRYPT_RIJNDAEL_MODE_CBC:
677
+ $xor = $this->encryptIV;
678
+ for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
679
+ $block = substr($plaintext, $i, $block_size);
680
+ $block = $this->_encryptBlock($block ^ $xor);
681
+ $xor = $block;
682
+ $ciphertext.= $block;
683
+ }
684
+ if ($this->continuousBuffer) {
685
+ $this->encryptIV = $xor;
686
+ }
687
+ break;
688
+ case CRYPT_RIJNDAEL_MODE_CTR:
689
+ $xor = $this->encryptIV;
690
+ if (!empty($buffer)) {
691
+ for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
692
+ $block = substr($plaintext, $i, $block_size);
693
+ $buffer.= $this->_encryptBlock($this->_generate_xor($block_size, $xor));
694
+ $key = $this->_string_shift($buffer, $block_size);
695
+ $ciphertext.= $block ^ $key;
696
+ }
697
+ } else {
698
+ for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
699
+ $block = substr($plaintext, $i, $block_size);
700
+ $key = $this->_encryptBlock($this->_generate_xor($block_size, $xor));
701
+ $ciphertext.= $block ^ $key;
702
+ }
703
+ }
704
+ if ($this->continuousBuffer) {
705
+ $this->encryptIV = $xor;
706
+ if ($start = strlen($plaintext) % $block_size) {
707
+ $buffer = substr($key, $start) . $buffer;
708
+ }
709
+ }
710
+ break;
711
+ case CRYPT_RIJNDAEL_MODE_CFB:
712
+ if (!empty($buffer['xor'])) {
713
+ $ciphertext = $plaintext ^ $buffer['xor'];
714
+ $iv = $buffer['encrypted'] . $ciphertext;
715
+ $start = strlen($ciphertext);
716
+ $buffer['encrypted'].= $ciphertext;
717
+ $buffer['xor'] = substr($buffer['xor'], strlen($ciphertext));
718
+ } else {
719
+ $ciphertext = '';
720
+ $iv = $this->encryptIV;
721
+ $start = 0;
722
+ }
723
+
724
+ for ($i = $start; $i < strlen($plaintext); $i+=$block_size) {
725
+ $block = substr($plaintext, $i, $block_size);
726
+ $xor = $this->_encryptBlock($iv);
727
+ $iv = $block ^ $xor;
728
+ if ($continuousBuffer && strlen($iv) != $block_size) {
729
+ $buffer = array(
730
+ 'encrypted' => $iv,
731
+ 'xor' => substr($xor, strlen($iv))
732
+ );
733
+ }
734
+ $ciphertext.= $iv;
735
+ }
736
+
737
+ if ($this->continuousBuffer) {
738
+ $this->encryptIV = $iv;
739
+ }
740
+ break;
741
+ case CRYPT_RIJNDAEL_MODE_OFB:
742
+ $xor = $this->encryptIV;
743
+ if (strlen($buffer)) {
744
+ for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
745
+ $xor = $this->_encryptBlock($xor);
746
+ $buffer.= $xor;
747
+ $key = $this->_string_shift($buffer, $block_size);
748
+ $ciphertext.= substr($plaintext, $i, $block_size) ^ $key;
749
+ }
750
+ } else {
751
+ for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
752
+ $xor = $this->_encryptBlock($xor);
753
+ $ciphertext.= substr($plaintext, $i, $block_size) ^ $xor;
754
+ }
755
+ $key = $xor;
756
+ }
757
+ if ($this->continuousBuffer) {
758
+ $this->encryptIV = $xor;
759
+ if ($start = strlen($plaintext) % $block_size) {
760
+ $buffer = substr($key, $start) . $buffer;
761
+ }
762
+ }
763
+ }
764
+
765
+ return $ciphertext;
766
+ }
767
+
768
+ /**
769
+ * Decrypts a message.
770
+ *
771
+ * If strlen($ciphertext) is not a multiple of the block size, null bytes will be added to the end of the string until
772
+ * it is.
773
+ *
774
+ * @see Crypt_Rijndael::encrypt()
775
+ * @access public
776
+ * @param String $ciphertext
777
+ */
778
+ function decrypt($ciphertext)
779
+ {
780
+ $this->_setup();
781
+
782
+ if ($this->paddable) {
783
+ // we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic :
784
+ // "The data is padded with "\0" to make sure the length of the data is n * blocksize."
785
+ $ciphertext = str_pad($ciphertext, strlen($ciphertext) + ($this->block_size - strlen($ciphertext) % $this->block_size) % $this->block_size, chr(0));
786
+ }
787
+
788
+ $block_size = $this->block_size;
789
+ $buffer = &$this->debuffer;
790
+ $continuousBuffer = $this->continuousBuffer;
791
+ $plaintext = '';
792
+ switch ($this->mode) {
793
+ case CRYPT_RIJNDAEL_MODE_ECB:
794
+ for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
795
+ $plaintext.= $this->_decryptBlock(substr($ciphertext, $i, $block_size));
796
+ }
797
+ break;
798
+ case CRYPT_RIJNDAEL_MODE_CBC:
799
+ $xor = $this->decryptIV;
800
+ for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
801
+ $block = substr($ciphertext, $i, $block_size);
802
+ $plaintext.= $this->_decryptBlock($block) ^ $xor;
803
+ $xor = $block;
804
+ }
805
+ if ($this->continuousBuffer) {
806
+ $this->decryptIV = $xor;
807
+ }
808
+ break;
809
+ case CRYPT_RIJNDAEL_MODE_CTR:
810
+ $xor = $this->decryptIV;
811
+ if (strlen($buffer)) {
812
+ for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
813
+ $block = substr($ciphertext, $i, $block_size);
814
+ $buffer.= $this->_encryptBlock($this->_generate_xor($block_size, $xor));
815
+ $key = $this->_string_shift($buffer, $block_size);
816
+ $plaintext.= $block ^ $key;
817
+ }
818
+ } else {
819
+ for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
820
+ $block = substr($ciphertext, $i, $block_size);
821
+ $key = $this->_encryptBlock($this->_generate_xor($block_size, $xor));
822
+ $plaintext.= $block ^ $key;
823
+ }
824
+ }
825
+ if ($this->continuousBuffer) {
826
+ $this->decryptIV = $xor;
827
+ if ($start = strlen($ciphertext) % $block_size) {
828
+ $buffer = substr($key, $start) . $buffer;
829
+ }
830
+ }
831
+ break;
832
+ case CRYPT_RIJNDAEL_MODE_CFB:
833
+ if (!empty($buffer['ciphertext'])) {
834
+ $plaintext = $ciphertext ^ substr($this->decryptIV, strlen($buffer['ciphertext']));
835
+ $buffer['ciphertext'].= substr($ciphertext, 0, strlen($plaintext));
836
+ if (strlen($buffer['ciphertext']) == $block_size) {
837
+ $xor = $this->_encryptBlock($buffer['ciphertext']);
838
+ $buffer['ciphertext'] = '';
839
+ }
840
+ $start = strlen($plaintext);
841
+ $block = $this->decryptIV;
842
+ } else {
843
+ $plaintext = '';
844
+ $xor = $this->_encryptBlock($this->decryptIV);
845
+ $start = 0;
846
+ }
847
+
848
+ for ($i = $start; $i < strlen($ciphertext); $i+=$block_size) {
849
+ $block = substr($ciphertext, $i, $block_size);
850
+ $plaintext.= $block ^ $xor;
851
+ if ($continuousBuffer && strlen($block) != $block_size) {
852
+ $buffer['ciphertext'].= $block;
853
+ $block = $xor;
854
+ } else if (strlen($block) == $block_size) {
855
+ $xor = $this->_encryptBlock($block);
856
+ }
857
+ }
858
+ if ($this->continuousBuffer) {
859
+ $this->decryptIV = $block;
860
+ }
861
+ break;
862
+ case CRYPT_RIJNDAEL_MODE_OFB:
863
+ $xor = $this->decryptIV;
864
+ if (strlen($buffer)) {
865
+ for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
866
+ $xor = $this->_encryptBlock($xor);
867
+ $buffer.= $xor;
868
+ $key = $this->_string_shift($buffer, $block_size);
869
+ $plaintext.= substr($ciphertext, $i, $block_size) ^ $key;
870
+ }
871
+ } else {
872
+ for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
873
+ $xor = $this->_encryptBlock($xor);
874
+ $plaintext.= substr($ciphertext, $i, $block_size) ^ $xor;
875
+ }
876
+ $key = $xor;
877
+ }
878
+ if ($this->continuousBuffer) {
879
+ $this->decryptIV = $xor;
880
+ if ($start = strlen($ciphertext) % $block_size) {
881
+ $buffer = substr($key, $start) . $buffer;
882
+ }
883
+ }
884
+ }
885
+
886
+ return $this->paddable ? $this->_unpad($plaintext) : $plaintext;
887
+ }
888
+
889
+ /**
890
+ * Encrypts a block
891
+ *
892
+ * @access private
893
+ * @param String $in
894
+ * @return String
895
+ */
896
+ function _encryptBlock($in)
897
+ {
898
+ $state = array();
899
+ $words = unpack('N*word', $in);
900
+
901
+ $w = $this->w;
902
+ $t0 = $this->t0;
903
+ $t1 = $this->t1;
904
+ $t2 = $this->t2;
905
+ $t3 = $this->t3;
906
+ $Nb = $this->Nb;
907
+ $Nr = $this->Nr;
908
+ $c = $this->c;
909
+
910
+ // addRoundKey
911
+ $i = 0;
912
+ foreach ($words as $word) {
913
+ $state[] = $word ^ $w[0][$i++];
914
+ }
915
+
916
+ // fips-197.pdf#page=19, "Figure 5. Pseudo Code for the Cipher", states that this loop has four components -
917
+ // subBytes, shiftRows, mixColumns, and addRoundKey. fips-197.pdf#page=30, "Implementation Suggestions Regarding
918
+ // Various Platforms" suggests that performs enhanced implementations are described in Rijndael-ammended.pdf.
919
+ // Rijndael-ammended.pdf#page=20, "Implementation aspects / 32-bit processor", discusses such an optimization.
920
+ // Unfortunately, the description given there is not quite correct. Per aes.spec.v316.pdf#page=19 [1],
921
+ // equation (7.4.7) is supposed to use addition instead of subtraction, so we'll do that here, as well.
922
+
923
+ // [1] http://fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.v316.pdf
924
+ $temp = array();
925
+ for ($round = 1; $round < $Nr; $round++) {
926
+ $i = 0; // $c[0] == 0
927
+ $j = $c[1];
928
+ $k = $c[2];
929
+ $l = $c[3];
930
+
931
+ while ($i < $this->Nb) {
932
+ $temp[$i] = $t0[$state[$i] & 0xFF000000] ^
933
+ $t1[$state[$j] & 0x00FF0000] ^
934
+ $t2[$state[$k] & 0x0000FF00] ^
935
+ $t3[$state[$l] & 0x000000FF] ^
936
+ $w[$round][$i];
937
+ $i++;
938
+ $j = ($j + 1) % $Nb;
939
+ $k = ($k + 1) % $Nb;
940
+ $l = ($l + 1) % $Nb;
941
+ }
942
+
943
+ for ($i = 0; $i < $Nb; $i++) {
944
+ $state[$i] = $temp[$i];
945
+ }
946
+ }
947
+
948
+ // subWord
949
+ for ($i = 0; $i < $Nb; $i++) {
950
+ $state[$i] = $this->_subWord($state[$i]);
951
+ }
952
+
953
+ // shiftRows + addRoundKey
954
+ $i = 0; // $c[0] == 0
955
+ $j = $c[1];
956
+ $k = $c[2];
957
+ $l = $c[3];
958
+ while ($i < $this->Nb) {
959
+ $temp[$i] = ($state[$i] & 0xFF000000) ^
960
+ ($state[$j] & 0x00FF0000) ^
961
+ ($state[$k] & 0x0000FF00) ^
962
+ ($state[$l] & 0x000000FF) ^
963
+ $w[$Nr][$i];
964
+ $i++;
965
+ $j = ($j + 1) % $Nb;
966
+ $k = ($k + 1) % $Nb;
967
+ $l = ($l + 1) % $Nb;
968
+ }
969
+ $state = $temp;
970
+
971
+ array_unshift($state, 'N*');
972
+
973
+ return call_user_func_array('pack', $state);
974
+ }
975
+
976
+ /**
977
+ * Decrypts a block
978
+ *
979
+ * @access private
980
+ * @param String $in
981
+ * @return String
982
+ */
983
+ function _decryptBlock($in)
984
+ {
985
+ $state = array();
986
+ $words = unpack('N*word', $in);
987
+
988
+ $num_states = count($state);
989
+ $dw = $this->dw;
990
+ $dt0 = $this->dt0;
991
+ $dt1 = $this->dt1;
992
+ $dt2 = $this->dt2;
993
+ $dt3 = $this->dt3;
994
+ $Nb = $this->Nb;
995
+ $Nr = $this->Nr;
996
+ $c = $this->c;
997
+
998
+ // addRoundKey
999
+ $i = 0;
1000
+ foreach ($words as $word) {
1001
+ $state[] = $word ^ $dw[$Nr][$i++];
1002
+ }
1003
+
1004
+ $temp = array();
1005
+ for ($round = $Nr - 1; $round > 0; $round--) {
1006
+ $i = 0; // $c[0] == 0
1007
+ $j = $Nb - $c[1];
1008
+ $k = $Nb - $c[2];
1009
+ $l = $Nb - $c[3];
1010
+
1011
+ while ($i < $Nb) {
1012
+ $temp[$i] = $dt0[$state[$i] & 0xFF000000] ^
1013
+ $dt1[$state[$j] & 0x00FF0000] ^
1014
+ $dt2[$state[$k] & 0x0000FF00] ^
1015
+ $dt3[$state[$l] & 0x000000FF] ^
1016
+ $dw[$round][$i];
1017
+ $i++;
1018
+ $j = ($j + 1) % $Nb;
1019
+ $k = ($k + 1) % $Nb;
1020
+ $l = ($l + 1) % $Nb;
1021
+ }
1022
+
1023
+ for ($i = 0; $i < $Nb; $i++) {
1024
+ $state[$i] = $temp[$i];
1025
+ }
1026
+ }
1027
+
1028
+ // invShiftRows + invSubWord + addRoundKey
1029
+ $i = 0; // $c[0] == 0
1030
+ $j = $Nb - $c[1];
1031
+ $k = $Nb - $c[2];
1032
+ $l = $Nb - $c[3];
1033
+
1034
+ while ($i < $Nb) {
1035
+ $temp[$i] = $dw[0][$i] ^
1036
+ $this->_invSubWord(($state[$i] & 0xFF000000) |
1037
+ ($state[$j] & 0x00FF0000) |
1038
+ ($state[$k] & 0x0000FF00) |
1039
+ ($state[$l] & 0x000000FF));
1040
+ $i++;
1041
+ $j = ($j + 1) % $Nb;
1042
+ $k = ($k + 1) % $Nb;
1043
+ $l = ($l + 1) % $Nb;
1044
+ }
1045
+
1046
+ $state = $temp;
1047
+
1048
+ array_unshift($state, 'N*');
1049
+
1050
+ return call_user_func_array('pack', $state);
1051
+ }
1052
+
1053
+ /**
1054
+ * Setup Rijndael
1055
+ *
1056
+ * Validates all the variables and calculates $Nr - the number of rounds that need to be performed - and $w - the key
1057
+ * key schedule.
1058
+ *
1059
+ * @access private
1060
+ */
1061
+ function _setup()
1062
+ {
1063
+ // Each number in $rcon is equal to the previous number multiplied by two in Rijndael's finite field.
1064
+ // See http://en.wikipedia.org/wiki/Finite_field_arithmetic#Multiplicative_inverse
1065
+ static $rcon = array(0,
1066
+ 0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000,
1067
+ 0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000,
1068
+ 0x6C000000, 0xD8000000, 0xAB000000, 0x4D000000, 0x9A000000,
1069
+ 0x2F000000, 0x5E000000, 0xBC000000, 0x63000000, 0xC6000000,
1070
+ 0x97000000, 0x35000000, 0x6A000000, 0xD4000000, 0xB3000000,
1071
+ 0x7D000000, 0xFA000000, 0xEF000000, 0xC5000000, 0x91000000
1072
+ );
1073
+
1074
+ if (!$this->changed) {
1075
+ return;
1076
+ }
1077
+
1078
+ if (!$this->explicit_key_length) {
1079
+ // we do >> 2, here, and not >> 5, as we do above, since strlen($this->key) tells us the number of bytes - not bits
1080
+ $length = strlen($this->key) >> 2;
1081
+ if ($length > 8) {
1082
+ $length = 8;
1083
+ } else if ($length < 4) {
1084
+ $length = 4;
1085
+ }
1086
+ $this->Nk = $length;
1087
+ $this->key_size = $length << 2;
1088
+ }
1089
+
1090
+ $this->key = str_pad(substr($this->key, 0, $this->key_size), $this->key_size, chr(0));
1091
+ $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($this->iv, 0, $this->block_size), $this->block_size, chr(0));
1092
+
1093
+ // see Rijndael-ammended.pdf#page=44
1094
+ $this->Nr = max($this->Nk, $this->Nb) + 6;
1095
+
1096
+ // shift offsets for Nb = 5, 7 are defined in Rijndael-ammended.pdf#page=44,
1097
+ // "Table 8: Shift offsets in Shiftrow for the alternative block lengths"
1098
+ // shift offsets for Nb = 4, 6, 8 are defined in Rijndael-ammended.pdf#page=14,
1099
+ // "Table 2: Shift offsets for different block lengths"
1100
+ switch ($this->Nb) {
1101
+ case 4:
1102
+ case 5:
1103
+ case 6:
1104
+ $this->c = array(0, 1, 2, 3);
1105
+ break;
1106
+ case 7:
1107
+ $this->c = array(0, 1, 2, 4);
1108
+ break;
1109
+ case 8:
1110
+ $this->c = array(0, 1, 3, 4);
1111
+ }
1112
+
1113
+ $key = $this->key;
1114
+
1115
+ $w = array_values(unpack('N*words', $key));
1116
+
1117
+ $length = $this->Nb * ($this->Nr + 1);
1118
+ for ($i = $this->Nk; $i < $length; $i++) {
1119
+ $temp = $w[$i - 1];
1120
+ if ($i % $this->Nk == 0) {
1121
+ // according to <http://php.net/language.types.integer>, "the size of an integer is platform-dependent".
1122
+ // on a 32-bit machine, it's 32-bits, and on a 64-bit machine, it's 64-bits. on a 32-bit machine,
1123
+ // 0xFFFFFFFF << 8 == 0xFFFFFF00, but on a 64-bit machine, it equals 0xFFFFFFFF00. as such, doing 'and'
1124
+ // with 0xFFFFFFFF (or 0xFFFFFF00) on a 32-bit machine is unnecessary, but on a 64-bit machine, it is.
1125
+ $temp = (($temp << 8) & 0xFFFFFF00) | (($temp >> 24) & 0x000000FF); // rotWord
1126
+ $temp = $this->_subWord($temp) ^ $rcon[$i / $this->Nk];
1127
+ } else if ($this->Nk > 6 && $i % $this->Nk == 4) {
1128
+ $temp = $this->_subWord($temp);
1129
+ }
1130
+ $w[$i] = $w[$i - $this->Nk] ^ $temp;
1131
+ }
1132
+
1133
+ // convert the key schedule from a vector of $Nb * ($Nr + 1) length to a matrix with $Nr + 1 rows and $Nb columns
1134
+ // and generate the inverse key schedule. more specifically,
1135
+ // according to <http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=23> (section 5.3.3),
1136
+ // "The key expansion for the Inverse Cipher is defined as follows:
1137
+ // 1. Apply the Key Expansion.
1138
+ // 2. Apply InvMixColumn to all Round Keys except the first and the last one."
1139
+ // also, see fips-197.pdf#page=27, "5.3.5 Equivalent Inverse Cipher"
1140
+ $temp = array();
1141
+ for ($i = $row = $col = 0; $i < $length; $i++, $col++) {
1142
+ if ($col == $this->Nb) {
1143
+ if ($row == 0) {
1144
+ $this->dw[0] = $this->w[0];
1145
+ } else {
1146
+ // subWord + invMixColumn + invSubWord = invMixColumn
1147
+ $j = 0;
1148
+ while ($j < $this->Nb) {
1149
+ $dw = $this->_subWord($this->w[$row][$j]);
1150
+ $temp[$j] = $this->dt0[$dw & 0xFF000000] ^
1151
+ $this->dt1[$dw & 0x00FF0000] ^
1152
+ $this->dt2[$dw & 0x0000FF00] ^
1153
+ $this->dt3[$dw & 0x000000FF];
1154
+ $j++;
1155
+ }
1156
+ $this->dw[$row] = $temp;
1157
+ }
1158
+
1159
+ $col = 0;
1160
+ $row++;
1161
+ }
1162
+ $this->w[$row][$col] = $w[$i];
1163
+ }
1164
+
1165
+ $this->dw[$row] = $this->w[$row];
1166
+
1167
+ $this->changed = false;
1168
+ }
1169
+
1170
+ /**
1171
+ * Performs S-Box substitutions
1172
+ *
1173
+ * @access private
1174
+ */
1175
+ function _subWord($word)
1176
+ {
1177
+ static $sbox0, $sbox1, $sbox2, $sbox3;
1178
+
1179
+ if (empty($sbox0)) {
1180
+ $sbox0 = array(
1181
+ 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
1182
+ 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
1183
+ 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
1184
+ 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
1185
+ 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
1186
+ 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
1187
+ 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
1188
+ 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
1189
+ 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
1190
+ 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
1191
+ 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
1192
+ 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
1193
+ 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
1194
+ 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
1195
+ 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
1196
+ 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
1197
+ );
1198
+
1199
+ $sbox1 = array();
1200
+ $sbox2 = array();
1201
+ $sbox3 = array();
1202
+
1203
+ for ($i = 0; $i < 256; $i++) {
1204
+ $sbox1[$i << 8] = $sbox0[$i] << 8;
1205
+ $sbox2[$i << 16] = $sbox0[$i] << 16;
1206
+ $sbox3[$i << 24] = $sbox0[$i] << 24;
1207
+ }
1208
+ }
1209
+
1210
+ return $sbox0[$word & 0x000000FF] |
1211
+ $sbox1[$word & 0x0000FF00] |
1212
+ $sbox2[$word & 0x00FF0000] |
1213
+ $sbox3[$word & 0xFF000000];
1214
+ }
1215
+
1216
+ /**
1217
+ * Performs inverse S-Box substitutions
1218
+ *
1219
+ * @access private
1220
+ */
1221
+ function _invSubWord($word)
1222
+ {
1223
+ static $sbox0, $sbox1, $sbox2, $sbox3;
1224
+
1225
+ if (empty($sbox0)) {
1226
+ $sbox0 = array(
1227
+ 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
1228
+ 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
1229
+ 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
1230
+ 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
1231
+ 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
1232
+ 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
1233
+ 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
1234
+ 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
1235
+ 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
1236
+ 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
1237
+ 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
1238
+ 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
1239
+ 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
1240
+ 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
1241
+ 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
1242
+ 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D
1243
+ );
1244
+
1245
+ $sbox1 = array();
1246
+ $sbox2 = array();
1247
+ $sbox3 = array();
1248
+
1249
+ for ($i = 0; $i < 256; $i++) {
1250
+ $sbox1[$i << 8] = $sbox0[$i] << 8;
1251
+ $sbox2[$i << 16] = $sbox0[$i] << 16;
1252
+ $sbox3[$i << 24] = $sbox0[$i] << 24;
1253
+ }
1254
+ }
1255
+
1256
+ return $sbox0[$word & 0x000000FF] |
1257
+ $sbox1[$word & 0x0000FF00] |
1258
+ $sbox2[$word & 0x00FF0000] |
1259
+ $sbox3[$word & 0xFF000000];
1260
+ }
1261
+
1262
+ /**
1263
+ * Pad "packets".
1264
+ *
1265
+ * Rijndael works by encrypting between sixteen and thirty-two bytes at a time, provided that number is also a multiple
1266
+ * of four. If you ever need to encrypt or decrypt something that isn't of the proper length, it becomes necessary to
1267
+ * pad the input so that it is of the proper length.
1268
+ *
1269
+ * Padding is enabled by default. Sometimes, however, it is undesirable to pad strings. Such is the case in SSH,
1270
+ * where "packets" are padded with random bytes before being encrypted. Unpad these packets and you risk stripping
1271
+ * away characters that shouldn't be stripped away. (SSH knows how many bytes are added because the length is
1272
+ * transmitted separately)
1273
+ *
1274
+ * @see Crypt_Rijndael::disablePadding()
1275
+ * @access public
1276
+ */
1277
+ function enablePadding()
1278
+ {
1279
+ $this->padding = true;
1280
+ }
1281
+
1282
+ /**
1283
+ * Do not pad packets.
1284
+ *
1285
+ * @see Crypt_Rijndael::enablePadding()
1286
+ * @access public
1287
+ */
1288
+ function disablePadding()
1289
+ {
1290
+ $this->padding = false;
1291
+ }
1292
+
1293
+ /**
1294
+ * Pads a string
1295
+ *
1296
+ * Pads a string using the RSA PKCS padding standards so that its length is a multiple of the blocksize.
1297
+ * $block_size - (strlen($text) % $block_size) bytes are added, each of which is equal to
1298
+ * chr($block_size - (strlen($text) % $block_size)
1299
+ *
1300
+ * If padding is disabled and $text is not a multiple of the blocksize, the string will be padded regardless
1301
+ * and padding will, hence forth, be enabled.
1302
+ *
1303
+ * @see Crypt_Rijndael::_unpad()
1304
+ * @access private
1305
+ */
1306
+ function _pad($text)
1307
+ {
1308
+ $length = strlen($text);
1309
+
1310
+ if (!$this->padding) {
1311
+ if ($length % $this->block_size == 0) {
1312
+ return $text;
1313
+ } else {
1314
+ user_error("The plaintext's length ($length) is not a multiple of the block size ({$this->block_size})", E_USER_NOTICE);
1315
+ $this->padding = true;
1316
+ }
1317
+ }
1318
+
1319
+ $pad = $this->block_size - ($length % $this->block_size);
1320
+
1321
+ return str_pad($text, $length + $pad, chr($pad));
1322
+ }
1323
+
1324
+ /**
1325
+ * Unpads a string.
1326
+ *
1327
+ * If padding is enabled and the reported padding length is invalid the encryption key will be assumed to be wrong
1328
+ * and false will be returned.
1329
+ *
1330
+ * @see Crypt_Rijndael::_pad()
1331
+ * @access private
1332
+ */
1333
+ function _unpad($text)
1334
+ {
1335
+ if (!$this->padding) {
1336
+ return $text;
1337
+ }
1338
+
1339
+ $length = ord($text[strlen($text) - 1]);
1340
+
1341
+ if (!$length || $length > $this->block_size) {
1342
+ return false;
1343
+ }
1344
+
1345
+ return substr($text, 0, -$length);
1346
+ }
1347
+
1348
+ /**
1349
+ * Treat consecutive "packets" as if they are a continuous buffer.
1350
+ *
1351
+ * Say you have a 32-byte plaintext $plaintext. Using the default behavior, the two following code snippets
1352
+ * will yield different outputs:
1353
+ *
1354
+ * <code>
1355
+ * echo $rijndael->encrypt(substr($plaintext, 0, 16));
1356
+ * echo $rijndael->encrypt(substr($plaintext, 16, 16));
1357
+ * </code>
1358
+ * <code>
1359
+ * echo $rijndael->encrypt($plaintext);
1360
+ * </code>
1361
+ *
1362
+ * The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates
1363
+ * another, as demonstrated with the following:
1364
+ *
1365
+ * <code>
1366
+ * $rijndael->encrypt(substr($plaintext, 0, 16));
1367
+ * echo $rijndael->decrypt($des->encrypt(substr($plaintext, 16, 16)));
1368
+ * </code>
1369
+ * <code>
1370
+ * echo $rijndael->decrypt($des->encrypt(substr($plaintext, 16, 16)));
1371
+ * </code>
1372
+ *
1373
+ * With the continuous buffer disabled, these would yield the same output. With it enabled, they yield different
1374
+ * outputs. The reason is due to the fact that the initialization vector's change after every encryption /
1375
+ * decryption round when the continuous buffer is enabled. When it's disabled, they remain constant.
1376
+ *
1377
+ * Put another way, when the continuous buffer is enabled, the state of the Crypt_Rijndael() object changes after each
1378
+ * encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that
1379
+ * continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them),
1380
+ * however, they are also less intuitive and more likely to cause you problems.
1381
+ *
1382
+ * @see Crypt_Rijndael::disableContinuousBuffer()
1383
+ * @access public
1384
+ */
1385
+ function enableContinuousBuffer()
1386
+ {
1387
+ $this->continuousBuffer = true;
1388
+ }
1389
+
1390
+ /**
1391
+ * Treat consecutive packets as if they are a discontinuous buffer.
1392
+ *
1393
+ * The default behavior.
1394
+ *
1395
+ * @see Crypt_Rijndael::enableContinuousBuffer()
1396
+ * @access public
1397
+ */
1398
+ function disableContinuousBuffer()
1399
+ {
1400
+ $this->continuousBuffer = false;
1401
+ $this->encryptIV = $this->iv;
1402
+ $this->decryptIV = $this->iv;
1403
+ }
1404
+
1405
+ /**
1406
+ * String Shift
1407
+ *
1408
+ * Inspired by array_shift
1409
+ *
1410
+ * @param String $string
1411
+ * @param optional Integer $index
1412
+ * @return String
1413
+ * @access private
1414
+ */
1415
+ function _string_shift(&$string, $index = 1)
1416
+ {
1417
+ $substr = substr($string, 0, $index);
1418
+ $string = substr($string, $index);
1419
+ return $substr;
1420
+ }
1421
+ }
1422
+
1423
+ // vim: ts=4:sw=4:et:
1424
+ // vim6: fdl=1:
trunk/includes/S3.php ADDED
@@ -0,0 +1,2211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * $Id$
4
+ *
5
+ * Copyright (c) 2011, Donovan Schönknecht. All rights reserved.
6
+ * Portions copyright (c) 2012, David Anderson (http://www.simbahosting.co.uk). All rights reserved.
7
+ *
8
+ * Redistribution and use in source and binary forms, with or without
9
+ * modification, are permitted provided that the following conditions are met:
10
+ *
11
+ * - Redistributions of source code must retain the above copyright notice,
12
+ * this list of conditions and the following disclaimer.
13
+ * - Redistributions in binary form must reproduce the above copyright
14
+ * notice, this list of conditions and the following disclaimer in the
15
+ * documentation and/or other materials provided with the distribution.
16
+ *
17
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
+ * POSSIBILITY OF SUCH DAMAGE.
28
+ *
29
+ * Amazon S3 is a trademark of Amazon.com, Inc. or its affiliates.
30
+ */
31
+
32
+ /**
33
+ * Amazon S3 PHP class
34
+ *
35
+ * @link http://undesigned.org.za/2007/10/22/amazon-s3-php-class
36
+ * @version 0.5.0-dev
37
+ */
38
+ class S3
39
+ {
40
+ // ACL flags
41
+ const ACL_PRIVATE = 'private';
42
+ const ACL_PUBLIC_READ = 'public-read';
43
+ const ACL_PUBLIC_READ_WRITE = 'public-read-write';
44
+ const ACL_AUTHENTICATED_READ = 'authenticated-read';
45
+
46
+ const STORAGE_CLASS_STANDARD = 'STANDARD';
47
+ const STORAGE_CLASS_RRS = 'REDUCED_REDUNDANCY';
48
+
49
+ private static $__accessKey = null; // AWS Access key
50
+ private static $__secretKey = null; // AWS Secret key
51
+ private static $__sslKey = null;
52
+
53
+ public static $endpoint = 's3.amazonaws.com';
54
+ public static $proxy = null;
55
+
56
+ public static $useSSL = false;
57
+ public static $useSSLValidation = true;
58
+ public static $useExceptions = false;
59
+
60
+ // SSL CURL SSL options - only needed if you are experiencing problems with your OpenSSL configuration
61
+ public static $sslKey = null;
62
+ public static $sslCert = null;
63
+ public static $sslCACert = null;
64
+
65
+ private static $__signingKeyPairId = null; // AWS Key Pair ID
66
+ private static $__signingKeyResource = false; // Key resource, freeSigningKey() must be called to clear it from memory
67
+
68
+
69
+ /**
70
+ * Constructor - if you're not using the class statically
71
+ *
72
+ * @param string $accessKey Access key
73
+ * @param string $secretKey Secret key
74
+ * @param boolean $useSSL Enable SSL
75
+ * @return void
76
+ */
77
+ public function __construct($accessKey = null, $secretKey = null, $useSSL = false, $endpoint = 's3.amazonaws.com')
78
+ {
79
+ if ($accessKey !== null && $secretKey !== null)
80
+ self::setAuth($accessKey, $secretKey);
81
+ self::$useSSL = $useSSL;
82
+ self::$endpoint = $endpoint;
83
+ }
84
+
85
+
86
+ /**
87
+ * Set the sertvice endpoint
88
+ *
89
+ * @param string $host Hostname
90
+ * @return void
91
+ */
92
+ public function setEndpoint($host)
93
+ {
94
+ self::$endpoint = $host;
95
+ }
96
+
97
+ /**
98
+ * Set AWS access key and secret key
99
+ *
100
+ * @param string $accessKey Access key
101
+ * @param string $secretKey Secret key
102
+ * @return void
103
+ */
104
+ public static function setAuth($accessKey, $secretKey)
105
+ {
106
+ self::$__accessKey = $accessKey;
107
+ self::$__secretKey = $secretKey;
108
+ }
109
+
110
+
111
+ /**
112
+ * Check if AWS keys have been set
113
+ *
114
+ * @return boolean
115
+ */
116
+ public static function hasAuth() {
117
+ return (self::$__accessKey !== null && self::$__secretKey !== null);
118
+ }
119
+
120
+
121
+ /**
122
+ * Set SSL on or off
123
+ *
124
+ * @param boolean $enabled SSL enabled
125
+ * @param boolean $validate SSL certificate validation
126
+ * @return void
127
+ */
128
+ public static function setSSL($enabled, $validate = true)
129
+ {
130
+ self::$useSSL = $enabled;
131
+ self::$useSSLValidation = $validate;
132
+ }
133
+
134
+
135
+ /**
136
+ * Set SSL client certificates (experimental)
137
+ *
138
+ * @param string $sslCert SSL client certificate
139
+ * @param string $sslKey SSL client key
140
+ * @param string $sslCACert SSL CA cert (only required if you are having problems with your system CA cert)
141
+ * @return void
142
+ */
143
+ public static function setSSLAuth($sslCert = null, $sslKey = null, $sslCACert = null)
144
+ {
145
+ self::$sslCert = $sslCert;
146
+ self::$sslKey = $sslKey;
147
+ self::$sslCACert = $sslCACert;
148
+ }
149
+
150
+
151
+ /**
152
+ * Set proxy information
153
+ *
154
+ * @param string $host Proxy hostname and port (localhost:1234)
155
+ * @param string $user Proxy username
156
+ * @param string $pass Proxy password
157
+ * @param constant $type CURL proxy type
158
+ * @return void
159
+ */
160
+ public static function setProxy($host, $user = null, $pass = null, $type = CURLPROXY_SOCKS5)
161
+ {
162
+ self::$proxy = array('host' => $host, 'type' => $type, 'user' => null, 'pass' => 'null');
163
+ }
164
+
165
+
166
+ /**
167
+ * Set the error mode to exceptions
168
+ *
169
+ * @param boolean $enabled Enable exceptions
170
+ * @return void
171
+ */
172
+ public static function setExceptions($enabled = true)
173
+ {
174
+ self::$useExceptions = $enabled;
175
+ }
176
+
177
+
178
+ /**
179
+ * Set signing key
180
+ *
181
+ * @param string $keyPairId AWS Key Pair ID
182
+ * @param string $signingKey Private Key
183
+ * @param boolean $isFile Load private key from file, set to false to load string
184
+ * @return boolean
185
+ */
186
+ public static function setSigningKey($keyPairId, $signingKey, $isFile = true)
187
+ {
188
+ self::$__signingKeyPairId = $keyPairId;
189
+ if ((self::$__signingKeyResource = openssl_pkey_get_private($isFile ?
190
+ file_get_contents($signingKey) : $signingKey)) !== false) return true;
191
+ self::__triggerError('S3::setSigningKey(): Unable to open load private key: '.$signingKey, __FILE__, __LINE__);
192
+ return false;
193
+ }
194
+
195
+
196
+ /**
197
+ * Free signing key from memory, MUST be called if you are using setSigningKey()
198
+ *
199
+ * @return void
200
+ */
201
+ public static function freeSigningKey()
202
+ {
203
+ if (self::$__signingKeyResource !== false)
204
+ openssl_free_key(self::$__signingKeyResource);
205
+ }
206
+
207
+
208
+ /**
209
+ * Internal error handler
210
+ *
211
+ * @internal Internal error handler
212
+ * @param string $message Error message
213
+ * @param string $file Filename
214
+ * @param integer $line Line number
215
+ * @param integer $code Error code
216
+ * @return void
217
+ */
218
+ private static function __triggerError($message, $file, $line, $code = 0)
219
+ {
220
+ if (self::$useExceptions)
221
+ throw new S3Exception($message, $file, $line, $code);
222
+ else
223
+ trigger_error($message, E_USER_WARNING);
224
+ }
225
+
226
+
227
+ /**
228
+ * Get a list of buckets
229
+ *
230
+ * @param boolean $detailed Returns detailed bucket list when true
231
+ * @return array | false
232
+ */
233
+ public static function listBuckets($detailed = false)
234
+ {
235
+ $rest = new S3Request('GET', '', '', self::$endpoint);
236
+ $rest = $rest->getResponse();
237
+ if ($rest->error === false && $rest->code !== 200)
238
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
239
+ if ($rest->error !== false)
240
+ {
241
+ self::__triggerError(sprintf("S3::listBuckets(): [%s] %s", $rest->error['code'],
242
+ $rest->error['message']), __FILE__, __LINE__);
243
+ return false;
244
+ }
245
+ $results = array();
246
+ if (!isset($rest->body->Buckets)) return $results;
247
+
248
+ if ($detailed)
249
+ {
250
+ if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName))
251
+ $results['owner'] = array(
252
+ 'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->ID
253
+ );
254
+ $results['buckets'] = array();
255
+ foreach ($rest->body->Buckets->Bucket as $b)
256
+ $results['buckets'][] = array(
257
+ 'name' => (string)$b->Name, 'time' => strtotime((string)$b->CreationDate)
258
+ );
259
+ } else
260
+ foreach ($rest->body->Buckets->Bucket as $b) $results[] = (string)$b->Name;
261
+
262
+ return $results;
263
+ }
264
+
265
+
266
+ /*
267
+ * Get contents for a bucket
268
+ *
269
+ * If maxKeys is null this method will loop through truncated result sets
270
+ *
271
+ * @param string $bucket Bucket name
272
+ * @param string $prefix Prefix
273
+ * @param string $marker Marker (last file listed)
274
+ * @param string $maxKeys Max keys (maximum number of keys to return)
275
+ * @param string $delimiter Delimiter
276
+ * @param boolean $returnCommonPrefixes Set to true to return CommonPrefixes
277
+ * @return array | false
278
+ */
279
+ public static function getBucket($bucket, $prefix = null, $marker = null, $maxKeys = null, $delimiter = null, $returnCommonPrefixes = false)
280
+ {
281
+ $rest = new S3Request('GET', $bucket, '', self::$endpoint);
282
+ if ($maxKeys == 0) $maxKeys = null;
283
+ if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
284
+ if ($marker !== null && $marker !== '') $rest->setParameter('marker', $marker);
285
+ if ($maxKeys !== null && $maxKeys !== '') $rest->setParameter('max-keys', $maxKeys);
286
+ if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
287
+ $response = $rest->getResponse();
288
+ if ($response->error === false && $response->code !== 200)
289
+ $response->error = array('code' => $response->code, 'message' => 'Unexpected HTTP status');
290
+ if ($response->error !== false)
291
+ {
292
+ self::__triggerError(sprintf("S3::getBucket(): [%s] %s",
293
+ $response->error['code'], $response->error['message']), __FILE__, __LINE__);
294
+ return false;
295
+ }
296
+
297
+ $results = array();
298
+
299
+ $nextMarker = null;
300
+ if (isset($response->body, $response->body->Contents))
301
+ foreach ($response->body->Contents as $c)
302
+ {
303
+ $results[(string)$c->Key] = array(
304
+ 'name' => (string)$c->Key,
305
+ 'time' => strtotime((string)$c->LastModified),
306
+ 'size' => (int)$c->Size,
307
+ 'hash' => substr((string)$c->ETag, 1, -1)
308
+ );
309
+ $nextMarker = (string)$c->Key;
310
+ }
311
+
312
+ if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes))
313
+ foreach ($response->body->CommonPrefixes as $c)
314
+ $results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix);
315
+
316
+ if (isset($response->body, $response->body->IsTruncated) &&
317
+ (string)$response->body->IsTruncated == 'false') return $results;
318
+
319
+ if (isset($response->body, $response->body->NextMarker))
320
+ $nextMarker = (string)$response->body->NextMarker;
321
+
322
+ // Loop through truncated results if maxKeys isn't specified
323
+ if ($maxKeys == null && $nextMarker !== null && (string)$response->body->IsTruncated == 'true')
324
+ do
325
+ {
326
+ $rest = new S3Request('GET', $bucket, '', self::$endpoint);
327
+ if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
328
+ $rest->setParameter('marker', $nextMarker);
329
+ if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
330
+
331
+ if (($response = $rest->getResponse()) == false || $response->code !== 200) break;
332
+
333
+ if (isset($response->body, $response->body->Contents))
334
+ foreach ($response->body->Contents as $c)
335
+ {
336
+ $results[(string)$c->Key] = array(
337
+ 'name' => (string)$c->Key,
338
+ 'time' => strtotime((string)$c->LastModified),
339
+ 'size' => (int)$c->Size,
340
+ 'hash' => substr((string)$c->ETag, 1, -1)
341
+ );
342
+ $nextMarker = (string)$c->Key;
343
+ }
344
+
345
+ if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes))
346
+ foreach ($response->body->CommonPrefixes as $c)
347
+ $results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix);
348
+
349
+ if (isset($response->body, $response->body->NextMarker))
350
+ $nextMarker = (string)$response->body->NextMarker;
351
+
352
+ } while ($response !== false && (string)$response->body->IsTruncated == 'true');
353
+
354
+ return $results;
355
+ }
356
+
357
+
358
+ /**
359
+ * Put a bucket
360
+ *
361
+ * @param string $bucket Bucket name
362
+ * @param constant $acl ACL flag
363
+ * @param string $location Set as "EU" to create buckets hosted in Europe
364
+ * @return boolean
365
+ */
366
+ public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = false)
367
+ {
368
+ $rest = new S3Request('PUT', $bucket, '', self::$endpoint);
369
+ $rest->setAmzHeader('x-amz-acl', $acl);
370
+
371
+ if ($location !== false)
372
+ {
373
+ $dom = new DOMDocument;
374
+ $createBucketConfiguration = $dom->createElement('CreateBucketConfiguration');
375
+ $locationConstraint = $dom->createElement('LocationConstraint', $location);
376
+ $createBucketConfiguration->appendChild($locationConstraint);
377
+ $dom->appendChild($createBucketConfiguration);
378
+ $rest->data = $dom->saveXML();
379
+ $rest->size = strlen($rest->data);
380
+ $rest->setHeader('Content-Type', 'application/xml');
381
+ }
382
+ $rest = $rest->getResponse();
383
+
384
+ if ($rest->error === false && $rest->code !== 200)
385
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
386
+ if ($rest->error !== false)
387
+ {
388
+ self::__triggerError(sprintf("S3::putBucket({$bucket}, {$acl}, {$location}): [%s] %s",
389
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
390
+ return false;
391
+ }
392
+ return true;
393
+ }
394
+
395
+
396
+ /**
397
+ * Delete an empty bucket
398
+ *
399
+ * @param string $bucket Bucket name
400
+ * @return boolean
401
+ */
402
+ public static function deleteBucket($bucket)
403
+ {
404
+ $rest = new S3Request('DELETE', $bucket, '', self::$endpoint);
405
+ $rest = $rest->getResponse();
406
+ if ($rest->error === false && $rest->code !== 204)
407
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
408
+ if ($rest->error !== false)
409
+ {
410
+ self::__triggerError(sprintf("S3::deleteBucket({$bucket}): [%s] %s",
411
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
412
+ return false;
413
+ }
414
+ return true;
415
+ }
416
+
417
+
418
+ /**
419
+ * Create input info array for putObject()
420
+ *
421
+ * @param string $file Input file
422
+ * @param mixed $md5sum Use MD5 hash (supply a string if you want to use your own)
423
+ * @return array | false
424
+ */
425
+ public static function inputFile($file, $md5sum = true)
426
+ {
427
+ if (!file_exists($file) || !is_file($file) || !is_readable($file))
428
+ {
429
+ self::__triggerError('S3::inputFile(): Unable to open input file: '.$file, __FILE__, __LINE__);
430
+ return false;
431
+ }
432
+ return array('file' => $file, 'size' => filesize($file), 'md5sum' => $md5sum !== false ?
433
+ (is_string($md5sum) ? $md5sum : base64_encode(md5_file($file, true))) : '');
434
+ }
435
+
436
+
437
+ /**
438
+ * Create input array info for putObject() with a resource
439
+ *
440
+ * @param string $resource Input resource to read from
441
+ * @param integer $bufferSize Input byte size
442
+ * @param string $md5sum MD5 hash to send (optional)
443
+ * @return array | false
444
+ */
445
+ public static function inputResource(&$resource, $bufferSize, $md5sum = '')
446
+ {
447
+ if (!is_resource($resource) || $bufferSize < 0)
448
+ {
449
+ self::__triggerError('S3::inputResource(): Invalid resource or buffer size', __FILE__, __LINE__);
450
+ return false;
451
+ }
452
+ $input = array('size' => $bufferSize, 'md5sum' => $md5sum);
453
+ $input['fp'] =& $resource;
454
+ return $input;
455
+ }
456
+
457
+ /**
458
+ * Initiate a multi-part upload (http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadInitiate.html)
459
+ *
460
+ * @param string $bucket Bucket name
461
+ * @param string $uri Object URI
462
+ * @param constant $acl ACL constant
463
+ * @param array $metaHeaders Array of x-amz-meta-* headers
464
+ * @param array $requestHeaders Array of request headers or content type as a string
465
+ * @param constant $storageClass Storage class constant
466
+ * @return string | false
467
+ */
468
+
469
+ public static function initiateMultipartUpload ($bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD)
470
+ {
471
+
472
+ $rest = new S3Request('POST', $bucket, $uri, self::$endpoint);
473
+ $rest->setParameter('uploads','');
474
+
475
+ // Custom request headers (Content-Type, Content-Disposition, Content-Encoding)
476
+ if (is_array($requestHeaders))
477
+ foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
478
+
479
+ // Set storage class
480
+ if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class
481
+ $rest->setAmzHeader('x-amz-storage-class', $storageClass);
482
+
483
+ // Set ACL headers
484
+ $rest->setAmzHeader('x-amz-acl', $acl);
485
+ foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
486
+
487
+ // Carry out the HTTP operation
488
+ $rest->getResponse();
489
+
490
+ if ($rest->response->error === false && $rest->response->code !== 200)
491
+ $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
492
+ if ($rest->response->error !== false)
493
+ {
494
+ self::__triggerError(sprintf("S3::initiateMultipartUpload(): [%s] %s",
495
+ $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__);
496
+ return false;
497
+ } elseif (isset($rest->response->body))
498
+ {
499
+ $body = new SimpleXMLElement($rest->response->body);
500
+ return (string) $body->UploadId;
501
+ }
502
+
503
+ // It is a programming error if we reach this line
504
+ return false;
505
+
506
+ }
507
+
508
+ /**
509
+ /* Upload a part of a multi-part set (http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadUploadPart.html)
510
+ * The chunk is read into memory, so make sure that you have enough (or patch this function to work another way!)
511
+ *
512
+ * @param string $bucket Bucket name
513
+ * @param string $uri Object URI
514
+ * @param string $uploadId uploadId returned previously from initiateMultipartUpload
515
+ * @param integer $partNumber sequential part number to upload
516
+ * @param string $filePath file to upload content from
517
+ * @param integer $partSize number of bytes in each part (though final part may have fewer) - pass the same value each time (for this particular upload) - default 5Mb (which is Amazon's minimum)
518
+ * @return string (ETag) | false
519
+ */
520
+
521
+ public static function uploadPart ($bucket, $uri, $uploadId, $filePath, $partNumber, $partSize = 5242880)
522
+ {
523
+
524
+ $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint);
525
+ $rest->setParameter('partNumber', $partNumber);
526
+ $rest->setParameter('uploadId', $uploadId);
527
+
528
+ // Where to begin
529
+ $fileOffset = ($partNumber - 1 ) * $partSize;
530
+
531
+ // Download the smallest of the remaining bytes and the part size
532
+ $fileBytes = min(filesize($filePath) - $fileOffset, $partSize);
533
+ if ($fileBytes < 0) $fileBytes = 0;
534
+
535
+ $rest->setHeader('Content-Type', 'application/octet-stream');
536
+ $rest->data = "";
537
+
538
+ $handle = fopen($filePath, "rb");
539
+ if ($fileOffset >0) fseek($handle, $fileOffset);
540
+ $bytes_read = 0;
541
+
542
+ while ($fileBytes >0) {
543
+ $read = fread($handle, min($fileBytes, 32768));
544
+ $fileBytes = $fileBytes - strlen($read);
545
+ $bytes_read += strlen($read);
546
+ $rest->data = $rest->data . $read;
547
+ }
548
+
549
+ fclose($handle);
550
+
551
+ $rest->setHeader('Content-MD5', base64_encode(md5($rest->data, true)));
552
+ $rest->size = $bytes_read;
553
+
554
+ $rest = $rest->getResponse();
555
+ if ($rest->error === false && $rest->code !== 200)
556
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
557
+ if ($rest->error !== false)
558
+ {
559
+ self::__triggerError(sprintf("S3::uploadPart(): [%s] %s",
560
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
561
+ return false;
562
+ }
563
+ return $rest->headers['hash'];
564
+
565
+ }
566
+
567
+ /**
568
+ * Complete a multi-part upload (http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadComplete.html)
569
+ *
570
+ * @param string $bucket Bucket name
571
+ * @param string $uri Object URI
572
+ * @param string $uploadId uploadId returned previously from initiateMultipartUpload
573
+ * @param array $parts an ordered list of eTags of previously uploaded parts from uploadPart
574
+ * @return boolean
575
+ */
576
+
577
+ public static function completeMultipartUpload ($bucket, $uri, $uploadId, $parts)
578
+ {
579
+ $rest = new S3Request('POST', $bucket, $uri, self::$endpoint);
580
+ $rest->setParameter('uploadId', $uploadId);
581
+
582
+ $xml = "<CompleteMultipartUpload>\n";
583
+ $partno = 1;
584
+ foreach ($parts as $etag) {
585
+ $xml .= "<Part><PartNumber>$partno</PartNumber><ETag>$etag</ETag></Part>\n";
586
+ $partno++;
587
+ }
588
+ $xml .= "</CompleteMultipartUpload>";
589
+
590
+ $rest->data = $xml;
591
+ $rest->size = strlen($rest->data);
592
+ $rest->setHeader('Content-Type', 'application/xml');
593
+
594
+ $rest = $rest->getResponse();
595
+ if ($rest->error === false && $rest->code !== 200)
596
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
597
+ if ($rest->error !== false)
598
+ {
599
+ self::__triggerError(sprintf("S3::completeMultipartUpload(): [%s] %s",
600
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
601
+ return false;
602
+ }
603
+ return true;
604
+
605
+ }
606
+
607
+ /**
608
+ * Abort a multi-part upload (http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadAbort.html)
609
+ *
610
+ * @param string $bucket Bucket name
611
+ * @param string $uri Object URI
612
+ * @param string $uploadId uploadId returned previously from initiateMultipartUpload
613
+ * @return boolean
614
+ */
615
+
616
+ public static function abortMultipartUpload ($bucket, $uri, $uploadId)
617
+ {
618
+ $rest = new S3Request('DELETE', $bucket, $uri, self::$endpoint);
619
+ $rest->setParameter('uploadId', $uploadId);
620
+ $rest = $rest->getResponse();
621
+ if ($rest->error === false && $rest->code !== 204)
622
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
623
+ if ($rest->error !== false)
624
+ {
625
+ self::__triggerError(sprintf("S3::abortMultipartUpload(): [%s] %s",
626
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
627
+ return false;
628
+ }
629
+ return true;
630
+ }
631
+
632
+ /**
633
+ * Put an object
634
+ *
635
+ * @param mixed $input Input data
636
+ * @param string $bucket Bucket name
637
+ * @param string $uri Object URI
638
+ * @param constant $acl ACL constant
639
+ * @param array $metaHeaders Array of x-amz-meta-* headers
640
+ * @param array $requestHeaders Array of request headers or content type as a string
641
+ * @param constant $storageClass Storage class constant
642
+ * @return boolean
643
+ */
644
+ public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD)
645
+ {
646
+ if ($input === false) return false;
647
+ $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint);
648
+
649
+ if (!is_array($input)) $input = array(
650
+ 'data' => $input, 'size' => strlen($input),
651
+ 'md5sum' => base64_encode(md5($input, true))
652
+ );
653
+
654
+ // Data
655
+ if (isset($input['fp']))
656
+ $rest->fp =& $input['fp'];
657
+ elseif (isset($input['file']))
658
+ $rest->fp = @fopen($input['file'], 'rb');
659
+ elseif (isset($input['data']))
660
+ $rest->data = $input['data'];
661
+
662
+ // Content-Length (required)
663
+ if (isset($input['size']) && $input['size'] >= 0)
664
+ $rest->size = $input['size'];
665
+ else {
666
+ if (isset($input['file']))
667
+ $rest->size = filesize($input['file']);
668
+ elseif (isset($input['data']))
669
+ $rest->size = strlen($input['data']);
670
+ }
671
+
672
+ // Custom request headers (Content-Type, Content-Disposition, Content-Encoding)
673
+ if (is_array($requestHeaders))
674
+ foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
675
+ elseif (is_string($requestHeaders)) // Support for legacy contentType parameter
676
+ $input['type'] = $requestHeaders;
677
+
678
+ // Content-Type
679
+ if (!isset($input['type']))
680
+ {
681
+ if (isset($requestHeaders['Content-Type']))
682
+ $input['type'] =& $requestHeaders['Content-Type'];
683
+ elseif (isset($input['file']))
684
+ $input['type'] = self::__getMimeType($input['file']);
685
+ else
686
+ $input['type'] = 'application/octet-stream';
687
+ }
688
+
689
+ if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class
690
+ $rest->setAmzHeader('x-amz-storage-class', $storageClass);
691
+
692
+ // We need to post with Content-Length and Content-Type, MD5 is optional
693
+ if ($rest->size >= 0 && ($rest->fp !== false || $rest->data !== false))
694
+ {
695
+ $rest->setHeader('Content-Type', $input['type']);
696
+ if (isset($input['md5sum'])) $rest->setHeader('Content-MD5', $input['md5sum']);
697
+
698
+ $rest->setAmzHeader('x-amz-acl', $acl);
699
+ foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
700
+ $rest->getResponse();
701
+ } else
702
+ $rest->response->error = array('code' => 0, 'message' => 'Missing input parameters');
703
+
704
+ if ($rest->response->error === false && $rest->response->code !== 200)
705
+ $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
706
+ if ($rest->response->error !== false)
707
+ {
708
+ self::__triggerError(sprintf("S3::putObject(): [%s] %s",
709
+ $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__);
710
+ return false;
711
+ }
712
+ return true;
713
+ }
714
+
715
+
716
+ /**
717
+ * Put an object from a file (legacy function)
718
+ *
719
+ * @param string $file Input file path
720
+ * @param string $bucket Bucket name
721
+ * @param string $uri Object URI
722
+ * @param constant $acl ACL constant
723
+ * @param array $metaHeaders Array of x-amz-meta-* headers
724
+ * @param string $contentType Content type
725
+ * @return boolean
726
+ */
727
+ public static function putObjectFile($file, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = null)
728
+ {
729
+ return self::putObject(self::inputFile($file), $bucket, $uri, $acl, $metaHeaders, $contentType);
730
+ }
731
+
732
+
733
+ /**
734
+ * Put an object from a string (legacy function)
735
+ *
736
+ * @param string $string Input data
737
+ * @param string $bucket Bucket name
738
+ * @param string $uri Object URI
739
+ * @param constant $acl ACL constant
740
+ * @param array $metaHeaders Array of x-amz-meta-* headers
741
+ * @param string $contentType Content type
742
+ * @return boolean
743
+ */
744
+ public static function putObjectString($string, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = 'text/plain')
745
+ {
746
+ return self::putObject($string, $bucket, $uri, $acl, $metaHeaders, $contentType);
747
+ }
748
+
749
+
750
+ /**
751
+ * Get an object
752
+ *
753
+ * @param string $bucket Bucket name
754
+ * @param string $uri Object URI
755
+ * @param mixed $saveTo Filename or resource to write to
756
+ * @return mixed
757
+ */
758
+ public static function getObject($bucket, $uri, $saveTo = false)
759
+ {
760
+ $rest = new S3Request('GET', $bucket, $uri, self::$endpoint);
761
+ if ($saveTo !== false)
762
+ {
763
+ if (is_resource($saveTo))
764
+ $rest->fp =& $saveTo;
765
+ else
766
+ if (($rest->fp = @fopen($saveTo, 'wb')) !== false)
767
+ $rest->file = realpath($saveTo);
768
+ else
769
+ $rest->response->error = array('code' => 0, 'message' => 'Unable to open save file for writing: '.$saveTo);
770
+ }
771
+ if ($rest->response->error === false) $rest->getResponse();
772
+
773
+ if ($rest->response->error === false && $rest->response->code !== 200)
774
+ $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
775
+ if ($rest->response->error !== false)
776
+ {
777
+ self::__triggerError(sprintf("S3::getObject({$bucket}, {$uri}): [%s] %s",
778
+ $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__);
779
+ return false;
780
+ }
781
+ return $rest->response;
782
+ }
783
+
784
+
785
+ /**
786
+ * Get object information
787
+ *
788
+ * @param string $bucket Bucket name
789
+ * @param string $uri Object URI
790
+ * @param boolean $returnInfo Return response information
791
+ * @return mixed | false
792
+ */
793
+ public static function getObjectInfo($bucket, $uri, $returnInfo = true)
794
+ {
795
+ $rest = new S3Request('HEAD', $bucket, $uri, self::$endpoint);
796
+ $rest = $rest->getResponse();
797
+ if ($rest->error === false && ($rest->code !== 200 && $rest->code !== 404))
798
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
799
+ if ($rest->error !== false)
800
+ {
801
+ self::__triggerError(sprintf("S3::getObjectInfo({$bucket}, {$uri}): [%s] %s",
802
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
803
+ return false;
804
+ }
805
+ return $rest->code == 200 ? $returnInfo ? $rest->headers : true : false;
806
+ }
807
+
808
+
809
+ /**
810
+ * Copy an object
811
+ *
812
+ * @param string $bucket Source bucket name
813
+ * @param string $uri Source object URI
814
+ * @param string $bucket Destination bucket name
815
+ * @param string $uri Destination object URI
816
+ * @param constant $acl ACL constant
817
+ * @param array $metaHeaders Optional array of x-amz-meta-* headers
818
+ * @param array $requestHeaders Optional array of request headers (content type, disposition, etc.)
819
+ * @param constant $storageClass Storage class constant
820
+ * @return mixed | false
821
+ */
822
+ public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD)
823
+ {
824
+ $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint);
825
+ $rest->setHeader('Content-Length', 0);
826
+ foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
827
+ foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
828
+ if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class
829
+ $rest->setAmzHeader('x-amz-storage-class', $storageClass);
830
+ $rest->setAmzHeader('x-amz-acl', $acl);
831
+ $rest->setAmzHeader('x-amz-copy-source', sprintf('/%s/%s', $srcBucket, rawurlencode($srcUri)));
832
+ if (sizeof($requestHeaders) > 0 || sizeof($metaHeaders) > 0)
833
+ $rest->setAmzHeader('x-amz-metadata-directive', 'REPLACE');
834
+
835
+ $rest = $rest->getResponse();
836
+ if ($rest->error === false && $rest->code !== 200)
837
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
838
+ if ($rest->error !== false)
839
+ {
840
+ self::__triggerError(sprintf("S3::copyObject({$srcBucket}, {$srcUri}, {$bucket}, {$uri}): [%s] %s",
841
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
842
+ return false;
843
+ }
844
+ return isset($rest->body->LastModified, $rest->body->ETag) ? array(
845
+ 'time' => strtotime((string)$rest->body->LastModified),
846
+ 'hash' => substr((string)$rest->body->ETag, 1, -1)
847
+ ) : false;
848
+ }
849
+
850
+
851
+ /**
852
+ * Set logging for a bucket
853
+ *
854
+ * @param string $bucket Bucket name
855
+ * @param string $targetBucket Target bucket (where logs are stored)
856
+ * @param string $targetPrefix Log prefix (e,g; domain.com-)
857
+ * @return boolean
858
+ */
859
+ public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = null)
860
+ {
861
+ // The S3 log delivery group has to be added to the target bucket's ACP
862
+ if ($targetBucket !== null && ($acp = self::getAccessControlPolicy($targetBucket, '')) !== false)
863
+ {
864
+ // Only add permissions to the target bucket when they do not exist
865
+ $aclWriteSet = false;
866
+ $aclReadSet = false;
867
+ foreach ($acp['acl'] as $acl)
868
+ if ($acl['type'] == 'Group' && $acl['uri'] == 'http://acs.amazonaws.com/groups/s3/LogDelivery')
869
+ {
870
+ if ($acl['permission'] == 'WRITE') $aclWriteSet = true;
871
+ elseif ($acl['permission'] == 'READ_ACP') $aclReadSet = true;
872
+ }
873
+ if (!$aclWriteSet) $acp['acl'][] = array(
874
+ 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'WRITE'
875
+ );
876
+ if (!$aclReadSet) $acp['acl'][] = array(
877
+ 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'READ_ACP'
878
+ );
879
+ if (!$aclReadSet || !$aclWriteSet) self::setAccessControlPolicy($targetBucket, '', $acp);
880
+ }
881
+
882
+ $dom = new DOMDocument;
883
+ $bucketLoggingStatus = $dom->createElement('BucketLoggingStatus');
884
+ $bucketLoggingStatus->setAttribute('xmlns', 'http://s3.amazonaws.com/doc/2006-03-01/');
885
+ if ($targetBucket !== null)
886
+ {
887
+ if ($targetPrefix == null) $targetPrefix = $bucket . '-';
888
+ $loggingEnabled = $dom->createElement('LoggingEnabled');
889
+ $loggingEnabled->appendChild($dom->createElement('TargetBucket', $targetBucket));
890
+ $loggingEnabled->appendChild($dom->createElement('TargetPrefix', $targetPrefix));
891
+ // TODO: Add TargetGrants?
892
+ $bucketLoggingStatus->appendChild($loggingEnabled);
893
+ }
894
+ $dom->appendChild($bucketLoggingStatus);
895
+
896
+ $rest = new S3Request('PUT', $bucket, '', self::$endpoint);
897
+ $rest->setParameter('logging', null);
898
+ $rest->data = $dom->saveXML();
899
+ $rest->size = strlen($rest->data);
900
+ $rest->setHeader('Content-Type', 'application/xml');
901
+ $rest = $rest->getResponse();
902
+ if ($rest->error === false && $rest->code !== 200)
903
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
904
+ if ($rest->error !== false)
905
+ {
906
+ self::__triggerError(sprintf("S3::setBucketLogging({$bucket}, {$targetBucket}): [%s] %s",
907
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
908
+ return false;
909
+ }
910
+ return true;
911
+ }
912
+
913
+
914
+ /**
915
+ * Get logging status for a bucket
916
+ *
917
+ * This will return false if logging is not enabled.
918
+ * Note: To enable logging, you also need to grant write access to the log group
919
+ *
920
+ * @param string $bucket Bucket name
921
+ * @return array | false
922
+ */
923
+ public static function getBucketLogging($bucket)
924
+ {
925
+ $rest = new S3Request('GET', $bucket, '', self::$endpoint);
926
+ $rest->setParameter('logging', null);
927
+ $rest = $rest->getResponse();
928
+ if ($rest->error === false && $rest->code !== 200)
929
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
930
+ if ($rest->error !== false)
931
+ {
932
+ self::__triggerError(sprintf("S3::getBucketLogging({$bucket}): [%s] %s",
933
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
934
+ return false;
935
+ }
936
+ if (!isset($rest->body->LoggingEnabled)) return false; // No logging
937
+ return array(
938
+ 'targetBucket' => (string)$rest->body->LoggingEnabled->TargetBucket,
939
+ 'targetPrefix' => (string)$rest->body->LoggingEnabled->TargetPrefix,
940
+ );
941
+ }
942
+
943
+
944
+ /**
945
+ * Disable bucket logging
946
+ *
947
+ * @param string $bucket Bucket name
948
+ * @return boolean
949
+ */
950
+ public static function disableBucketLogging($bucket)
951
+ {
952
+ return self::setBucketLogging($bucket, null);
953
+ }
954
+
955
+
956
+ /**
957
+ * Get a bucket's location
958
+ *
959
+ * @param string $bucket Bucket name
960
+ * @return string | false
961
+ */
962
+ public static function getBucketLocation($bucket)
963
+ {
964
+ $rest = new S3Request('GET', $bucket, '', self::$endpoint);
965
+ $rest->setParameter('location', null);
966
+ $rest = $rest->getResponse();
967
+ if ($rest->error === false && $rest->code !== 200)
968
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
969
+ if ($rest->error !== false)
970
+ {
971
+ self::__triggerError(sprintf("S3::getBucketLocation({$bucket}): [%s] %s",
972
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
973
+ return false;
974
+ }
975
+ return (isset($rest->body[0]) && (string)$rest->body[0] !== '') ? (string)$rest->body[0] : 'US';
976
+ }
977
+
978
+
979
+ /**
980
+ * Set object or bucket Access Control Policy
981
+ *
982
+ * @param string $bucket Bucket name
983
+ * @param string $uri Object URI
984
+ * @param array $acp Access Control Policy Data (same as the data returned from getAccessControlPolicy)
985
+ * @return boolean
986
+ */
987
+ public static function setAccessControlPolicy($bucket, $uri = '', $acp = array())
988
+ {
989
+ $dom = new DOMDocument;
990
+ $dom->formatOutput = true;
991
+ $accessControlPolicy = $dom->createElement('AccessControlPolicy');
992
+ $accessControlList = $dom->createElement('AccessControlList');
993
+
994
+ // It seems the owner has to be passed along too
995
+ $owner = $dom->createElement('Owner');
996
+ $owner->appendChild($dom->createElement('ID', $acp['owner']['id']));
997
+ $owner->appendChild($dom->createElement('DisplayName', $acp['owner']['name']));
998
+ $accessControlPolicy->appendChild($owner);
999
+
1000
+ foreach ($acp['acl'] as $g)
1001
+ {
1002
+ $grant = $dom->createElement('Grant');
1003
+ $grantee = $dom->createElement('Grantee');
1004
+ $grantee->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
1005
+ if (isset($g['id']))
1006
+ { // CanonicalUser (DisplayName is omitted)
1007
+ $grantee->setAttribute('xsi:type', 'CanonicalUser');
1008
+ $grantee->appendChild($dom->createElement('ID', $g['id']));
1009
+ }
1010
+ elseif (isset($g['email']))
1011
+ { // AmazonCustomerByEmail
1012
+ $grantee->setAttribute('xsi:type', 'AmazonCustomerByEmail');
1013
+ $grantee->appendChild($dom->createElement('EmailAddress', $g['email']));
1014
+ }
1015
+ elseif ($g['type'] == 'Group')
1016
+ { // Group
1017
+ $grantee->setAttribute('xsi:type', 'Group');
1018
+ $grantee->appendChild($dom->createElement('URI', $g['uri']));
1019
+ }
1020
+ $grant->appendChild($grantee);
1021
+ $grant->appendChild($dom->createElement('Permission', $g['permission']));
1022
+ $accessControlList->appendChild($grant);
1023
+ }
1024
+
1025
+ $accessControlPolicy->appendChild($accessControlList);
1026
+ $dom->appendChild($accessControlPolicy);
1027
+
1028
+ $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint);
1029
+ $rest->setParameter('acl', null);
1030
+ $rest->data = $dom->saveXML();
1031
+ $rest->size = strlen($rest->data);
1032
+ $rest->setHeader('Content-Type', 'application/xml');
1033
+ $rest = $rest->getResponse();
1034
+ if ($rest->error === false && $rest->code !== 200)
1035
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1036
+ if ($rest->error !== false)
1037
+ {
1038
+ self::__triggerError(sprintf("S3::setAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
1039
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1040
+ return false;
1041
+ }
1042
+ return true;
1043
+ }
1044
+
1045
+
1046
+ /**
1047
+ * Get object or bucket Access Control Policy
1048
+ *
1049
+ * @param string $bucket Bucket name
1050
+ * @param string $uri Object URI
1051
+ * @return mixed | false
1052
+ */
1053
+ public static function getAccessControlPolicy($bucket, $uri = '')
1054
+ {
1055
+ $rest = new S3Request('GET', $bucket, $uri, self::$endpoint);
1056
+ $rest->setParameter('acl', null);
1057
+ $rest = $rest->getResponse();
1058
+ if ($rest->error === false && $rest->code !== 200)
1059
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1060
+ if ($rest->error !== false)
1061
+ {
1062
+ self::__triggerError(sprintf("S3::getAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
1063
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1064
+ return false;
1065
+ }
1066
+
1067
+ $acp = array();
1068
+ if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName))
1069
+ $acp['owner'] = array(
1070
+ 'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->DisplayName
1071
+ );
1072
+
1073
+ if (isset($rest->body->AccessControlList))
1074
+ {
1075
+ $acp['acl'] = array();
1076
+ foreach ($rest->body->AccessControlList->Grant as $grant)
1077
+ {
1078
+ foreach ($grant->Grantee as $grantee)
1079
+ {
1080
+ if (isset($grantee->ID, $grantee->DisplayName)) // CanonicalUser
1081
+ $acp['acl'][] = array(
1082
+ 'type' => 'CanonicalUser',
1083
+ 'id' => (string)$grantee->ID,
1084
+ 'name' => (string)$grantee->DisplayName,
1085
+ 'permission' => (string)$grant->Permission
1086
+ );
1087
+ elseif (isset($grantee->EmailAddress)) // AmazonCustomerByEmail
1088
+ $acp['acl'][] = array(
1089
+ 'type' => 'AmazonCustomerByEmail',
1090
+ 'email' => (string)$grantee->EmailAddress,
1091
+ 'permission' => (string)$grant->Permission
1092
+ );
1093
+ elseif (isset($grantee->URI)) // Group
1094
+ $acp['acl'][] = array(
1095
+ 'type' => 'Group',
1096
+ 'uri' => (string)$grantee->URI,
1097
+ 'permission' => (string)$grant->Permission
1098
+ );
1099
+ else continue;
1100
+ }
1101
+ }
1102
+ }
1103
+ return $acp;
1104
+ }
1105
+
1106
+
1107
+ /**
1108
+ * Delete an object
1109
+ *
1110
+ * @param string $bucket Bucket name
1111
+ * @param string $uri Object URI
1112
+ * @return boolean
1113
+ */
1114
+ public static function deleteObject($bucket, $uri)
1115
+ {
1116
+ $rest = new S3Request('DELETE', $bucket, $uri, self::$endpoint);
1117
+ $rest = $rest->getResponse();
1118
+ if ($rest->error === false && $rest->code !== 204)
1119
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1120
+ if ($rest->error !== false)
1121
+ {
1122
+ self::__triggerError(sprintf("S3::deleteObject(): [%s] %s",
1123
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1124
+ return false;
1125
+ }
1126
+ return true;
1127
+ }
1128
+
1129
+
1130
+ /**
1131
+ * Get a query string authenticated URL
1132
+ *
1133
+ * @param string $bucket Bucket name
1134
+ * @param string $uri Object URI
1135
+ * @param integer $lifetime Lifetime in seconds
1136
+ * @param boolean $hostBucket Use the bucket name as the hostname
1137
+ * @param boolean $https Use HTTPS ($hostBucket should be false for SSL verification)
1138
+ * @return string
1139
+ */
1140
+ public static function getAuthenticatedURL($bucket, $uri, $lifetime, $hostBucket = false, $https = false)
1141
+ {
1142
+ $expires = time() + $lifetime;
1143
+ $uri = str_replace(array('%2F', '%2B'), array('/', '+'), rawurlencode($uri));
1144
+ return sprintf(($https ? 'https' : 'http').'://%s/%s?AWSAccessKeyId=%s&Expires=%u&Signature=%s',
1145
+ // $hostBucket ? $bucket : $bucket.'.s3.amazonaws.com', $uri, self::$__accessKey, $expires,
1146
+ $hostBucket ? $bucket : 's3.amazonaws.com/'.$bucket, $uri, self::$__accessKey, $expires,
1147
+ urlencode(self::__getHash("GET\n\n\n{$expires}\n/{$bucket}/{$uri}")));
1148
+ }
1149
+
1150
+
1151
+ /**
1152
+ * Get a CloudFront signed policy URL
1153
+ *
1154
+ * @param array $policy Policy
1155
+ * @return string
1156
+ */
1157
+ public static function getSignedPolicyURL($policy)
1158
+ {
1159
+ $data = json_encode($policy);
1160
+ $signature = '';
1161
+ if (!openssl_sign($data, $signature, self::$__signingKeyResource)) return false;
1162
+
1163
+ $encoded = str_replace(array('+', '='), array('-', '_', '~'), base64_encode($data));
1164
+ $signature = str_replace(array('+', '='), array('-', '_', '~'), base64_encode($signature));
1165
+
1166
+ $url = $policy['Statement'][0]['Resource'] . '?';
1167
+ foreach (array('Policy' => $encoded, 'Signature' => $signature, 'Key-Pair-Id' => self::$__signingKeyPairId) as $k => $v)
1168
+ $url .= $k.'='.str_replace('%2F', '/', rawurlencode($v)).'&';
1169
+ return substr($url, 0, -1);
1170
+ }
1171
+
1172
+
1173
+ /**
1174
+ * Get a CloudFront canned policy URL
1175
+ *
1176
+ * @param string $string URL to sign
1177
+ * @param integer $lifetime URL lifetime
1178
+ * @return string
1179
+ */
1180
+ public static function getSignedCannedURL($url, $lifetime)
1181
+ {
1182
+ return self::getSignedPolicyURL(array(
1183
+ 'Statement' => array(
1184
+ array('Resource' => $url, 'Condition' => array(
1185
+ 'DateLessThan' => array('AWS:EpochTime' => time() + $lifetime)
1186
+ ))
1187
+ )
1188
+ ));
1189
+ }
1190
+
1191
+
1192
+ /**
1193
+ * Get upload POST parameters for form uploads
1194
+ *
1195
+ * @param string $bucket Bucket name
1196
+ * @param string $uriPrefix Object URI prefix
1197
+ * @param constant $acl ACL constant
1198
+ * @param integer $lifetime Lifetime in seconds
1199
+ * @param integer $maxFileSize Maximum filesize in bytes (default 5MB)
1200
+ * @param string $successRedirect Redirect URL or 200 / 201 status code
1201
+ * @param array $amzHeaders Array of x-amz-meta-* headers
1202
+ * @param array $headers Array of request headers or content type as a string
1203
+ * @param boolean $flashVars Includes additional "Filename" variable posted by Flash
1204
+ * @return object
1205
+ */
1206
+ public static function getHttpUploadPostParams($bucket, $uriPrefix = '', $acl = self::ACL_PRIVATE, $lifetime = 3600,
1207
+ $maxFileSize = 5242880, $successRedirect = "201", $amzHeaders = array(), $headers = array(), $flashVars = false)
1208
+ {
1209
+ // Create policy object
1210
+ $policy = new stdClass;
1211
+ $policy->expiration = gmdate('Y-m-d\TH:i:s\Z', (time() + $lifetime));
1212
+ $policy->conditions = array();
1213
+ $obj = new stdClass; $obj->bucket = $bucket; array_push($policy->conditions, $obj);
1214
+ $obj = new stdClass; $obj->acl = $acl; array_push($policy->conditions, $obj);
1215
+
1216
+ $obj = new stdClass; // 200 for non-redirect uploads
1217
+ if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201)))
1218
+ $obj->success_action_status = (string)$successRedirect;
1219
+ else // URL
1220
+ $obj->success_action_redirect = $successRedirect;
1221
+ array_push($policy->conditions, $obj);
1222
+
1223
+ if ($acl !== self::ACL_PUBLIC_READ)
1224
+ array_push($policy->conditions, array('eq', '$acl', $acl));
1225
+
1226
+ array_push($policy->conditions, array('starts-with', '$key', $uriPrefix));
1227
+ if ($flashVars) array_push($policy->conditions, array('starts-with', '$Filename', ''));
1228
+ foreach (array_keys($headers) as $headerKey)
1229
+ array_push($policy->conditions, array('starts-with', '$'.$headerKey, ''));
1230
+ foreach ($amzHeaders as $headerKey => $headerVal)
1231
+ {
1232
+ $obj = new stdClass;
1233
+ $obj->{$headerKey} = (string)$headerVal;
1234
+ array_push($policy->conditions, $obj);
1235
+ }
1236
+ array_push($policy->conditions, array('content-length-range', 0, $maxFileSize));
1237
+ $policy = base64_encode(str_replace('\/', '/', json_encode($policy)));
1238
+
1239
+ // Create parameters
1240
+ $params = new stdClass;
1241
+ $params->AWSAccessKeyId = self::$__accessKey;
1242
+ $params->key = $uriPrefix.'${filename}';
1243
+ $params->acl = $acl;
1244
+ $params->policy = $policy; unset($policy);
1245
+ $params->signature = self::__getHash($params->policy);
1246
+ if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201)))
1247
+ $params->success_action_status = (string)$successRedirect;
1248
+ else
1249
+ $params->success_action_redirect = $successRedirect;
1250
+ foreach ($headers as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal;
1251
+ foreach ($amzHeaders as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal;
1252
+ return $params;
1253
+ }
1254
+
1255
+
1256
+ /**
1257
+ * Create a CloudFront distribution
1258
+ *
1259
+ * @param string $bucket Bucket name
1260
+ * @param boolean $enabled Enabled (true/false)
1261
+ * @param array $cnames Array containing CNAME aliases
1262
+ * @param string $comment Use the bucket name as the hostname
1263
+ * @param string $defaultRootObject Default root object
1264
+ * @param string $originAccessIdentity Origin access identity
1265
+ * @param array $trustedSigners Array of trusted signers
1266
+ * @return array | false
1267
+ */
1268
+ public static function createDistribution($bucket, $enabled = true, $cnames = array(), $comment = null, $defaultRootObject = null, $originAccessIdentity = null, $trustedSigners = array())
1269
+ {
1270
+ if (!extension_loaded('openssl'))
1271
+ {
1272
+ self::__triggerError(sprintf("S3::createDistribution({$bucket}, ".(int)$enabled.", [], '$comment'): %s",
1273
+ "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1274
+ return false;
1275
+ }
1276
+ $useSSL = self::$useSSL;
1277
+
1278
+ self::$useSSL = true; // CloudFront requires SSL
1279
+ $rest = new S3Request('POST', '', '2010-11-01/distribution', 'cloudfront.amazonaws.com');
1280
+ $rest->data = self::__getCloudFrontDistributionConfigXML(
1281
+ $bucket.'.s3.amazonaws.com',
1282
+ $enabled,
1283
+ (string)$comment,
1284
+ (string)microtime(true),
1285
+ $cnames,
1286
+ $defaultRootObject,
1287
+ $originAccessIdentity,
1288
+ $trustedSigners
1289
+ );
1290
+
1291
+ $rest->size = strlen($rest->data);
1292
+ $rest->setHeader('Content-Type', 'application/xml');
1293
+ $rest = self::__getCloudFrontResponse($rest);
1294
+
1295
+ self::$useSSL = $useSSL;
1296
+
1297
+ if ($rest->error === false && $rest->code !== 201)
1298
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1299
+ if ($rest->error !== false)
1300
+ {
1301
+ self::__triggerError(sprintf("S3::createDistribution({$bucket}, ".(int)$enabled.", [], '$comment'): [%s] %s",
1302
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1303
+ return false;
1304
+ } elseif ($rest->body instanceof SimpleXMLElement)
1305
+ return self::__parseCloudFrontDistributionConfig($rest->body);
1306
+ return false;
1307
+ }
1308
+
1309
+
1310
+ /**
1311
+ * Get CloudFront distribution info
1312
+ *
1313
+ * @param string $distributionId Distribution ID from listDistributions()
1314
+ * @return array | false
1315
+ */
1316
+ public static function getDistribution($distributionId)
1317
+ {
1318
+ if (!extension_loaded('openssl'))
1319
+ {
1320
+ self::__triggerError(sprintf("S3::getDistribution($distributionId): %s",
1321
+ "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1322
+ return false;
1323
+ }
1324
+ $useSSL = self::$useSSL;
1325
+
1326
+ self::$useSSL = true; // CloudFront requires SSL
1327
+ $rest = new S3Request('GET', '', '2010-11-01/distribution/'.$distributionId, 'cloudfront.amazonaws.com');
1328
+ $rest = self::__getCloudFrontResponse($rest);
1329
+
1330
+ self::$useSSL = $useSSL;
1331
+
1332
+ if ($rest->error === false && $rest->code !== 200)
1333
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1334
+ if ($rest->error !== false)
1335
+ {
1336
+ self::__triggerError(sprintf("S3::getDistribution($distributionId): [%s] %s",
1337
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1338
+ return false;
1339
+ }
1340
+ elseif ($rest->body instanceof SimpleXMLElement)
1341
+ {
1342
+ $dist = self::__parseCloudFrontDistributionConfig($rest->body);
1343
+ $dist['hash'] = $rest->headers['hash'];
1344
+ $dist['id'] = $distributionId;
1345
+ return $dist;
1346
+ }
1347
+ return false;
1348
+ }
1349
+
1350
+
1351
+ /**
1352
+ * Update a CloudFront distribution
1353
+ *
1354
+ * @param array $dist Distribution array info identical to output of getDistribution()
1355
+ * @return array | false
1356
+ */
1357
+ public static function updateDistribution($dist)
1358
+ {
1359
+ if (!extension_loaded('openssl'))
1360
+ {
1361
+ self::__triggerError(sprintf("S3::updateDistribution({$dist['id']}): %s",
1362
+ "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1363
+ return false;
1364
+ }
1365
+
1366
+ $useSSL = self::$useSSL;
1367
+
1368
+ self::$useSSL = true; // CloudFront requires SSL
1369
+ $rest = new S3Request('PUT', '', '2010-11-01/distribution/'.$dist['id'].'/config', 'cloudfront.amazonaws.com');
1370
+ $rest->data = self::__getCloudFrontDistributionConfigXML(
1371
+ $dist['origin'],
1372
+ $dist['enabled'],
1373
+ $dist['comment'],
1374
+ $dist['callerReference'],
1375
+ $dist['cnames'],
1376
+ $dist['defaultRootObject'],
1377
+ $dist['originAccessIdentity'],
1378
+ $dist['trustedSigners']
1379
+ );
1380
+
1381
+ $rest->size = strlen($rest->data);
1382
+ $rest->setHeader('If-Match', $dist['hash']);
1383
+ $rest = self::__getCloudFrontResponse($rest);
1384
+
1385
+ self::$useSSL = $useSSL;
1386
+
1387
+ if ($rest->error === false && $rest->code !== 200)
1388
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1389
+ if ($rest->error !== false)
1390
+ {
1391
+ self::__triggerError(sprintf("S3::updateDistribution({$dist['id']}): [%s] %s",
1392
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1393
+ return false;
1394
+ } else {
1395
+ $dist = self::__parseCloudFrontDistributionConfig($rest->body);
1396
+ $dist['hash'] = $rest->headers['hash'];
1397
+ return $dist;
1398
+ }
1399
+ return false;
1400
+ }
1401
+
1402
+
1403
+ /**
1404
+ * Delete a CloudFront distribution
1405
+ *
1406
+ * @param array $dist Distribution array info identical to output of getDistribution()
1407
+ * @return boolean
1408
+ */
1409
+ public static function deleteDistribution($dist)
1410
+ {
1411
+ if (!extension_loaded('openssl'))
1412
+ {
1413
+ self::__triggerError(sprintf("S3::deleteDistribution({$dist['id']}): %s",
1414
+ "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1415
+ return false;
1416
+ }
1417
+
1418
+ $useSSL = self::$useSSL;
1419
+
1420
+ self::$useSSL = true; // CloudFront requires SSL
1421
+ $rest = new S3Request('DELETE', '', '2008-06-30/distribution/'.$dist['id'], 'cloudfront.amazonaws.com');
1422
+ $rest->setHeader('If-Match', $dist['hash']);
1423
+ $rest = self::__getCloudFrontResponse($rest);
1424
+
1425
+ self::$useSSL = $useSSL;
1426
+
1427
+ if ($rest->error === false && $rest->code !== 204)
1428
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1429
+ if ($rest->error !== false)
1430
+ {
1431
+ self::__triggerError(sprintf("S3::deleteDistribution({$dist['id']}): [%s] %s",
1432
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1433
+ return false;
1434
+ }
1435
+ return true;
1436
+ }
1437
+
1438
+
1439
+ /**
1440
+ * Get a list of CloudFront distributions
1441
+ *
1442
+ * @return array
1443
+ */
1444
+ public static function listDistributions()
1445
+ {
1446
+ if (!extension_loaded('openssl'))
1447
+ {
1448
+ self::__triggerError(sprintf("S3::listDistributions(): [%s] %s",
1449
+ "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1450
+ return false;
1451
+ }
1452
+
1453
+ $useSSL = self::$useSSL;
1454
+ self::$useSSL = true; // CloudFront requires SSL
1455
+ $rest = new S3Request('GET', '', '2010-11-01/distribution', 'cloudfront.amazonaws.com');
1456
+ $rest = self::__getCloudFrontResponse($rest);
1457
+ self::$useSSL = $useSSL;
1458
+
1459
+ if ($rest->error === false && $rest->code !== 200)
1460
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1461
+ if ($rest->error !== false)
1462
+ {
1463
+ self::__triggerError(sprintf("S3::listDistributions(): [%s] %s",
1464
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1465
+ return false;
1466
+ }
1467
+ elseif ($rest->body instanceof SimpleXMLElement && isset($rest->body->DistributionSummary))
1468
+ {
1469
+ $list = array();
1470
+ if (isset($rest->body->Marker, $rest->body->MaxItems, $rest->body->IsTruncated))
1471
+ {
1472
+ //$info['marker'] = (string)$rest->body->Marker;
1473
+ //$info['maxItems'] = (int)$rest->body->MaxItems;
1474
+ //$info['isTruncated'] = (string)$rest->body->IsTruncated == 'true' ? true : false;
1475
+ }
1476
+ foreach ($rest->body->DistributionSummary as $summary)
1477
+ $list[(string)$summary->Id] = self::__parseCloudFrontDistributionConfig($summary);
1478
+
1479
+ return $list;
1480
+ }
1481
+ return array();
1482
+ }
1483
+
1484
+ /**
1485
+ * List CloudFront Origin Access Identities
1486
+ *
1487
+ * @return array
1488
+ */
1489
+ public static function listOriginAccessIdentities()
1490
+ {
1491
+ if (!extension_loaded('openssl'))
1492
+ {
1493
+ self::__triggerError(sprintf("S3::listOriginAccessIdentities(): [%s] %s",
1494
+ "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1495
+ return false;
1496
+ }
1497
+
1498
+ self::$useSSL = true; // CloudFront requires SSL
1499
+ $rest = new S3Request('GET', '', '2010-11-01/origin-access-identity/cloudfront', 'cloudfront.amazonaws.com');
1500
+ $rest = self::__getCloudFrontResponse($rest);
1501
+ $useSSL = self::$useSSL;
1502
+
1503
+ if ($rest->error === false && $rest->code !== 200)
1504
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1505
+ if ($rest->error !== false)
1506
+ {
1507
+ trigger_error(sprintf("S3::listOriginAccessIdentities(): [%s] %s",
1508
+ $rest->error['code'], $rest->error['message']), E_USER_WARNING);
1509
+ return false;
1510
+ }
1511
+
1512
+ if (isset($rest->body->CloudFrontOriginAccessIdentitySummary))
1513
+ {
1514
+ $identities = array();
1515
+ foreach ($rest->body->CloudFrontOriginAccessIdentitySummary as $identity)
1516
+ if (isset($identity->S3CanonicalUserId))
1517
+ $identities[(string)$identity->Id] = array('id' => (string)$identity->Id, 's3CanonicalUserId' => (string)$identity->S3CanonicalUserId);
1518
+ return $identities;
1519
+ }
1520
+ return false;
1521
+ }
1522
+
1523
+
1524
+ /**
1525
+ * Invalidate objects in a CloudFront distribution
1526
+ *
1527
+ * Thanks to Martin Lindkvist for S3::invalidateDistribution()
1528
+ *
1529
+ * @param string $distributionId Distribution ID from listDistributions()
1530
+ * @param array $paths Array of object paths to invalidate
1531
+ * @return boolean
1532
+ */
1533
+ public static function invalidateDistribution($distributionId, $paths)
1534
+ {
1535
+ if (!extension_loaded('openssl'))
1536
+ {
1537
+ self::__triggerError(sprintf("S3::invalidateDistribution(): [%s] %s",
1538
+ "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1539
+ return false;
1540
+ }
1541
+
1542
+ $useSSL = self::$useSSL;
1543
+ self::$useSSL = true; // CloudFront requires SSL
1544
+ $rest = new S3Request('POST', '', '2010-08-01/distribution/'.$distributionId.'/invalidation', 'cloudfront.amazonaws.com');
1545
+ $rest->data = self::__getCloudFrontInvalidationBatchXML($paths, (string)microtime(true));
1546
+ $rest->size = strlen($rest->data);
1547
+ $rest = self::__getCloudFrontResponse($rest);
1548
+ self::$useSSL = $useSSL;
1549
+
1550
+ if ($rest->error === false && $rest->code !== 201)
1551
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1552
+ if ($rest->error !== false)
1553
+ {
1554
+ trigger_error(sprintf("S3::invalidate('{$distributionId}',{$paths}): [%s] %s",
1555
+ $rest->error['code'], $rest->error['message']), E_USER_WARNING);
1556
+ return false;
1557
+ }
1558
+ return true;
1559
+ }
1560
+
1561
+
1562
+ /**
1563
+ * Get a InvalidationBatch DOMDocument
1564
+ *
1565
+ * @internal Used to create XML in invalidateDistribution()
1566
+ * @param array $paths Paths to objects to invalidateDistribution
1567
+ * @return string
1568
+ */
1569
+ private static function __getCloudFrontInvalidationBatchXML($paths, $callerReference = '0') {
1570
+ $dom = new DOMDocument('1.0', 'UTF-8');
1571
+ $dom->formatOutput = true;
1572
+ $invalidationBatch = $dom->createElement('InvalidationBatch');
1573
+ foreach ($paths as $path)
1574
+ $invalidationBatch->appendChild($dom->createElement('Path', $path));
1575
+
1576
+ $invalidationBatch->appendChild($dom->createElement('CallerReference', $callerReference));
1577
+ $dom->appendChild($invalidationBatch);
1578
+ return $dom->saveXML();
1579
+ }
1580
+
1581
+
1582
+ /**
1583
+ * List your invalidation batches for invalidateDistribution() in a CloudFront distribution
1584
+ *
1585
+ * http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/ListInvalidation.html
1586
+ * returned array looks like this:
1587
+ * Array
1588
+ * (
1589
+ * [I31TWB0CN9V6XD] => InProgress
1590
+ * [IT3TFE31M0IHZ] => Completed
1591
+ * [I12HK7MPO1UQDA] => Completed
1592
+ * [I1IA7R6JKTC3L2] => Completed
1593
+ * )
1594
+ *
1595
+ * @param string $distributionId Distribution ID from listDistributions()
1596
+ * @return array
1597
+ */
1598
+ public static function getDistributionInvalidationList($distributionId)
1599
+ {
1600
+ if (!extension_loaded('openssl'))
1601
+ {
1602
+ self::__triggerError(sprintf("S3::getDistributionInvalidationList(): [%s] %s",
1603
+ "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1604
+ return false;
1605
+ }
1606
+
1607
+ $useSSL = self::$useSSL;
1608
+ self::$useSSL = true; // CloudFront requires SSL
1609
+ $rest = new S3Request('GET', '', '2010-11-01/distribution/'.$distributionId.'/invalidation', 'cloudfront.amazonaws.com');
1610
+ $rest = self::__getCloudFrontResponse($rest);
1611
+ self::$useSSL = $useSSL;
1612
+
1613
+ if ($rest->error === false && $rest->code !== 200)
1614
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1615
+ if ($rest->error !== false)
1616
+ {
1617
+ trigger_error(sprintf("S3::getDistributionInvalidationList('{$distributionId}'): [%s]",
1618
+ $rest->error['code'], $rest->error['message']), E_USER_WARNING);
1619
+ return false;
1620
+ }
1621
+ elseif ($rest->body instanceof SimpleXMLElement && isset($rest->body->InvalidationSummary))
1622
+ {
1623
+ $list = array();
1624
+ foreach ($rest->body->InvalidationSummary as $summary)
1625
+ $list[(string)$summary->Id] = (string)$summary->Status;
1626
+
1627
+ return $list;
1628
+ }
1629
+ return array();
1630
+ }
1631
+
1632
+
1633
+ /**
1634
+ * Get a DistributionConfig DOMDocument
1635
+ *
1636
+ * http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/index.html?PutConfig.html
1637
+ *
1638
+ * @internal Used to create XML in createDistribution() and updateDistribution()
1639
+ * @param string $bucket S3 Origin bucket
1640
+ * @param boolean $enabled Enabled (true/false)
1641
+ * @param string $comment Comment to append
1642
+ * @param string $callerReference Caller reference
1643
+ * @param array $cnames Array of CNAME aliases
1644
+ * @param string $defaultRootObject Default root object
1645
+ * @param string $originAccessIdentity Origin access identity
1646
+ * @param array $trustedSigners Array of trusted signers
1647
+ * @return string
1648
+ */
1649
+ private static function __getCloudFrontDistributionConfigXML($bucket, $enabled, $comment, $callerReference = '0', $cnames = array(), $defaultRootObject = null, $originAccessIdentity = null, $trustedSigners = array())
1650
+ {
1651
+ $dom = new DOMDocument('1.0', 'UTF-8');
1652
+ $dom->formatOutput = true;
1653
+ $distributionConfig = $dom->createElement('DistributionConfig');
1654
+ $distributionConfig->setAttribute('xmlns', 'http://cloudfront.amazonaws.com/doc/2010-11-01/');
1655
+
1656
+ $origin = $dom->createElement('S3Origin');
1657
+ $origin->appendChild($dom->createElement('DNSName', $bucket));
1658
+ if ($originAccessIdentity !== null) $origin->appendChild($dom->createElement('OriginAccessIdentity', $originAccessIdentity));
1659
+ $distributionConfig->appendChild($origin);
1660
+
1661
+ if ($defaultRootObject !== null) $distributionConfig->appendChild($dom->createElement('DefaultRootObject', $defaultRootObject));
1662
+
1663
+ $distributionConfig->appendChild($dom->createElement('CallerReference', $callerReference));
1664
+ foreach ($cnames as $cname)
1665
+ $distributionConfig->appendChild($dom->createElement('CNAME', $cname));
1666
+ if ($comment !== '') $distributionConfig->appendChild($dom->createElement('Comment', $comment));
1667
+ $distributionConfig->appendChild($dom->createElement('Enabled', $enabled ? 'true' : 'false'));
1668
+
1669
+ $trusted = $dom->createElement('TrustedSigners');
1670
+ foreach ($trustedSigners as $id => $type)
1671
+ $trusted->appendChild($id !== '' ? $dom->createElement($type, $id) : $dom->createElement($type));
1672
+ $distributionConfig->appendChild($trusted);
1673
+
1674
+ $dom->appendChild($distributionConfig);
1675
+ //var_dump($dom->saveXML());
1676
+ return $dom->saveXML();
1677
+ }
1678
+
1679
+
1680
+ /**
1681
+ * Parse a CloudFront distribution config
1682
+ *
1683
+ * See http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/index.html?GetDistribution.html
1684
+ *
1685
+ * @internal Used to parse the CloudFront DistributionConfig node to an array
1686
+ * @param object &$node DOMNode
1687
+ * @return array
1688
+ */
1689
+ private static function __parseCloudFrontDistributionConfig(&$node)
1690
+ {
1691
+ if (isset($node->DistributionConfig))
1692
+ return self::__parseCloudFrontDistributionConfig($node->DistributionConfig);
1693
+
1694
+ $dist = array();
1695
+ if (isset($node->Id, $node->Status, $node->LastModifiedTime, $node->DomainName))
1696
+ {
1697
+ $dist['id'] = (string)$node->Id;
1698
+ $dist['status'] = (string)$node->Status;
1699
+ $dist['time'] = strtotime((string)$node->LastModifiedTime);
1700
+ $dist['domain'] = (string)$node->DomainName;
1701
+ }
1702
+
1703
+ if (isset($node->CallerReference))
1704
+ $dist['callerReference'] = (string)$node->CallerReference;
1705
+
1706
+ if (isset($node->Enabled))
1707
+ $dist['enabled'] = (string)$node->Enabled == 'true' ? true : false;
1708
+
1709
+ if (isset($node->S3Origin))
1710
+ {
1711
+ if (isset($node->S3Origin->DNSName))
1712
+ $dist['origin'] = (string)$node->S3Origin->DNSName;
1713
+
1714
+ $dist['originAccessIdentity'] = isset($node->S3Origin->OriginAccessIdentity) ?
1715
+ (string)$node->S3Origin->OriginAccessIdentity : null;
1716
+ }
1717
+
1718
+ $dist['defaultRootObject'] = isset($node->DefaultRootObject) ? (string)$node->DefaultRootObject : null;
1719
+
1720
+ $dist['cnames'] = array();
1721
+ if (isset($node->CNAME))
1722
+ foreach ($node->CNAME as $cname)
1723
+ $dist['cnames'][(string)$cname] = (string)$cname;
1724
+
1725
+ $dist['trustedSigners'] = array();
1726
+ if (isset($node->TrustedSigners))
1727
+ foreach ($node->TrustedSigners as $signer)
1728
+ {
1729
+ if (isset($signer->Self))
1730
+ $dist['trustedSigners'][''] = 'Self';
1731
+ elseif (isset($signer->KeyPairId))
1732
+ $dist['trustedSigners'][(string)$signer->KeyPairId] = 'KeyPairId';
1733
+ elseif (isset($signer->AwsAccountNumber))
1734
+ $dist['trustedSigners'][(string)$signer->AwsAccountNumber] = 'AwsAccountNumber';
1735
+ }
1736
+
1737
+ $dist['comment'] = isset($node->Comment) ? (string)$node->Comment : null;
1738
+ return $dist;
1739
+ }
1740
+
1741
+
1742
+ /**
1743
+ * Grab CloudFront response
1744
+ *
1745
+ * @internal Used to parse the CloudFront S3Request::getResponse() output
1746
+ * @param object &$rest S3Request instance
1747
+ * @return object
1748
+ */
1749
+ private static function __getCloudFrontResponse(&$rest)
1750
+ {
1751
+ $rest->getResponse();
1752
+ if ($rest->response->error === false && isset($rest->response->body) &&
1753
+ is_string($rest->response->body) && substr($rest->response->body, 0, 5) == '<?xml')
1754
+ {
1755
+ $rest->response->body = simplexml_load_string($rest->response->body);
1756
+ // Grab CloudFront errors
1757
+ if (isset($rest->response->body->Error, $rest->response->body->Error->Code,
1758
+ $rest->response->body->Error->Message))
1759
+ {
1760
+ $rest->response->error = array(
1761
+ 'code' => (string)$rest->response->body->Error->Code,
1762
+ 'message' => (string)$rest->response->body->Error->Message
1763
+ );
1764
+ unset($rest->response->body);
1765
+ }
1766
+ }
1767
+ return $rest->response;
1768
+ }
1769
+
1770
+
1771
+ /**
1772
+ * Get MIME type for file
1773
+ *
1774
+ * @internal Used to get mime types
1775
+ * @param string &$file File path
1776
+ * @return string
1777
+ */
1778
+ public static function __getMimeType(&$file)
1779
+ {
1780
+ $type = false;
1781
+ // Fileinfo documentation says fileinfo_open() will use the
1782
+ // MAGIC env var for the magic file
1783
+ if (extension_loaded('fileinfo') && isset($_ENV['MAGIC']) &&
1784
+ ($finfo = finfo_open(FILEINFO_MIME, $_ENV['MAGIC'])) !== false)
1785
+ {
1786
+ if (($type = finfo_file($finfo, $file)) !== false)
1787
+ {
1788
+ // Remove the charset and grab the last content-type
1789
+ $type = explode(' ', str_replace('; charset=', ';charset=', $type));
1790
+ $type = array_pop($type);
1791
+ $type = explode(';', $type);
1792
+ $type = trim(array_shift($type));
1793
+ }
1794
+ finfo_close($finfo);
1795
+
1796
+ // If anyone is still using mime_content_type()
1797
+ } elseif (function_exists('mime_content_type'))
1798
+ $type = trim(mime_content_type($file));
1799
+
1800
+ if ($type !== false && strlen($type) > 0) return $type;
1801
+
1802
+ // Otherwise do it the old fashioned way
1803
+ static $exts = array(
1804
+ 'jpg' => 'image/jpeg', 'gif' => 'image/gif', 'png' => 'image/png',
1805
+ 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'ico' => 'image/x-icon',
1806
+ 'swf' => 'application/x-shockwave-flash', 'pdf' => 'application/pdf',
1807
+ 'zip' => 'application/zip', 'gz' => 'application/x-gzip',
1808
+ 'tar' => 'application/x-tar', 'bz' => 'application/x-bzip',
1809
+ 'bz2' => 'application/x-bzip2', 'txt' => 'text/plain',
1810
+ 'asc' => 'text/plain', 'htm' => 'text/html', 'html' => 'text/html',
1811
+ 'css' => 'text/css', 'js' => 'text/javascript',
1812
+ 'xml' => 'text/xml', 'xsl' => 'application/xsl+xml',
1813
+ 'ogg' => 'application/ogg', 'mp3' => 'audio/mpeg', 'wav' => 'audio/x-wav',
1814
+ 'avi' => 'video/x-msvideo', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg',
1815
+ 'mov' => 'video/quicktime', 'flv' => 'video/x-flv', 'php' => 'text/x-php'
1816
+ );
1817
+ $ext = strtolower(pathInfo($file, PATHINFO_EXTENSION));
1818
+ return isset($exts[$ext]) ? $exts[$ext] : 'application/octet-stream';
1819
+ }
1820
+
1821
+
1822
+ /**
1823
+ * Generate the auth string: "AWS AccessKey:Signature"
1824
+ *
1825
+ * @internal Used by S3Request::getResponse()
1826
+ * @param string $string String to sign
1827
+ * @return string
1828
+ */
1829
+ public static function __getSignature($string)
1830
+ {
1831
+ return 'AWS '.self::$__accessKey.':'.self::__getHash($string);
1832
+ }
1833
+
1834
+
1835
+ /**
1836
+ * Creates a HMAC-SHA1 hash
1837
+ *
1838
+ * This uses the hash extension if loaded
1839
+ *
1840
+ * @internal Used by __getSignature()
1841
+ * @param string $string String to sign
1842
+ * @return string
1843
+ */
1844
+ private static function __getHash($string)
1845
+ {
1846
+ return base64_encode(extension_loaded('hash') ?
1847
+ hash_hmac('sha1', $string, self::$__secretKey, true) : pack('H*', sha1(
1848
+ (str_pad(self::$__secretKey, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) .
1849
+ pack('H*', sha1((str_pad(self::$__secretKey, 64, chr(0x00)) ^
1850
+ (str_repeat(chr(0x36), 64))) . $string)))));
1851
+ }
1852
+
1853
+ }
1854
+
1855
+ final class S3Request
1856
+ {
1857
+ private $endpoint, $verb, $bucket, $uri, $resource = '', $parameters = array(),
1858
+ $amzHeaders = array(), $headers = array(
1859
+ 'Host' => '', 'Date' => '', 'Content-MD5' => '', 'Content-Type' => ''
1860
+ );
1861
+ public $fp = false, $size = 0, $data = false, $response;
1862
+
1863
+
1864
+ /**
1865
+ * Constructor
1866
+ *
1867
+ * @param string $verb Verb
1868
+ * @param string $bucket Bucket name
1869
+ * @param string $uri Object URI
1870
+ * @return mixed
1871
+ */
1872
+ function __construct($verb, $bucket = '', $uri = '', $endpoint = 's3.amazonaws.com')
1873
+ {
1874
+ $this->endpoint = $endpoint;
1875
+ $this->verb = $verb;
1876
+ $this->bucket = $bucket;
1877
+ $this->uri = $uri !== '' ? '/'.str_replace('%2F', '/', rawurlencode($uri)) : '/';
1878
+
1879
+ //if ($this->bucket !== '')
1880
+ // $this->resource = '/'.$this->bucket.$this->uri;
1881
+ //else
1882
+ // $this->resource = $this->uri;
1883
+
1884
+ if ($this->bucket !== '')
1885
+ {
1886
+ if ($this->__dnsBucketName($this->bucket))
1887
+ {
1888
+ $this->headers['Host'] = $this->bucket.'.'.$this->endpoint;
1889
+ $this->resource = '/'.$this->bucket.$this->uri;
1890
+ }
1891
+ else
1892
+ {
1893
+ $this->headers['Host'] = $this->endpoint;
1894
+ $this->uri = $this->uri;
1895
+ if ($this->bucket !== '') $this->uri = '/'.$this->bucket.$this->uri;
1896
+ $this->bucket = '';
1897
+ $this->resource = $this->uri;
1898
+ }
1899
+ }
1900
+ else
1901
+ {
1902
+ $this->headers['Host'] = $this->endpoint;
1903
+ $this->resource = $this->uri;
1904
+ }
1905
+
1906
+
1907
+ $this->headers['Date'] = gmdate('D, d M Y H:i:s T');
1908
+ $this->response = new STDClass;
1909
+ $this->response->error = false;
1910
+ }
1911
+
1912
+
1913
+ /**
1914
+ * Set request parameter
1915
+ *
1916
+ * @param string $key Key
1917
+ * @param string $value Value
1918
+ * @return void
1919
+ */
1920
+ public function setParameter($key, $value)
1921
+ {
1922
+ $this->parameters[$key] = $value;
1923
+ }
1924
+
1925
+
1926
+ /**
1927
+ * Set request header
1928
+ *
1929
+ * @param string $key Key
1930
+ * @param string $value Value
1931
+ * @return void
1932
+ */
1933
+ public function setHeader($key, $value)
1934
+ {
1935
+ $this->headers[$key] = $value;
1936
+ }
1937
+
1938
+
1939
+ /**
1940
+ * Set x-amz-meta-* header
1941
+ *
1942
+ * @param string $key Key
1943
+ * @param string $value Value
1944
+ * @return void
1945
+ */
1946
+ public function setAmzHeader($key, $value)
1947
+ {
1948
+ $this->amzHeaders[$key] = $value;
1949
+ }
1950
+
1951
+
1952
+ /**
1953
+ * Get the S3 response
1954
+ *
1955
+ * @return object | false
1956
+ */
1957
+ public function getResponse()
1958
+ {
1959
+ $query = '';
1960
+ if (sizeof($this->parameters) > 0)
1961
+ {
1962
+ $query = substr($this->uri, -1) !== '?' ? '?' : '&';
1963
+ foreach ($this->parameters as $var => $value)
1964
+ if ($value == null || $value == '') $query .= $var.'&';
1965
+ else $query .= $var.'='.rawurlencode($value).'&';
1966
+ $query = substr($query, 0, -1);
1967
+ $this->uri .= $query;
1968
+
1969
+ if (array_key_exists('acl', $this->parameters) ||
1970
+ array_key_exists('location', $this->parameters) ||
1971
+ array_key_exists('torrent', $this->parameters) ||
1972
+ array_key_exists('logging', $this->parameters) ||
1973
+ array_key_exists('partNumber', $this->parameters) ||
1974
+ array_key_exists('uploads', $this->parameters) ||
1975
+ array_key_exists('uploadId', $this->parameters))
1976
+ $this->resource .= $query;
1977
+ }
1978
+ $url = (S3::$useSSL ? 'https://' : 'http://') . ($this->headers['Host'] !== '' ? $this->headers['Host'] : $this->endpoint) . $this->uri;
1979
+
1980
+ //var_dump('bucket: ' . $this->bucket, 'uri: ' . $this->uri, 'resource: ' . $this->resource, 'url: ' . $url);
1981
+
1982
+ // Basic setup
1983
+ $curl = curl_init();
1984
+ curl_setopt($curl, CURLOPT_USERAGENT, 'S3/php');
1985
+
1986
+ if (S3::$useSSL)
1987
+ {
1988
+ // SSL Validation can now be optional for those with broken OpenSSL installations
1989
+ curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, S3::$useSSLValidation ? 1 : 0);
1990
+ curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, S3::$useSSLValidation ? 1 : 0);
1991
+
1992
+ if (S3::$sslKey !== null) curl_setopt($curl, CURLOPT_SSLKEY, S3::$sslKey);
1993
+ if (S3::$sslCert !== null) curl_setopt($curl, CURLOPT_SSLCERT, S3::$sslCert);
1994
+ if (S3::$sslCACert !== null) curl_setopt($curl, CURLOPT_CAINFO, S3::$sslCACert);
1995
+ }
1996
+
1997
+ curl_setopt($curl, CURLOPT_URL, $url);
1998
+
1999
+ if (S3::$proxy != null && isset(S3::$proxy['host']))
2000
+ {
2001
+ curl_setopt($curl, CURLOPT_PROXY, S3::$proxy['host']);
2002
+ curl_setopt($curl, CURLOPT_PROXYTYPE, S3::$proxy['type']);
2003
+ if (isset(S3::$proxy['user'], S3::$proxy['pass']) && S3::$proxy['user'] != null && S3::$proxy['pass'] != null)
2004
+ curl_setopt($curl, CURLOPT_PROXYUSERPWD, sprintf('%s:%s', S3::$proxy['user'], S3::$proxy['pass']));
2005
+ }
2006
+
2007
+ // Headers
2008
+ $headers = array(); $amz = array();
2009
+ foreach ($this->amzHeaders as $header => $value)
2010
+ if (strlen($value) > 0) $headers[] = $header.': '.$value;
2011
+ foreach ($this->headers as $header => $value)
2012
+ if (strlen($value) > 0) $headers[] = $header.': '.$value;
2013
+
2014
+ // Collect AMZ headers for signature
2015
+ foreach ($this->amzHeaders as $header => $value)
2016
+ if (strlen($value) > 0) $amz[] = strtolower($header).':'.$value;
2017
+
2018
+ // AMZ headers must be sorted
2019
+ if (sizeof($amz) > 0)
2020
+ {
2021
+ //sort($amz);
2022
+ usort($amz, array(&$this, '__sortMetaHeadersCmp'));
2023
+ $amz = "\n".implode("\n", $amz);
2024
+ } else $amz = '';
2025
+
2026
+ if (S3::hasAuth())
2027
+ {
2028
+ // Authorization string (CloudFront stringToSign should only contain a date)
2029
+ if ($this->headers['Host'] == 'cloudfront.amazonaws.com')
2030
+ $headers[] = 'Authorization: ' . S3::__getSignature($this->headers['Date']);
2031
+ else
2032
+ {
2033
+ $headers[] = 'Authorization: ' . S3::__getSignature(
2034
+ $this->verb."\n".
2035
+ $this->headers['Content-MD5']."\n".
2036
+ $this->headers['Content-Type']."\n".
2037
+ $this->headers['Date'].$amz."\n".
2038
+ $this->resource
2039
+ );
2040
+ }
2041
+ }
2042
+
2043
+ curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
2044
+ curl_setopt($curl, CURLOPT_HEADER, false);
2045
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, false);
2046
+ curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback'));
2047
+ curl_setopt($curl, CURLOPT_HEADERFUNCTION, array(&$this, '__responseHeaderCallback'));
2048
+ curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
2049
+
2050
+ // Request types
2051
+ switch ($this->verb)
2052
+ {
2053
+ case 'GET': break;
2054
+ case 'PUT': case 'POST':
2055
+ if ($this->fp !== false)
2056
+ {
2057
+ curl_setopt($curl, CURLOPT_PUT, true);
2058
+ curl_setopt($curl, CURLOPT_INFILE, $this->fp);
2059
+ if ($this->size >= 0)
2060
+ curl_setopt($curl, CURLOPT_INFILESIZE, $this->size);
2061
+ }
2062
+ elseif ($this->data !== false)
2063
+ {
2064
+ curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
2065
+ curl_setopt($curl, CURLOPT_POSTFIELDS, $this->data);
2066
+ }
2067
+ else
2068
+ curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
2069
+ break;
2070
+ case 'HEAD':
2071
+ curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'HEAD');
2072
+ curl_setopt($curl, CURLOPT_NOBODY, true);
2073
+ break;
2074
+ case 'DELETE':
2075
+ curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
2076
+ break;
2077
+ default: break;
2078
+ }
2079
+
2080
+ // Execute, grab errors
2081
+ if (curl_exec($curl))
2082
+ $this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
2083
+ else
2084
+ $this->response->error = array(
2085
+ 'code' => curl_errno($curl),
2086
+ 'message' => curl_error($curl),
2087
+ 'resource' => $this->resource
2088
+ );
2089
+
2090
+ @curl_close($curl);
2091
+
2092
+ // Parse body into XML
2093
+ if ($this->response->error === false && isset($this->response->headers['type']) &&
2094
+ $this->response->headers['type'] == 'application/xml' && isset($this->response->body))
2095
+ {
2096
+ $this->response->body = simplexml_load_string($this->response->body);
2097
+
2098
+ // Grab S3 errors
2099
+ if (!in_array($this->response->code, array(200, 204, 206)) &&
2100
+ isset($this->response->body->Code, $this->response->body->Message))
2101
+ {
2102
+ $this->response->error = array(
2103
+ 'code' => (string)$this->response->body->Code,
2104
+ 'message' => (string)$this->response->body->Message
2105
+ );
2106
+ if (isset($this->response->body->Resource))
2107
+ $this->response->error['resource'] = (string)$this->response->body->Resource;
2108
+ unset($this->response->body);
2109
+ }
2110
+ }
2111
+
2112
+ // Clean up file resources
2113
+ if ($this->fp !== false && is_resource($this->fp)) fclose($this->fp);
2114
+
2115
+ return $this->response;
2116
+ }
2117
+
2118
+ /**
2119
+ * Sort compare for meta headers
2120
+ *
2121
+ * @internal Used to sort x-amz meta headers
2122
+ * @param string $a String A
2123
+ * @param string $b String B
2124
+ * @return integer
2125
+ */
2126
+ private function __sortMetaHeadersCmp($a, $b)
2127
+ {
2128
+ $lenA = strpos($a, ':');
2129
+ $lenB = strpos($b, ':');
2130
+ $minLen = min($lenA, $lenB);
2131
+ $ncmp = strncmp($a, $b, $minLen);
2132
+ if ($lenA == $lenB) return $ncmp;
2133
+ if (0 == $ncmp) return $lenA < $lenB ? -1 : 1;
2134
+ return $ncmp;
2135
+ }
2136
+
2137
+ /**
2138
+ * CURL write callback
2139
+ *
2140
+ * @param resource &$curl CURL resource
2141
+ * @param string &$data Data
2142
+ * @return integer
2143
+ */
2144
+ private function __responseWriteCallback(&$curl, &$data)
2145
+ {
2146
+ if (in_array($this->response->code, array(200, 206)) && $this->fp !== false)
2147
+ return fwrite($this->fp, $data);
2148
+ else
2149
+ $this->response->body .= $data;
2150
+ return strlen($data);
2151
+ }
2152
+
2153
+
2154
+ /**
2155
+ * Check DNS conformity
2156
+ *
2157
+ * @param string $bucket Bucket name
2158
+ * @return boolean
2159
+ */
2160
+ private function __dnsBucketName($bucket)
2161
+ {
2162
+ if (strlen($bucket) > 63 || !preg_match("/[^a-z0-9\.-]/", $bucket)) return false;
2163
+ if (strstr($bucket, '-.') !== false) return false;
2164
+ if (strstr($bucket, '..') !== false) return false;
2165
+ if (!preg_match("/^[0-9a-z]/", $bucket)) return false;
2166
+ if (!preg_match("/[0-9a-z]$/", $bucket)) return false;
2167
+ return true;
2168
+ }
2169
+
2170
+
2171
+ /**
2172
+ * CURL header callback
2173
+ *
2174
+ * @param resource &$curl CURL resource
2175
+ * @param string &$data Data
2176
+ * @return integer
2177
+ */
2178
+ private function __responseHeaderCallback(&$curl, &$data)
2179
+ {
2180
+ if (($strlen = strlen($data)) <= 2) return $strlen;
2181
+ if (substr($data, 0, 4) == 'HTTP')
2182
+ $this->response->code = (int)substr($data, 9, 3);
2183
+ else
2184
+ {
2185
+ $data = trim($data);
2186
+ if (strpos($data, ': ') === false) return $strlen;
2187
+ list($header, $value) = explode(': ', $data, 2);
2188
+ if ($header == 'Last-Modified')
2189
+ $this->response->headers['time'] = strtotime($value);
2190
+ elseif ($header == 'Content-Length')
2191
+ $this->response->headers['size'] = (int)$value;
2192
+ elseif ($header == 'Content-Type')
2193
+ $this->response->headers['type'] = $value;
2194
+ elseif ($header == 'ETag')
2195
+ $this->response->headers['hash'] = $value{0} == '"' ? substr($value, 1, -1) : $value;
2196
+ elseif (preg_match('/^x-amz-meta-.*$/', $header))
2197
+ $this->response->headers[$header] = $value;
2198
+ }
2199
+ return $strlen;
2200
+ }
2201
+
2202
+ }
2203
+
2204
+ class S3Exception extends Exception {
2205
+ function __construct($message, $file, $line, $code = 0)
2206
+ {
2207
+ parent::__construct($message, $code);
2208
+ $this->file = $file;
2209
+ $this->line = $line;
2210
+ }
2211
+ }
trunk/includes/class-gdocs.php ADDED
@@ -0,0 +1,630 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // Originally contained: GDocs class
4
+ // Contains: UpdraftPlus_GDocs class (new methods added - could not extend, as too much was private)
5
+
6
+ // The following copyright notice is reproduced exactly as found in the "Backup" plugin (http://wordpress.org/extend/plugins/backup)
7
+ // It applies to the code apart from the methods we added (get_content_link, download_data)
8
+
9
+ /*
10
+ Copyright 2012 Sorin Iclanzan (email : sorin@hel.io)
11
+
12
+ This file is part of Backup.
13
+
14
+ Backup is free software: you can redistribute it and/or modify
15
+ it under the terms of the GNU General Public License as published by
16
+ the Free Software Foundation, either version 3 of the License, or
17
+ (at your option) any later version.
18
+
19
+ Backup is distributed in the hope that it will be useful,
20
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
21
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
+ GNU General Public License for more details.
23
+
24
+ You should have received a copy of the GNU General Public License
25
+ along with Backup. If not, see http://www.gnu.org/licenses/gpl.html.
26
+ */
27
+
28
+ /**
29
+ * Google Docs class
30
+ *
31
+ * Implements communication with Google Docs via the Google Documents List v3 API.
32
+ *
33
+ * Currently uploading, resuming and deleting resources is implemented as well as retrieving quotas.
34
+ *
35
+ * @uses WP_Error for storing error messages.
36
+ */
37
+ class UpdraftPlus_GDocs {
38
+
39
+ /**
40
+ * Stores the API version.
41
+ *
42
+ * @var string
43
+ * @access private
44
+ */
45
+ private $gdata_version;
46
+
47
+
48
+ /**
49
+ * Stores the base URL for the API requests.
50
+ *
51
+ * @var string
52
+ * @access private
53
+ */
54
+ private $base_url;
55
+
56
+ /**
57
+ * Stores the URL to the metadata feed.
58
+ * @var string
59
+ */
60
+ private $metadata_url;
61
+
62
+ /**
63
+ * Stores the token needed to access the API.
64
+ * @var string
65
+ * @access private
66
+ */
67
+ private $token;
68
+
69
+ /**
70
+ * Stores feeds to avoid requesting them again for successive use.
71
+ *
72
+ * @var array
73
+ * @access private
74
+ */
75
+ private $cache = array();
76
+
77
+ /**
78
+ * Files are uploadded in chunks of this size in bytes.
79
+ *
80
+ * @var integer
81
+ * @access private
82
+ */
83
+ private $chunk_size;
84
+
85
+ /**
86
+ * Stores whether or not to verify host SSL certificate.
87
+ *
88
+ * @var boolean
89
+ * @access private
90
+ */
91
+ private $ssl_verify;
92
+
93
+ /**
94
+ * Stores the number of seconds to wait for a response before timing out.
95
+ *
96
+ * @var integer
97
+ * @access private
98
+ */
99
+ private $request_timeout;
100
+
101
+ /**
102
+ * Stores the MIME type of the file that is uploading
103
+ *
104
+ * @var string
105
+ * @access private
106
+ */
107
+ private $upload_file_type;
108
+
109
+ /**
110
+ * Stores info about the file being uploaded.
111
+ *
112
+ * @var array
113
+ * @access private
114
+ */
115
+ private $file;
116
+
117
+ /**
118
+ * Stores the number of seconds the upload process is allowed to run
119
+ *
120
+ * @var integer
121
+ * @access private
122
+ */
123
+ private $time_limit;
124
+
125
+ /**
126
+ * Stores a timer for upload processes
127
+ *
128
+ * @var array
129
+ */
130
+ private $timer;
131
+
132
+ /**
133
+ * Constructor - Sets the access token.
134
+ *
135
+ * @param string $token Access token
136
+ */
137
+ function __construct( $token ) {
138
+ $this->token = $token;
139
+ $this->gdata_version = '3.0';
140
+ $this->base_url = 'https://docs.google.com/feeds/default/private/full/';
141
+ $this->metadata_url = 'https://docs.google.com/feeds/metadata/default';
142
+ $this->chunk_size = 524288; // 512 KiB
143
+ $this->max_resume_attempts = 5;
144
+ $this->request_timeout = 5;
145
+ $this->ssl_verify = true;
146
+ $this->timer = array(
147
+ 'start' => 0,
148
+ 'stop' => 0,
149
+ 'delta' => 0,
150
+ 'cycle' => 0
151
+ );
152
+ $this->time_limit = @ini_get( 'max_execution_time' );
153
+ if ( ! $this->time_limit && '0' !== $this->time_limit )
154
+ $this->time_limit = 30; // default php max exec time
155
+ }
156
+
157
+ /**
158
+ * Sets an option.
159
+ *
160
+ * @access public
161
+ * @param string $option The option to set.
162
+ * @param mixed $value The value to set the option to.
163
+ */
164
+ public function set_option( $option, $value ) {
165
+ switch ( $option ) {
166
+ case 'chunk_size':
167
+ if ( floatval($value) >= 0.5 ) {
168
+ $this->chunk_size = floatval($value) * 1024 * 1024; // Transform from MiB to bytes
169
+ return true;
170
+ }
171
+ break;
172
+ case 'ssl_verify':
173
+ $this->ssl_verify = ( bool ) $value;
174
+ return true;
175
+ case 'request_timeout':
176
+ if ( intval( $value ) > 0 ) {
177
+ $this->request_timeout = intval( $value );
178
+ return true;
179
+ }
180
+ break;
181
+ case 'max_resume_attempts':
182
+ $this->max_resume_attempts = intval($value);
183
+ return true;
184
+ }
185
+ return false;
186
+ }
187
+
188
+ /**
189
+ * Gets an option.
190
+ *
191
+ * @access public
192
+ * @param string $option The option to get.
193
+ */
194
+ public function get_option( $option ) {
195
+ switch ( $option ) {
196
+ case 'chunk_size':
197
+ return $this->chunk_size;
198
+ case 'ssl_verify':
199
+ return $this->ssl_verify;
200
+ case 'request_timeout':
201
+ return $this->request_timeout;
202
+ case 'max_resume_attempts':
203
+ return $this->max_resume_attempts;
204
+ }
205
+ return false;
206
+ }
207
+
208
+ /**
209
+ * This function makes all the requests to the API.
210
+ *
211
+ * @uses wp_remote_request
212
+ * @access private
213
+ * @param string $url The URL where the request is sent.
214
+ * @param string $method The HTTP request method, defaults to 'GET'.
215
+ * @param array $headers Headers to be sent.
216
+ * @param string $body The body of the request.
217
+ * @return mixed Returns an array containing the response on success or an instance of WP_Error on failure.
218
+ */
219
+ private function request( $url, $method = 'GET', $headers = array(), $body = NULL ) {
220
+ $args = array(
221
+ 'method' => $method,
222
+ 'timeout' => $this->request_timeout,
223
+ 'httpversion' => '1.1',
224
+ 'redirection' => 0,
225
+ 'sslverify' => $this->ssl_verify,
226
+ 'headers' => array(
227
+ 'Authorization' => 'Bearer ' . $this->token,
228
+ 'GData-Version' => $this->gdata_version
229
+ )
230
+ );
231
+ if ( ! empty( $headers ) )
232
+ $args['headers'] = array_merge( $args['headers'], $headers );
233
+ if ( ! empty( $body ) )
234
+ $args['body'] = $body;
235
+
236
+ return wp_remote_request( $url, $args );
237
+ }
238
+
239
+ /**
240
+ * Returns the feed from a URL.
241
+ *
242
+ * @access public
243
+ * @param string $url The feed URL.
244
+ * @return mixed Returns the feed as an instance of SimpleXMLElement on success or an instance of WP_Error on failure.
245
+ */
246
+ public function get_feed( $url ) {
247
+ if ( ! isset( $this->cache[$url] ) ) {
248
+ $result = $this->cache_feed( $url );
249
+ if ( is_wp_error( $result ) )
250
+ return $result;
251
+ }
252
+
253
+ return $this->cache[$url];
254
+ }
255
+
256
+ /**
257
+ * Requests a feed and adds it to cache.
258
+ *
259
+ * @access private
260
+ * @param string $url The feed URL.
261
+ * @return mixed Returns TRUE on success or an instance of WP_Error on failure.
262
+ */
263
+ private function cache_feed( $url ) {
264
+ $result = $this->request( $url );
265
+
266
+ if ( is_wp_error( $result ) )
267
+ return $result;
268
+
269
+ if ( $result['response']['code'] == '200' ) {
270
+ $feed = @simplexml_load_string( $result['body'] );
271
+ if ( $feed === false )
272
+ return new WP_Error( 'invalid_data', "Could not create SimpleXMLElement from '" . $result['body'] . "'." );
273
+
274
+ $this->cache[$url] = $feed;
275
+ return true;
276
+ }
277
+ return new WP_Error( 'bad_response', "Received response code '" . $result['response']['code'] . " " . $result['response']['message'] . "' while trying to get '" . $url . "'. Response body: " . $result['body'] );
278
+
279
+ }
280
+
281
+ /**
282
+ * Deletes a resource from Google Docs.
283
+ *
284
+ * @access public
285
+ * @param string $id Gdata Id of the resource to be deleted.
286
+ * @return mixed Returns TRUE on success, an instance of WP_Error on failure.
287
+ */
288
+ public function delete_resource( $id ) {
289
+ $headers = array( 'If-Match' => '*' );
290
+
291
+ $result = $this->request( $this->base_url . $id . '?delete=true', 'DELETE', $headers );
292
+ if ( is_wp_error( $result ) )
293
+ return $result;
294
+
295
+ if ( $result['response']['code'] == '200' )
296
+ return true;
297
+ return new WP_Error( 'bad_response', "Received response code '" . $result['response']['code'] . " " . $result['response']['message'] . "' while trying to delete resource '" . $id . "'. The resource might not have been deleted." );
298
+ }
299
+
300
+ /**
301
+ * Get the resumable-create-media link needed to upload files.
302
+ *
303
+ * @access private
304
+ * @param string $parent The Id of the folder where the upload is to be made. Default is empty string.
305
+ * @return mixed Returns a link on success, instance of WP_Error on failure.
306
+ */
307
+ private function get_resumable_create_media_link( $parent = '' ) {
308
+ $url = $this->base_url;
309
+ if ( $parent )
310
+ $url .= $parent;
311
+
312
+ $feed = $this->get_feed( $url );
313
+
314
+ if ( is_wp_error( $feed ) )
315
+ return $feed;
316
+
317
+ foreach ( $feed->link as $link )
318
+ if ( $link['rel'] == 'http://schemas.google.com/g/2005#resumable-create-media' )
319
+ return ( string ) $link['href'];
320
+ return new WP_Error( 'not_found', "The 'resumable_create_media_link' was not found in feed." );
321
+ }
322
+
323
+ /**
324
+ * Get used quota in bytes.
325
+ *
326
+ * @access public
327
+ * @return mixed Returns the number of bytes used in Google Docs on success or an instance of WP_Error on failure.
328
+ */
329
+ public function get_quota_used() {
330
+ $feed = $this->get_feed( $this->metadata_url );
331
+ if ( is_wp_error( $feed ) )
332
+ return $feed;
333
+ return ( string ) $feed->children( "http://schemas.google.com/g/2005" )->quotaBytesUsed;
334
+ }
335
+
336
+ /**
337
+ * Get total quota in bytes.
338
+ *
339
+ * @access public
340
+ * @return string|WP_Error Returns the total quota in bytes in Google Docs on success or an instance of WP_Error on failure.
341
+ */
342
+ public function get_quota_total() {
343
+ $feed = $this->get_feed( $this->metadata_url );
344
+ if ( is_wp_error( $feed ) )
345
+ return $feed;
346
+ return ( string ) $feed->children( "http://schemas.google.com/g/2005" )->quotaBytesTotal;
347
+ }
348
+
349
+ /**
350
+ * Function to prepare a file to be uploaded to Google Docs.
351
+ *
352
+ * The function requests a URI for uploading and prepends a new element in the resume_list array.
353
+ *
354
+ * @uses wp_check_filetype
355
+ * @access public
356
+ *
357
+ * @param string $file Path to the file that is to be uploaded.
358
+ * @param string $title Title to be given to the file.
359
+ * @param string $parent ID of the folder in which to upload the file.
360
+ * @param string $type MIME type of the file to be uploaded. The function tries to identify the type if it is omitted.
361
+ * @return mixed Returns the URI where to upload on success, an instance of WP_Error on failure.
362
+ */
363
+ public function prepare_upload( $file, $title, $parent = '', $type = '' ) {
364
+ if ( ! @is_readable( $file ) )
365
+ return new WP_Error( 'not_file', "The path '" . $file . "' does not point to a readable file." );
366
+
367
+ // If a mime type wasn't passed try to guess it from the extension based on the WordPress allowed mime types
368
+ if ( empty( $type ) ) {
369
+ $check = wp_check_filetype( $file );
370
+ $this->upload_file_type = $type = $check['type'];
371
+ }
372
+
373
+ $size = filesize( $file );
374
+
375
+ $body = '<?xml version=\'1.0\' encoding=\'UTF-8\'?><entry xmlns="http://www.w3.org/2005/Atom" xmlns:docs="http://schemas.google.com/docs/2007"><category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/docs/2007#file"/><title>' . $title . '</title></entry>';
376
+
377
+ $headers = array(
378
+ 'Content-Type' => 'application/atom+xml',
379
+ 'X-Upload-Content-Type' => $type,
380
+ 'X-Upload-Content-Length' => (string) $size
381
+ );
382
+
383
+ $url = $this->get_resumable_create_media_link( $parent );
384
+
385
+ if ( is_wp_error( $url ) )
386
+ return $url;
387
+
388
+ $url .= '?convert=false'; // needed to upload a file
389
+
390
+ $result = $this->request( $url, 'POST', $headers, $body );
391
+
392
+ if ( is_wp_error( $result ) )
393
+ return $result;
394
+
395
+ if ( $result['response']['code'] != '200' )
396
+ return new WP_Error( 'bad_response', "Received response code '" . $result['response']['code'] . " " . $result['response']['message'] . "' while trying to get '" . $url . "'." );
397
+
398
+ $this->file = array(
399
+ 'path' => $file,
400
+ 'size' => $size,
401
+ 'location' => $result['headers']['location'],
402
+ 'pointer' => 0
403
+ );
404
+
405
+ // Open file for reading.
406
+ if ( !$this->file['handle'] = fopen( $file, "rb" ) )
407
+ return new WP_Error( 'open_error', "Could not open file '" . $file . "' for reading." );
408
+
409
+ // Start timer
410
+ $this->timer['start'] = microtime( true );
411
+
412
+ return $result['headers']['location'];
413
+ }
414
+
415
+
416
+ /**
417
+ * Resume an upload.
418
+ *
419
+ * @access public
420
+ * @param string $file Path to the file which needs to be uploaded
421
+ * @param string $location URI where to upload the file
422
+ * @return mixed Returns the next location URI on success, an instance of WP_Error on failure.
423
+ */
424
+ public function resume_upload( $file, $location ) {
425
+
426
+ if ( ! @is_readable( $file ) )
427
+ return new WP_Error( 'not_file', "The path '" . $this->resume_list[$id]['path'] . "' does not point to a readable file. Upload has been canceled." );
428
+
429
+ $size = filesize( $file );
430
+
431
+ $headers = array( 'Content-Range' => 'bytes */' . $size );
432
+ $result = $this->request( $location, 'PUT', $headers );
433
+ if( is_wp_error( $result ) )
434
+ return $result;
435
+
436
+ if ( '308' != $result['response']['code'] ) {
437
+ if ( '201' == $result['response']['code'] ) {
438
+ $feed = @simplexml_load_string( $result['body'] );
439
+ if ( $feed === false )
440
+ return new WP_Error( 'invalid_data', "Could not create SimpleXMLElement from '" . $result['body'] . "'." );
441
+ $this->file['id'] = substr( ( string ) $feed->children( "http://schemas.google.com/g/2005" )->resourceId, 5 );
442
+ return true;
443
+ }
444
+ return new WP_Error( 'bad_response', "Received response code '" . $result['response']['code'] . " " . $result['response']['message'] . "' while trying to resume the upload of file '" . $file . "'." );
445
+ }
446
+ if( isset( $result['headers']['location'] ) )
447
+ $location = $result['headers']['location'];
448
+ $pointer = $this->pointer( $result['headers']['range'] );
449
+
450
+ $this->file = array(
451
+ 'path' => $file,
452
+ 'size' => $size,
453
+ 'location' => $location,
454
+ 'pointer' => $pointer
455
+ );
456
+
457
+ // Open file for reading.
458
+ if ( !$this->file['handle'] = fopen( $file, "rb" ) )
459
+ return new WP_Error( 'open_error', "Could not open file '" . $file . "' for reading." );
460
+
461
+ // Start timer
462
+ $this->timer['start'] = microtime( true );
463
+
464
+ return $location;
465
+ }
466
+
467
+ /**
468
+ * Work out where the file pointer should be from the range header.
469
+ *
470
+ * @access private
471
+ * @param string $range The range HTTP response header.
472
+ * @return integer Returns the number of bytes that have been uploaded.
473
+ */
474
+ private function pointer( $range ) {
475
+ return intval(substr( $range, strpos( $range, '-' ) + 1 )) + 1;
476
+ }
477
+
478
+ /**
479
+ * Uploads a chunk of the file being uploaded.
480
+ *
481
+ * @access public
482
+ * @return mixed Returns TRUE if the chunk was uploaded successfully;
483
+ * returns Google Docs resource ID if the file upload finished;
484
+ * returns an instance of WP_Error on failure.
485
+ */
486
+ public function upload_chunk() {
487
+ if ( !isset( $this->file['handle'] ) )
488
+ return new WP_Error( "no_upload", "There is no file being uploaded." );
489
+
490
+ $cycle_start = microtime( true );
491
+ fseek( $this->file['handle'], $this->file['pointer'] );
492
+ $chunk = @fread( $this->file['handle'], $this->chunk_size );
493
+ if ( false === $chunk ) {
494
+ $is_file = (is_file($this->file['path'])) ? 1 : 0;
495
+ $is_readable = (is_readable($this->file['path'])) ? 1 : 0;
496
+ return new WP_Error( 'read_error', "Failed to read from file (path: ".$this->file['path'].", size: ".$this->file['size'].", pointer: ".$this->file['pointer'].", is_file: $is_file, is_readable: $is_readable)");
497
+ }
498
+
499
+ $chunk_size = strlen( $chunk );
500
+ $bytes = 'bytes ' . (string)$this->file['pointer'] . '-' . (string)($this->file['pointer'] + $chunk_size - 1) . '/' . (string)$this->file['size'];
501
+
502
+ $headers = array( 'Content-Range' => $bytes );
503
+
504
+ $result = $this->request( $this->file['location'], 'PUT', $headers, $chunk );
505
+
506
+ if ( !is_wp_error( $result ) )
507
+ if ( '308' == $result['response']['code'] ) {
508
+ if ( isset( $result['headers']['range'] ) )
509
+ $this->file['pointer'] = $this->pointer( $result['headers']['range'] );
510
+ else
511
+ $this->file['pointer'] += $chunk_size;
512
+
513
+ if ( isset( $result['headers']['location'] ) )
514
+ $this->file['location'] = $result['headers']['location'];
515
+
516
+ if ( $this->timer['cycle'] )
517
+ $this->timer['cycle'] = ( microtime( true ) - $cycle_start + $this->timer['cycle'] ) / 2;
518
+ else
519
+ $this->timer['cycle'] = microtime(true) - $cycle_start;
520
+
521
+ return $this->file['location'];
522
+ }
523
+ elseif ( '201' == $result['response']['code'] ) {
524
+ fclose( $this->file['handle'] );
525
+
526
+ // Stop timer
527
+ $this->timer['stop'] = microtime(true);
528
+ $this->timer['delta'] = $this->timer['stop'] - $this->timer['start'];
529
+
530
+ if ( $this->timer['cycle'] )
531
+ $this->timer['cycle'] = ( microtime( true ) - $cycle_start + $this->timer['cycle'] ) / 2;
532
+ else
533
+ $this->timer['cycle'] = microtime(true) - $cycle_start;
534
+
535
+ $this->file['pointer'] = $this->file['size'];
536
+
537
+ $feed = @simplexml_load_string( $result['body'] );
538
+ if ( $feed === false )
539
+ return new WP_Error( 'invalid_data', "Could not create SimpleXMLElement from '" . $result['body'] . "'." );
540
+ $this->file['id'] = substr( ( string ) $feed->children( "http://schemas.google.com/g/2005" )->resourceId, 5 );
541
+ return true;
542
+ }
543
+
544
+ // If we got to this point it means the upload wasn't successful.
545
+ fclose( $this->file['handle'] );
546
+ if ( is_wp_error( $result ) )
547
+ return $result;
548
+ return new WP_Error( 'bad_response', "Received response code '" . $result['response']['code'] . " " . $result['response']['message'] . "' while trying to upload a file chunk." );
549
+ }
550
+
551
+ /**
552
+ * Get the resource ID of the most recent uploaded file.
553
+ *
554
+ * @access public
555
+ * @return string The ID of the uploaded file or an empty string.
556
+ */
557
+ public function get_file_id() {
558
+ if ( isset( $this->file['id'] ) )
559
+ return $this->file['id'];
560
+ return '';
561
+ }
562
+
563
+ /**
564
+ * Get the upload speed recorded on the last upload performed.
565
+ *
566
+ * @access public
567
+ * @return integer Returns the upload speed in bytes/second or 0.
568
+ */
569
+ public function get_upload_speed() {
570
+ if ( $this->timer['cycle'] > 0 )
571
+ if ( $this->file['size'] < $this->chunk_size )
572
+ return $this->file['size'] / $this->timer['cycle'];
573
+ else
574
+ return $this->chunk_size / $this->timer['cycle'];
575
+ return 0;
576
+ }
577
+
578
+ /**
579
+ * Get the percentage of the file uploaded.
580
+ *
581
+ * @return float Returns a percentage on success, 0 on failure.
582
+ */
583
+ public function get_upload_percentage() {
584
+ if ( isset( $this->file['path'] ) )
585
+ return $this->file['pointer'] * 100 / $this->file['size'];
586
+ return 0;
587
+ }
588
+
589
+ /**
590
+ * Returns the time taken for an upload to complete.
591
+ *
592
+ * @access public
593
+ * @return float Returns the number of seconds the last upload took to complete, 0 if there has been no completed upload.
594
+ */
595
+ public function time_taken() {
596
+ return $this->timer['delta'];
597
+ }
598
+
599
+ public function get_content_link( $id, $title ) {
600
+
601
+ $feed = $this->get_feed($this->base_url . $id);
602
+
603
+ if ( is_wp_error( $feed ) )
604
+ return $feed;
605
+
606
+ if ( $feed->title != $title )
607
+ return new WP_Error( 'bad_response', "Unexpected response");
608
+
609
+ $att = $feed->content->attributes();
610
+ return $att['src'];
611
+
612
+ }
613
+
614
+ public function download_data( $link, $saveas ) {
615
+
616
+ $result = $this->request( $link );
617
+
618
+ if ( is_wp_error( $result ) )
619
+ return $result;
620
+
621
+ if ( $result['response']['code'] != '200' )
622
+ return new WP_Error( 'bad_response', "Received response code '" . $result['response']['code'] . " " . $result['response']['message'] . "' while trying to get '" . $url . "'." );
623
+
624
+ file_put_contents($saveas, $result['body']);
625
+
626
+ }
627
+
628
+
629
+
630
+ }
trunk/includes/ftp.class.php ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /* from http://www.solutionbot.com/2009/01/02/php-ftp-class/ */
3
+ class ftp_wrapper
4
+ {
5
+ private $conn_id;
6
+ private $host;
7
+ private $username;
8
+ private $password;
9
+ private $port;
10
+ public $timeout = 90;
11
+ public $passive = false;
12
+ public $ssl = false;
13
+ public $system_type = '';
14
+
15
+
16
+ public function __construct($host, $username, $password, $port = 21)
17
+ {
18
+ $this->host = $host;
19
+ $this->username = $username;
20
+ $this->password = $password;
21
+ $this->port = $port;
22
+ }
23
+
24
+ public function connect()
25
+ {
26
+ if ($this->ssl == false)
27
+ {
28
+ $this->conn_id = ftp_connect($this->host, $this->port);
29
+ }
30
+ else
31
+ {
32
+ if (function_exists('ftp_ssl_connect'))
33
+ {
34
+ $this->conn_id = ftp_ssl_connect($this->host, $this->port);
35
+ }
36
+ else
37
+ {
38
+ return false;
39
+ }
40
+ }
41
+
42
+ $result = ftp_login($this->conn_id, $this->username, $this->password);
43
+
44
+ if ($result == true)
45
+ {
46
+ ftp_set_option($this->conn_id, FTP_TIMEOUT_SEC, $this->timeout);
47
+
48
+ if ($this->passive == true)
49
+ {
50
+ ftp_pasv($this->conn_id, true);
51
+ }
52
+ else
53
+ {
54
+ ftp_pasv($this->conn_id, false);
55
+ }
56
+
57
+ $this->system_type = ftp_systype($this->conn_id);
58
+
59
+ return true;
60
+ }
61
+ else
62
+ {
63
+ return false;
64
+ }
65
+ }
66
+
67
+ public function put($local_file_path, $remote_file_path, $mode = FTP_ASCII)
68
+ {
69
+ if (ftp_put($this->conn_id, $remote_file_path, $local_file_path, $mode))
70
+ {
71
+ return true;
72
+ }
73
+ else
74
+ {
75
+ return false;
76
+ }
77
+ }
78
+
79
+ public function get($local_file_path, $remote_file_path, $mode = FTP_ASCII)
80
+ {
81
+ if (ftp_get($this->conn_id, $local_file_path, $remote_file_path, $mode))
82
+ {
83
+ return true;
84
+ }
85
+ else
86
+ {
87
+ return false;
88
+ }
89
+ }
90
+
91
+ public function chmod($permissions, $remote_filename)
92
+ {
93
+ if ($this->is_octal($permissions))
94
+ {
95
+ $result = ftp_chmod($this->conn_id, $permissions, $remote_filename);
96
+ if ($result)
97
+ {
98
+ return true;
99
+ }
100
+ else
101
+ {
102
+ return false;
103
+ }
104
+ }
105
+ else
106
+ {
107
+ throw new Exception('$permissions must be an octal number');
108
+ }
109
+ }
110
+
111
+ public function chdir($directory)
112
+ {
113
+ ftp_chdir($this->conn_id, $directory);
114
+ }
115
+
116
+ public function delete($remote_file_path)
117
+ {
118
+ if (ftp_delete($this->conn_id, $remote_file_path))
119
+ {
120
+ return true;
121
+ }
122
+ else
123
+ {
124
+ return false;
125
+ }
126
+ }
127
+
128
+ public function make_dir($directory)
129
+ {
130
+ if (ftp_mkdir($this->conn_id, $directory))
131
+ {
132
+ return true;
133
+ }
134
+ else
135
+ {
136
+ return false;
137
+ }
138
+ }
139
+
140
+ public function rename($old_name, $new_name)
141
+ {
142
+ if (ftp_rename($this->conn_id, $old_name, $new_name))
143
+ {
144
+ return true;
145
+ }
146
+ else
147
+ {
148
+ return false;
149
+ }
150
+ }
151
+
152
+ public function remove_dir($directory)
153
+ {
154
+ if (ftp_rmdir($this->conn_id, $directory))
155
+ {
156
+ return true;
157
+ }
158
+ else
159
+ {
160
+ return false;
161
+ }
162
+ }
163
+
164
+ public function dir_list($directory)
165
+ {
166
+ $contents = ftp_nlist($this->conn_id, $directory);
167
+ return $contents;
168
+ }
169
+
170
+ public function cdup()
171
+ {
172
+ ftp_cdup($this->conn_id);
173
+ }
174
+
175
+ public function current_dir()
176
+ {
177
+ return ftp_pwd($this->conn_id);
178
+ }
179
+
180
+ private function is_octal($i)
181
+ {
182
+ return decoct(octdec($i)) == $i;
183
+ }
184
+
185
+ public function __destruct()
186
+ {
187
+ if ($this->conn_id)
188
+ {
189
+ ftp_close($this->conn_id);
190
+ }
191
+ }
192
+ }
193
+ ?>
trunk/includes/updraft-restorer.php ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class Updraft_Restorer extends WP_Upgrader {
3
+
4
+ function backup_strings() {
5
+ $this->strings['no_package'] = __('Backup file not available.');
6
+ $this->strings['unpack_package'] = __('Unpacking backup...');
7
+ $this->strings['moving_old'] = __('Moving old directory out of the way...');
8
+ $this->strings['moving_backup'] = __('Moving unpackaged backup in place...');
9
+ $this->strings['cleaning_up'] = __('Cleaning up detritus...');
10
+ $this->strings['old_move_failed'] = __('Could not move old dir out of the way.');
11
+ $this->strings['new_move_failed'] = __('Could not move new dir into place. Check your wp-content/upgrade folder.');
12
+ $this->strings['delete_failed'] = __('Failed to delete working directory after restoring.');
13
+ }
14
+
15
+ function restore_backup($backup_file,$type) {
16
+ global $wp_filesystem;
17
+ $this->init();
18
+ $this->backup_strings();
19
+
20
+ $res = $this->fs_connect( array(ABSPATH, WP_CONTENT_DIR) );
21
+ if(!$res) {
22
+ exit;
23
+ }
24
+
25
+ $wp_dir = trailingslashit($wp_filesystem->abspath());
26
+
27
+ $download = $this->download_package( $backup_file );
28
+ if ( is_wp_error($download) )
29
+ return $download;
30
+
31
+ $delete = (get_option('updraft_delete_local'))?true:false;
32
+
33
+ $working_dir = $this->unpack_package( $download , $delete );
34
+ if ( is_wp_error($working_dir) )
35
+ return $working_dir;
36
+
37
+ if ($type == "others" ) {
38
+
39
+ // In this special case, the backup contents are not in a folder, so it is not simply a case of moving the folder around, but rather looping over all that we find
40
+
41
+ $upgrade_files = $wp_filesystem->dirlist($working_dir);
42
+ if ( !empty($upgrade_files) ) {
43
+ foreach ( $upgrade_files as $filestruc ) {
44
+ $file = $filestruc['name'];
45
+ # Sanity check (should not be possible as these were excluded at backup time)
46
+ if ($file != "plugins" && $file != "themes" && $file != "uploads" && $file != "upgrade") {
47
+ # First, move the existing one, if necessary (may not be present)
48
+ if ($wp_filesystem->exists($wp_dir . "wp-content/$file")) {
49
+ if ( !$wp_filesystem->move($wp_dir . "wp-content/$file", $wp_dir . "wp-content/$file-old", true) ) {
50
+ return new WP_Error('old_move_failed', $this->strings['old_move_failed']);
51
+ }
52
+ }
53
+ # Now, move in the new one
54
+ if ( !$wp_filesystem->move($working_dir . "/$file", $wp_dir . "wp-content/$file", true) ) {
55
+ return new WP_Error('new_move_failed', $this->strings['new_move_failed']);
56
+ }
57
+ }
58
+ }
59
+ }
60
+
61
+ } else {
62
+
63
+ show_message($this->strings['moving_old']);
64
+ if ( !$wp_filesystem->move($wp_dir . "wp-content/$type", $wp_dir . "wp-content/$type-old", true) ) {
65
+ return new WP_Error('old_move_failed', $this->strings['old_move_failed']);
66
+ }
67
+
68
+ show_message($this->strings['moving_backup']);
69
+ if ( !$wp_filesystem->move($working_dir . "/$type", $wp_dir . "wp-content/$type", true) ) {
70
+ return new WP_Error('new_move_failed', $this->strings['new_move_failed']);
71
+ }
72
+
73
+ }
74
+
75
+ show_message($this->strings['cleaning_up']);
76
+ if ( !$wp_filesystem->delete($working_dir) ) {
77
+ return new WP_Error('delete_failed', $this->strings['delete_failed']);
78
+ }
79
+
80
+
81
+ switch($type) {
82
+ case 'uploads':
83
+ $wp_filesystem->chmod($wp_dir . "wp-content/$type", 0777, true);
84
+ break;
85
+ default:
86
+ $wp_filesystem->chmod($wp_dir . "wp-content/$type", FS_CHMOD_DIR);
87
+ }
88
+ }
89
+
90
+ }
91
+ ?>
trunk/readme.txt ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ === UpdraftPlus Backup ===
2
+ Contributors: David Anderson
3
+ Tags: backup, restore, database, cloud, amazon, s3, Amazon S3, google drive, google, gdrive, ftp, cloud, updraft, back up
4
+ Requires at least: 3.2
5
+ Tested up to: 3.5
6
+ Stable tag: 1.0.8
7
+ Donate link: http://david.dw-perspective.org.uk/donate
8
+ License: GPLv3 or later
9
+
10
+ == Upgrade Notice ==
11
+ Fixed bug with Google Drive uploads
12
+
13
+ == Description ==
14
+
15
+ UpdraftPlus simplifies backups (and restoration). Backup into the cloud (Amazon S3, Google Drive, FTP, and email) and restore with a single click. Backups of files and database can have separate schedules.
16
+
17
+ * Supports backups to Amazon S3, Google Drive, FTP, email
18
+ * One-click restore
19
+ * Backup automatically on a repeating schedule
20
+ * Files and databases can have separate schedules
21
+ * Failed uploads are automatically resumed/retried
22
+ * Select which files to backup (plugins, themes, content, other)
23
+ * Databases can be encrypted for security
24
+ * Debug mode that gives full logging
25
+
26
+ == Installation ==
27
+
28
+ Standard WordPress plugin installation:
29
+
30
+ 1. Search for "UpdraftPlus" in your site's admin area plugin page
31
+ 2. Press 'Install'
32
+ 3. Go to the options page and go through the questions there
33
+
34
+ == Frequently Asked Questions ==
35
+
36
+ = How is this better than the original Updraft? =
37
+
38
+ You can check the changelog for changes; but the original Updraft, before I forked it, had three major problems. Firstly, it only backed up WP core tables from the database; if any of your plugins stored data in extra tables, then they were not backed up. Secondly, it only backed up your plugins/themes/uploads and not any further directories inside wp-content that other plugins might have created. Thirdly, the database backup did not include charset information, which meant that you needed to know some SQL wizardry to actually be able to use the backup. I made UpdraftPlus out of my experience of trying to back up several sites with Updraft. Then, I added encryption for the database file for extra peace of mind, and future-proofed by getting rid of some deprecated aspects.
39
+
40
+ = I like automating WordPress, and using the command-line. Please tell me more. =
41
+
42
+ That's very good of you, thank you. You are looking for WordShell, <a href="http://wordshell.net">http://wordshell.net</a>.
43
+
44
+ = I found a bug. What do I do? =
45
+
46
+ Contact me! This is a complex plugin and the only way I can ensure it's robust is to get bug reports and fix the problems that crop up. Please turn on debugging mode (in the UpdraftPlus options page) and then try again, and after that send me the log if you can find it (it is in the directory wp-content/updraft, so FTP in and look for it there). If you cannot find the log, then I may not be able to help, but you can try - include as much information as you can when reporting (PHP version, your blog's site, the error you saw and how you got to the page that caused it, etcetera). If you can send a patch, that's even better.
47
+
48
+ = Some of my files have uploaded into my cloud storage, but not others. =
49
+
50
+ From version 0.9.0, UpdraftPlus features a resumption feature - if you wait 5 minutes and visit a page on your site, then it should re-try not-yet-uploaded files. If that fails, then turn on debugging and paste the debug log (log in via FTP, and look in wp-content/updraft) into the support forum.
51
+
52
+ = I want to restore, but have failed to do so from the WP Admin console =
53
+
54
+ That's no problem. If you have your backed files, then you simply need to unzip them into the right places. UpdraftPlus does not back up the WordPress core - you can just get a fresh copy of that from www.wordpress.org. After installing that, then unzip the zip files for your uploads, themes and plugins back into the wp-content directory. Then re-install the database (e.g. by running it through PHPMyAdmin). Please don't ask me how to carry out these steps - they are basic operations which you can hire any of hundreds of thousands of people to show you how to do.
55
+
56
+ = Anything essential to know? =
57
+
58
+ After you have set up UpdraftPlus, you must check that your backups are taking place successfully. WordPress is a complex piece of software that runs in many situations. Don't wait until you need your backups before you find out that they never worked in the first place. Remember, there's no warranty and no guarantees - this is free software.
59
+
60
+ = What exactly does UpdraftPlus back up ? =
61
+
62
+ Unless you disable any of these, it will back up your database (all tables which have been prefixed with the prefix for this WordPress installation, both core tables and extra ones added by plugins), your plugins folder, your themes folder, your uploads folder and any extra folders that other plugins have created in the WordPress content directory.
63
+
64
+ = What does UpdraftPlus not back up ? =
65
+
66
+ It does not back up WordPress core (since you can always get another copy of this from wordpress.org), and does not back up any extra files which you have added outside of the WordPress content directory (files which, by their nature, are unknown to WordPress). By default the WordPress content directory is "wp-content" in your WordPress root. It will not back up database tables which do not have the WordPress prefix (i.e. database tables from other applications but sharing a database with WordPress).
67
+
68
+ = Any known bugs ? =
69
+ Not a bug as such, but one major issue to be aware of is that backups of very large sites (lots of uploaded media) can fail due to timing out. This depends on how many seconds your web host allows a PHP process to run. With such sites, you need to use Amazon S3, which UpdraftPlus supports (since 0.9.20) or Google Drive (since 0.9.21) with chunked, resumable uploads. Other backup methods have code (since 0.9.0) to retry failed uploads of an archive, but the upload cannot be chunked, so if an archive is enormous (i.e. cannot be completely uploaded in the time that PHP is allowed for running on your web host) it cannot work.
70
+
71
+ = I encrypted my database - how do I decrypt it? =
72
+
73
+ If you have the encryption key entered in your settings and you are restoring from the settings interface, then it will automatically decrypt. Otherwise, use the file example-decrypt.php found in the plugin directory.
74
+
75
+ = I lost my encryption key - what can I do? =
76
+
77
+ Nothing, probably. That's the point of an encryption key - people who don't have it can't get the data. Hire an encryption expert to build a super computer to try to break the encryption by brute force, at a price.
78
+
79
+ = My site was hacked, and I have no backups! I thought UpdraftPlus was working! Can I kill you? =
80
+ No, there's no warranty or guarantee, etc. It's completely up to you to verify that UpdraftPlus is working correctly. If it doesn't then that's unfortunate, but this is a free plugin.
81
+
82
+ == Changelog ==
83
+
84
+ = 1.0.8 - 12/20/2012 =
85
+ * Fixed bug that set 1Tb (instead of 1Mb) chunk sizes for Google Drive uploads
86
+ * Added link to some screenshots to help with Google Drive setup
87
+
88
+ = 1.0.5 - 12/13/2012 =
89
+ * Tweaked default Google Drive options
90
+
91
+ = 1.0.4 - 12/10/2012 =
92
+ * Implemented resumption/chunked uploading on Google Drive - much bigger sites can now be backed up
93
+ * Fixed bug whereby setting for deleting local backups was lost
94
+ * Now marked as 1.0, since we are feature-complete with targeted features for this release
95
+ * Made description fuller
96
+
97
+ = 0.9.20 - 12/06/2012 =
98
+ * Updated to latest S3.php library with chunked uploading patch
99
+ * Implemented chunked uploading on Amazon S3 - much bigger sites can now be backed up with S3
100
+
101
+ = 0.9.10 - 11/22/2012 =
102
+ * Completed basic Google Drive support (thanks to Sorin Iclanzan, code taken from "Backup" plugin under GPLv3+); now supporting uploading, purging and restoring - i.e. full UpdraftPlus functionality
103
+ * Licence change to GPLv3+ (from GPLv2+) to allow incorporating Sorin's code
104
+ * Tidied/organised the settings screen further
105
+
106
+ = 0.9.2 - 11/21/2012 =
107
+ * Failed uploads can now be re-tried, giving really big blogs a better opportunity to eventually succeed uploading
108
+
109
+ = 0.8.51 - 11/19/2012 =
110
+ * Moved screenshot into assets, reducing plugin download size
111
+
112
+ = 0.8.50 - 10/13/2012 =
113
+ * Important new feature: back up other directories found in the WP content (wp-content) directory (not just plugins/themes/uploads, as in original Updraft)
114
+
115
+ = 0.8.37 - 10/12/2012 =
116
+ * Don't whinge about Google Drive authentication if that method is not current
117
+
118
+ = 0.8.36 - 10/03/2012 =
119
+ * Support using sub-directories in Amazon S3
120
+ * Some more debug logging for Amazon S3
121
+
122
+ = 0.8.33 - 09/19/2012 =
123
+ * Work around some web hosts with invalid safe_mode configurations
124
+
125
+ = 0.8.32 - 09/17/2012 =
126
+ * Fix a subtle bug that caused database tables from outside of this WordPress install to be backed up
127
+
128
+ = 0.8.31 - 09/08/2012 =
129
+ * Fixed error deleting old S3 backups. If your expired S3 backups were not deleted, they should be in future - but you will need to delete manually those that expired before you installed this update.
130
+ * Fixed minor bug closing log file
131
+ * Marked as working with WordPress 3.4.2
132
+
133
+ = 0.8.29 - 06/29/2012 =
134
+ * Marking as tested up to WordPress 3.4.1
135
+
136
+ = 0.8.28 - 06/06/2012 =
137
+ * Now experimentally supports Google Drive (thanks to Sorin Iclanzan, code re-used from his Google Drive-only 'backup' plugin)
138
+ * New feature: backup files and database on separate schedules
139
+ * Tidied and improved retain behaviour
140
+
141
+ = 0.7.7 - 05/29/2012 =
142
+ * Implementation of a logging mechanism to allow easier debugging and development
143
+
144
+ = 0.7.4 - 05/21/2012 =
145
+ * Removed CloudFront method; I have no way of testing this
146
+ * Backup all tables found in the database that have this site's table prefix
147
+ * If encryption fails, then abort (don't revert to not encrypting)
148
+ * Added ability to decrypt encrypted database backups
149
+ * Added ability to opt out of backing up each file group
150
+ * Now adds database character set, the lack of which before made database backups unusable without modifications
151
+ * Version number bump to make clear that this is an improvement on the original Updraft, and is now tried and tested
152
+
153
+ = 0.1.3 - 01/16/2012 =
154
+ * Force backup of all tables found in database (vanilla Updraft only backed up WP core tables)
155
+ * Tweak notification email to include site name
156
+
157
+ = 0.1 - 08/10/2011 =
158
+
159
+ * A fork of Updraft 0.6.1 by Paul Kehrer with the following improvements
160
+ * Replaced deprecated function calls (in WordPress 3.2.1)
161
+ * Removed all warnings from basic admin page with WP_DEBUG on
162
+ * Implemented encrypted backup (but not yet automatic restoration) on database
163
+ * Some de-uglification of admin interface
164
+
165
+
166
+ == Screenshots ==
167
+
168
+ 1. Configuration page
169
+
170
+
171
+ == License ==
172
+
173
+ Portions copyright 2011-2 David Anderson
174
+ Portions copyright 2010 Paul Kehrer
175
+
176
+ This program is free software; you can redistribute it and/or modify
177
+ it under the terms of the GNU General Public License as published by
178
+ the Free Software Foundation; either version 2 of the License, or
179
+ (at your option) any later version.
180
+
181
+ This program is distributed in the hope that it will be useful,
182
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
183
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
184
+ GNU General Public License for more details.
185
+
186
+ You should have received a copy of the GNU General Public License
187
+ along with this program; if not, write to the Free Software
188
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
189
+
trunk/updraftplus.php ADDED
@@ -0,0 +1,2418 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Plugin Name: UpdraftPlus - Backup/Restore
4
+ Plugin URI: http://wordpress.org/extend/plugins/updraftplus
5
+ Description: Uploads, themes, plugins, and your DB can be automatically backed up to Amazon S3, Google Drive, FTP, or emailed, on separate schedules.
6
+ Author: David Anderson.
7
+ Version: 1.0.8
8
+ Donate link: http://david.dw-perspective.org.uk/donate
9
+ License: GPLv3 or later
10
+ Author URI: http://wordshell.net
11
+ */
12
+
13
+ /*
14
+ TODO (some of these items mine, some from original Updraft awaiting review):
15
+ //Add DropBox and Microsoft Skydrive support
16
+ //Backup record should include *where* the backup was placed, so that we don't attempt to delete old ones from the wrong place? (Or would that be unexpected to users to have things from elsewhere deleted?)
17
+ //improve error reporting. s3 and dir backup have decent reporting now, but not sure i know what to do from here
18
+ //list backups that aren't tracked (helps with double backup problem)
19
+ //investigate $php_errormsg further
20
+ //pretty up return messages in admin area
21
+ //check s3/ftp download
22
+
23
+ //Rip out the "last backup" bit, and/or put in a display of the last log
24
+
25
+ Encrypt filesystem, if memory allows (and have option for abort if not); split up into multiple zips when needed
26
+ // Does not delete old custom directories upon a restore?
27
+ */
28
+
29
+ /* Portions copyright 2010 Paul Kehrer
30
+ Portions copyright 2011-12 David Anderson
31
+ Other portions copyright as indicated authors in the relevant files
32
+ Particular thanks to Sorin Iclanzan, author of the "Backup" plugin, from which much Google Drive code was taken under the GPLv3+
33
+
34
+ This program is free software; you can redistribute it and/or modify
35
+ it under the terms of the GNU General Public License as published by
36
+ the Free Software Foundation; either version 3 of the License, or
37
+ (at your option) any later version.
38
+
39
+ This program is distributed in the hope that it will be useful,
40
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
41
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
42
+ GNU General Public License for more details.
43
+
44
+ You should have received a copy of the GNU General Public License
45
+ along with this program; if not, write to the Free Software
46
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
47
+ */
48
+ // TODO: Note this might *lower* the limit - should check first.
49
+
50
+ @set_time_limit(900); //15 minutes max. i'm not sure how long a really big site could take to back up?
51
+
52
+ $updraft = new UpdraftPlus();
53
+
54
+ if(!$updraft->memory_check(192)) {
55
+ # TODO: Better solution is to split the backup set into manageable chunks based on this limit
56
+ @ini_set('memory_limit', '192M'); //up the memory limit for large backup files... should split the backup set into manageable chunks based on the limit
57
+ }
58
+
59
+ define('UPDRAFT_DEFAULT_OTHERS_EXCLUDE','upgrade,cache,updraft,index.php');
60
+
61
+ class UpdraftPlus {
62
+
63
+ var $version = '1.0.8';
64
+
65
+ var $dbhandle;
66
+ var $errors = array();
67
+ var $nonce;
68
+ var $logfile_name = "";
69
+ var $logfile_handle = false;
70
+ var $backup_time;
71
+ var $gdocs;
72
+ var $gdocs_access_token;
73
+
74
+ function __construct() {
75
+ // Initialisation actions
76
+ # Create admin page
77
+ add_action('admin_menu', array($this,'add_admin_pages'));
78
+ add_action('admin_init', array($this,'admin_init'));
79
+ add_action('updraft_backup', array($this,'backup_files'));
80
+ add_action('updraft_backup_database', array($this,'backup_database'));
81
+ # backup_all is used by the manual "Backup Now" button
82
+ add_action('updraft_backup_all', array($this,'backup_all'));
83
+ # this is our runs-after-backup event, whose purpose is to see if it succeeded or failed, and resume/mom-up etc.
84
+ add_action('updraft_backup_resume', array($this,'backup_resume'));
85
+ add_action('wp_ajax_updraft_download_backup', array($this, 'updraft_download_backup'));
86
+ # http://codex.wordpress.org/Plugin_API/Filter_Reference/cron_schedules
87
+ add_filter('cron_schedules', array($this,'modify_cron_schedules'));
88
+ add_filter('plugin_action_links', array($this, 'plugin_action_links'), 10, 2);
89
+ add_action('init', array($this, 'googledrive_backup_auth'));
90
+ }
91
+
92
+ // Handle Google OAuth 2.0 - ?page=updraftplus&action=auth
93
+ function googledrive_backup_auth() {
94
+ if ( is_admin() && isset( $_GET['page'] ) && $_GET['page'] == 'updraftplus' && isset( $_GET['action'] ) && $_GET['action'] == 'auth' ) {
95
+ if ( isset( $_GET['state'] ) ) {
96
+ if ( $_GET['state'] == 'token' )
97
+ $this->gdrive_auth_token();
98
+ elseif ( $_GET['state'] == 'revoke' )
99
+ $this->gdrive_auth_revoke();
100
+ } elseif (isset($_GET['updraftplus_googleauth'])) {
101
+ $this->gdrive_auth_request();
102
+ }
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Acquire single-use authorization code from Google OAuth 2.0
108
+ */
109
+ function gdrive_auth_request() {
110
+ $params = array(
111
+ 'response_type' => 'code',
112
+ 'client_id' => get_option('updraft_googledrive_clientid'),
113
+ 'redirect_uri' => admin_url('options-general.php?page=updraftplus&action=auth'),
114
+ 'scope' => 'https://www.googleapis.com/auth/drive.file https://docs.google.com/feeds/ https://docs.googleusercontent.com/ https://spreadsheets.google.com/feeds/',
115
+ 'state' => 'token',
116
+ 'access_type' => 'offline',
117
+ 'approval_prompt' => 'auto'
118
+ );
119
+ header('Location: https://accounts.google.com/o/oauth2/auth?'.http_build_query($params));
120
+ }
121
+
122
+ /**
123
+ * Get a Google account access token using the refresh token
124
+ */
125
+ function access_token( $token, $client_id, $client_secret ) {
126
+ $context = array(
127
+ 'http' => array(
128
+ 'method' => 'POST',
129
+ 'header' => 'Content-type: application/x-www-form-urlencoded',
130
+ 'content' => http_build_query( array(
131
+ 'refresh_token' => $token,
132
+ 'client_id' => $client_id,
133
+ 'client_secret' => $client_secret,
134
+ 'grant_type' => 'refresh_token'
135
+ ) )
136
+ )
137
+ );
138
+ $this->log("Google Drive: requesting access token: client_id=$client_id");
139
+ $result = @file_get_contents('https://accounts.google.com/o/oauth2/token', false, stream_context_create($context));
140
+ if($result) {
141
+ $result = json_decode( $result, true );
142
+ if ( isset( $result['access_token'] ) ) {
143
+ $this->log("Google Drive: successfully obtained access token");
144
+ return $result['access_token'];
145
+ } else {
146
+ $this->log("Google Drive error when requesting access token: response does not contain access_token");
147
+ return false;
148
+ }
149
+ } else {
150
+ $this->log("Google Drive error when requesting access token: no response");
151
+ return false;
152
+ }
153
+ }
154
+
155
+ function googledrive_delete_file( $file, $token) {
156
+ $ids = get_option('updraft_file_ids', array());
157
+ if (!isset($ids[$file])) {
158
+ $this->log("Could not delete: could not find a record of the Google Drive file ID for this file");
159
+ return;
160
+ } else {
161
+ $del == $this->gdocs->delete_resource($ids[$file]);
162
+ if (is_wp_error($del)) {
163
+ foreach ($del->get_error_messages() as $msg) {
164
+ $this->log("Deletion failed: $msg");
165
+ }
166
+ } else {
167
+ $this->log("Deletion successful");
168
+ unset($ids[$file]);
169
+ update_option('updraft_file_ids', $ids);
170
+ }
171
+ }
172
+ return;
173
+ }
174
+
175
+ /**
176
+ * Get a Google account refresh token using the code received from gdrive_auth_request
177
+ */
178
+ function gdrive_auth_token() {
179
+ if( isset( $_GET['code'] ) ) {
180
+ $context = array(
181
+ 'http' => array(
182
+ 'timeout' => 30,
183
+ 'method' => 'POST',
184
+ 'header' => 'Content-type: application/x-www-form-urlencoded',
185
+ 'content' => http_build_query( array(
186
+ 'code' => $_GET['code'],
187
+ 'client_id' => get_option('updraft_googledrive_clientid'),
188
+ 'client_secret' => get_option('updraft_googledrive_secret'),
189
+ 'redirect_uri' => admin_url('options-general.php?page=updraftplus&action=auth'),
190
+ 'grant_type' => 'authorization_code'
191
+ ) )
192
+ )
193
+ );
194
+ // TODO Use curl??
195
+ $result = @file_get_contents('https://accounts.google.com/o/oauth2/token', false, stream_context_create($context));
196
+ # Oddly, sometimes fails and then trying again works...
197
+ /*
198
+ if (!$result) { sleep(1); $result = @file_get_contents('https://accounts.google.com/o/oauth2/token', false, stream_context_create($context));}
199
+ if (!$result) { sleep(1); $result = @file_get_contents('https://accounts.google.com/o/oauth2/token', false, stream_context_create($context));}
200
+ */
201
+ if($result) {
202
+ $result = json_decode( $result, true );
203
+ if ( isset( $result['refresh_token'] ) ) {
204
+ update_option('updraft_googledrive_token',$result['refresh_token']); // Save token
205
+ header('Location: '.admin_url('options-general.php?page=updraftplus&message=' . __( 'Google Drive authorization was successful.', 'updraftplus' ) ) );
206
+ }
207
+ else {
208
+ header('Location: '.admin_url('options-general.php?page=updraftplus&error=' . __( 'No refresh token was received!', 'updraftplus' ) ) );
209
+ }
210
+ } else {
211
+ header('Location: '.admin_url('options-general.php?page=updraftplus&error=' . __( 'Bad response!', 'backup' ) ) );
212
+ }
213
+ }
214
+ else {
215
+ header('Location: '.admin_url('options-general.php?page=updraftplus&error=' . __( 'Authorisation failed!', 'backup' ) ) );
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Revoke a Google account refresh token
221
+ */
222
+ function gdrive_auth_revoke() {
223
+ @file_get_contents( 'https://accounts.google.com/o/oauth2/revoke?token=' . get_option('updraft_googledrive_token') );
224
+ update_option('updraft_googledrive_token','');
225
+ header( 'Location: '.admin_url( 'options-general.php?page=updraftplus&message=' . __( 'Authorization revoked.', 'backup' ) ) );
226
+ }
227
+
228
+ # Adds the settings link under the plugin on the plugin screen.
229
+ function plugin_action_links($links, $file) {
230
+ if ($file == plugin_basename(__FILE__)){
231
+ $settings_link = '<a href="'.site_url().'/wp-admin/options-general.php?page=updraftplus">'.__("Settings", "UpdraftPlus").'</a>';
232
+ array_unshift($links, $settings_link);
233
+ $settings_link = '<a href="http://david.dw-perspective.org.uk/donate">'.__("Donate","UpdraftPlus").'</a>';
234
+ array_unshift($links, $settings_link);
235
+ }
236
+ return $links;
237
+ }
238
+
239
+ function backup_time_nonce() {
240
+ $this->backup_time = time();
241
+ $this->nonce = substr(md5(time().rand()),20);
242
+ }
243
+
244
+ # Logs the given line, adding date stamp and newline
245
+ function log($line) {
246
+ if ($this->logfile_handle) fwrite($this->logfile_handle,date('r')." ".$line."\n");
247
+ }
248
+
249
+ function backup_resume($resumption_no) {
250
+ @ignore_user_abort(true);
251
+ // This is scheduled for 5 minutes after a backup job starts
252
+ $bnonce = get_transient('updraftplus_backup_job_nonce');
253
+ if (!$bnonce) return;
254
+ $this->nonce = $bnonce;
255
+ $this->logfile_open($bnonce);
256
+ $this->log("Resume backup ($resumption_no): begin run (will check for any remaining jobs)");
257
+ $btime = get_transient('updraftplus_backup_job_time');
258
+ if (!$btime) {
259
+ $this->log("Did not find stored time setting - aborting");
260
+ return;
261
+ }
262
+ $this->log("Resuming backup: resumption=$resumption_no, nonce=$bnonce, begun at=$btime");
263
+ // Schedule again, to run in 5 minutes again, in case we again fail
264
+ $resume_delay = 300;
265
+ // A different argument than before is needed otherwise the event is ignored
266
+ $next_resumption = $resumption_no+1;
267
+ if ($next_resumption < 10) {
268
+ wp_schedule_single_event(time()+$resume_delay, 'updraft_backup_resume' ,array($next_resumption));
269
+ } else {
270
+ $this->log("This is our tenth attempt - will not try again");
271
+ }
272
+ $this->backup_time = $btime;
273
+
274
+ // Returns an array, most recent first, of backup sets
275
+ $backup_history = $this->get_backup_history();
276
+ if (!isset($backup_history[$btime])) $this->log("Error: Could not find a record in the database of a backup with this timestamp");
277
+
278
+ $our_files=$backup_history[$btime];
279
+ $undone_files = array();
280
+ foreach ($our_files as $key => $file) {
281
+ $hash = md5($file);
282
+ $fullpath = trailingslashit(get_option('updraft_dir')).$file;
283
+ if (get_transient('updraft_'.$hash) === "yes") {
284
+ $this->log("$file: $key: This file has been successfully uploaded in the last 3 hours");
285
+ } elseif (is_file($fullpath)) {
286
+ $this->log("$file: $key: This file has NOT been successfully uploaded in the last 3 hours: will retry");
287
+ $undone_files[$key] = $file;
288
+ } else {
289
+ $this->log("$file: Note: This file was not marked as successfully uploaded, but does not exist on the local filesystem");
290
+ $this->uploaded_file($file);
291
+ }
292
+ }
293
+
294
+ if (count($undone_files) == 0) {
295
+ $this->log("There were no files that needed uploading; backup job is finished");
296
+ return;
297
+ }
298
+
299
+ $this->log("Requesting backup of the files that were not successfully uploaded");
300
+ $this->cloud_backup($undone_files);
301
+ $this->cloud_backup_finish($undone_files);
302
+
303
+ $this->log("Resume backup ($resumption_no): finish run");
304
+
305
+ $this->backup_finish($next_resumption);
306
+
307
+ }
308
+
309
+ function backup_all() {
310
+ $this->backup(true,true);
311
+ }
312
+
313
+ function backup_files() {
314
+ # Note that the "false" for database gets over-ridden automatically if they turn out to have the same schedules
315
+ $this->backup(true,false);
316
+ }
317
+
318
+ function backup_database() {
319
+ # Note that nothing will happen if the file backup had the same schedule
320
+ $this->backup(false,true);
321
+ }
322
+
323
+ function logfile_open($nonce) {
324
+ //set log file name and open log file
325
+ $updraft_dir = $this->backups_dir_location();
326
+ $this->logfile_name = $updraft_dir. "/log.$nonce.txt";
327
+ // Use append mode in case it already exists
328
+ $this->logfile_handle = fopen($this->logfile_name, 'a');
329
+ }
330
+
331
+ //scheduled wp-cron events can have a race condition here if page loads are coming fast enough, but there's nothing we can do about it. TODO: I reckon there is. Store a transient based on the backup schedule. Then as the backup proceeds, check for its existence; if it has changed, then another task has begun, so abort.
332
+ function backup($backup_files, $backup_database) {
333
+
334
+ @ignore_user_abort(true);
335
+ //generate backup information
336
+ $this->backup_time_nonce();
337
+ // If we don't finish in 3 hours, then we won't finish
338
+ // This transient indicates the identity of the current backup job (which can be used to find the files and logfile)
339
+ set_transient("updraftplus_backup_job_nonce",$this->nonce,3600*3);
340
+ set_transient("updraftplus_backup_job_time",$this->backup_time,3600*3);
341
+ $this->logfile_open($this->nonce);
342
+
343
+ // Schedule the even to run later, which checks on success and can resume the backup
344
+ // We save the time to a variable because it is needed for un-scheduling
345
+ // $resume_delay = (get_option('updraft_debug_mode')) ? 60 : 300;
346
+ $resume_delay = 300;
347
+ wp_schedule_single_event(time()+$resume_delay, 'updraft_backup_resume', array(1));
348
+ $this->log("In case we run out of time, scheduled a resumption at: $resume_delay seconds from now");
349
+
350
+ // Log some information that may be helpful
351
+ global $wp_version;
352
+ $this->log("PHP version: ".phpversion()." WordPress version: ".$wp_version." Updraft version: ".$this->version." Backup files: $backup_files (schedule: ".get_option('updraft_interval','unset').") Backup DB: $backup_database (schedule: ".get_option('updraft_interval_database','unset').")");
353
+
354
+ # If the files and database schedules are the same, and if this the file one, then we rope in database too.
355
+ # On the other hand, if the schedules were the same and this was the database run, then there is nothing to do.
356
+ if (get_option('updraft_interval') == get_option('updraft_interval_database') || get_option('updraft_interval_database','xyz') == 'xyz' ) {
357
+ $backup_database = ($backup_files == true) ? true : false;
358
+ }
359
+
360
+ $this->log("Processed schedules. Tasks now: Backup files: $backup_files Backup DB: $backup_database");
361
+
362
+ # Possibly now nothing is to be done, except to close the log file
363
+ if ($backup_files || $backup_database) {
364
+
365
+ $backup_contains = "";
366
+
367
+ $backup_array = array();
368
+
369
+ //backup directories and return a numerically indexed array of file paths to the backup files
370
+ if ($backup_files) {
371
+ $this->log("Beginning backup of directories");
372
+ $backup_array = $this->backup_dirs();
373
+ $backup_contains = "Files only (no database)";
374
+ }
375
+
376
+ //backup DB and return string of file path
377
+ if ($backup_database) {
378
+ $this->log("Beginning backup of database");
379
+ $db_backup = $this->backup_db();
380
+ //add db path to rest of files
381
+ if(is_array($backup_array)) { $backup_array['db'] = $db_backup; }
382
+ $backup_contains = ($backup_files) ? "Files and database" : "Database only (no files)";
383
+ }
384
+
385
+ set_transient("updraftplus_backupcontains", $backup_contains, 3600*3);
386
+
387
+ //save this to our history so we can track backups for the retain feature
388
+ $this->log("Saving backup history");
389
+ // 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.
390
+ $this->save_backup_history($backup_array);
391
+
392
+ //cloud operations (S3,Google Drive,FTP,email,nothing)
393
+ //this also calls the retain (prune) feature at the end (done in this method to reuse existing cloud connections)
394
+ if(is_array($backup_array) && count($backup_array) >0) {
395
+ $this->log("Beginning dispatch of backup to remote");
396
+ $this->cloud_backup($backup_array);
397
+ }
398
+
399
+ //save the last backup info, including errors, if any
400
+ $this->log("Saving last backup information into WordPress db");
401
+ $this->save_last_backup($backup_array);
402
+
403
+ // Send the email
404
+ $this->cloud_backup_finish($backup_array);
405
+
406
+ }
407
+
408
+ // Close log file; delete and also delete transients if not in debug mode
409
+ $this->backup_finish(1);
410
+
411
+ }
412
+
413
+ function backup_finish($cancel_event) {
414
+
415
+ // 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.
416
+ if (empty($this->errors)) {
417
+ $this->log("There were no errors in the uploads, so the 'resume' event is being unscheduled");
418
+ wp_clear_scheduled_hook('updraft_backup_resume', array($cancel_event));
419
+ delete_transient("updraftplus_backup_job_nonce");
420
+ delete_transient("updraftplus_backup_job_time");
421
+ } else {
422
+ $this->log("There were errors in the uploads, so the 'resume' event is remaining scheduled");
423
+ }
424
+
425
+ @fclose($this->logfile_handle);
426
+
427
+ if (!get_option('updraft_debug_mode')) @unlink($this->logfile_name);
428
+
429
+ }
430
+
431
+ function cloud_backup_finish($backup_array) {
432
+
433
+ // Send the results email if requested
434
+ if(get_option('updraft_email') != "" && get_option('updraft_service') != 'email') $this->send_results_email();
435
+
436
+ }
437
+
438
+ function send_results_email() {
439
+
440
+ $sendmail_to = get_option('updraft_email');
441
+
442
+ $this->log("Sending email report to: ".$sendmail_to);
443
+
444
+ $append_log = (get_option('updraft_debug_mode') && $this->logfile_name != "") ? "\r\nLog contents:\r\n".file_get_contents($this->logfile_name) : "" ;
445
+
446
+ wp_mail($sendmail_to,'Backed up: '.get_bloginfo('name').' (UpdraftPlus '.$this->version.') '.date('Y-m-d H:i',time()),'Site: '.site_url()."\r\nUpdraftPlus WordPress backup is complete.\r\nBackup contains: ".get_transient("updraftplus_backupcontains")."\r\n\r\n".$this->wordshell_random_advert(0)."\r\n".$append_log);
447
+
448
+ }
449
+
450
+ function save_last_backup($backup_array) {
451
+ $success = (empty($this->errors))?1:0;
452
+
453
+ $last_backup = array('backup_time'=>$this->backup_time, 'backup_array'=>$backup_array, 'success'=>$success, 'errors'=>$this->errors);
454
+
455
+ update_option('updraft_last_backup', $last_backup);
456
+ }
457
+
458
+ // This should be called whenever a file is successfully uploaded
459
+ function uploaded_file($file, $id = false) {
460
+ # We take an MD5 hash because set_transient wants a name of 45 characters or less
461
+ $hash = md5($file);
462
+ $this->log("$file: $hash: recording as successfully uploaded");
463
+ set_transient("updraft_".$hash, "yes", 3600*4);
464
+ if ($id) {
465
+ $ids = get_option('updraft_file_ids', array() );
466
+ $ids[$file] = $id;
467
+ update_option('updraft_file_ids',$ids);
468
+ $this->log("Stored file<->id correlation in database ($file <-> $id)");
469
+ }
470
+ // Delete local files if the option is set
471
+ $this->delete_local($file);
472
+
473
+ }
474
+
475
+ function cloud_backup($backup_array) {
476
+ switch(get_option('updraft_service')) {
477
+ case 's3':
478
+ @set_time_limit(900);
479
+ $this->log("Cloud backup: S3");
480
+ if (count($backup_array) >0) $this->s3_backup($backup_array);
481
+ break;
482
+ case 'googledrive':
483
+ @set_time_limit(900);
484
+ $this->log("Cloud backup: Google Drive");
485
+ if (count($backup_array) >0) $this->googledrive_backup($backup_array);
486
+ break;
487
+ case 'ftp':
488
+ @set_time_limit(900);
489
+ $this->log("Cloud backup: FTP");
490
+ if (count($backup_array) >0) $this->ftp_backup($backup_array);
491
+ break;
492
+ case 'email':
493
+ @set_time_limit(900);
494
+ $this->log("Cloud backup: Email");
495
+ //files can easily get way too big for this...
496
+ foreach($backup_array as $type=>$file) {
497
+ $fullpath = trailingslashit(get_option('updraft_dir')).$file;
498
+ wp_mail(get_option('updraft_email'),"WordPress Backup ".date('Y-m-d H:i',$this->backup_time),"Backup is of the $type. Be wary; email backups may fail because of file size limitations on mail servers.",null,array($fullpath));
499
+ $this->uploaded_file($file);
500
+ }
501
+ //we don't break here so it goes and executes all the default behavior below as well. this gives us retain behavior for email
502
+ default:
503
+ $this->prune_retained_backups("local");
504
+ break;
505
+ }
506
+ }
507
+
508
+ // Carries out retain behaviour. Pass in a valid S3 or FTP object and path if relevant.
509
+ function prune_retained_backups($updraft_service,$remote_object,$remote_path) {
510
+ $this->log("Retain: beginning examination of existing backup sets");
511
+ $updraft_retain = get_option('updraft_retain');
512
+ // Number of backups to retain
513
+ $retain = (isset($updraft_retain))?get_option('updraft_retain'):1;
514
+ $this->log("Retain: user setting: number to retain = $retain");
515
+ // Returns an array, most recent first, of backup sets
516
+ $backup_history = $this->get_backup_history();
517
+ $db_backups_found = 0;
518
+ $file_backups_found = 0;
519
+ $this->log("Number of backup sets in history: ".count($backup_history));
520
+ foreach ($backup_history as $backup_datestamp => $backup_to_examine) {
521
+ // $backup_to_examine is an array of file names, keyed on db/plugins/themes/uploads
522
+ // The new backup_history array is saved afterwards, so remember to unset the ones that are to be deleted
523
+ $this->log("Examining backup set with datestamp: $backup_datestamp");
524
+ if (isset($backup_to_examine['db'])) {
525
+ $db_backups_found++;
526
+ $this->log("$backup_datestamp: this set includes a database (".$backup_to_examine['db']."); db count is now $db_backups_found");
527
+ if ($db_backups_found > $retain) {
528
+ $this->log("$backup_datestamp: over retain limit; will delete this database");
529
+ $file = $backup_to_examine['db'];
530
+ $this->log("$backup_datestamp: Delete this file: $file");
531
+ if ($file != '') {
532
+ $fullpath = trailingslashit(get_option('updraft_dir')).$file;
533
+ @unlink($fullpath); //delete it if it's locally available
534
+ if ($updraft_service == "s3") {
535
+ if (preg_match("#^([^/]+)/(.*)$#",$remote_path,$bmatches)) {
536
+ $s3_bucket=$bmatches[1];
537
+ $s3_uri = $bmatches[2]."/".$file;
538
+ } else {
539
+ $s3_bucket = $remote_path;
540
+ $s3_uri = $file;
541
+ }
542
+ $this->log("$backup_datestamp: Delete remote: bucket=$s3_bucket, URI=$s3_uri");
543
+ # Here we brought in the function deleteObject in order to get more direct access to any error
544
+ $rest = new S3Request('DELETE', $s3_bucket, $s3_uri);
545
+ $rest = $rest->getResponse();
546
+ if ($rest->error === false && $rest->code !== 204) {
547
+ $this->log("S3 Error: Expected HTTP response 204; got: ".$rest->code);
548
+ $this->error("S3 Error: Unexpected HTTP response code ".$rest->code." (expected 204)");
549
+ } elseif ($rest->error !== false) {
550
+ $this->log("S3 Error: ".$rest->error['code'].": ".$rest->error['message']);
551
+ $this->error("S3 delete error: ".$rest->error['code'].": ".$rest->error['message']);
552
+ }
553
+ } elseif ($updraft_service == "ftp") {
554
+ $this->log("$backup_datestamp: Delete remote ftp: $remote_path/$file");
555
+ @$remote_object->delete($remote_path.$file);
556
+ } elseif ($updraft_service == "googledrive") {
557
+ $this->log("$backup_datestamp: Delete remote file from Google Drive: $file");
558
+ $this->googledrive_delete_file($file,$remote_object);
559
+ }
560
+ }
561
+ unset($backup_to_examine['db']);
562
+ }
563
+ }
564
+ if (isset($backup_to_examine['plugins']) || isset($backup_to_examine['themes']) || isset($backup_to_examine['uploads']) || isset($backup_to_examine['others'])) {
565
+ $file_backups_found++;
566
+ $this->log("$backup_datestamp: this set includes files; fileset count is now $file_backups_found");
567
+ if ($file_backups_found > $retain) {
568
+ $this->log("$backup_datestamp: over retain limit; will delete this file set");
569
+ $file = isset($backup_to_examine['plugins']) ? $backup_to_examine['plugins'] : "";
570
+ $file2 = isset($backup_to_examine['themes']) ? $backup_to_examine['themes'] : "";
571
+ $file3 = isset($backup_to_examine['uploads']) ? $backup_to_examine['uploads'] : "";
572
+ $file4 = isset($backup_to_examine['others']) ? $backup_to_examine['others'] : "";
573
+ foreach (array($file,$file2,$file3,$file4) as $dofile) {
574
+ if ($dofile) {
575
+ $this->log("$backup_datestamp: Delete this file: $dofile");
576
+ $fullpath = trailingslashit(get_option('updraft_dir')).$dofile;
577
+ @unlink($fullpath); //delete it if it's locally available
578
+ if ($updraft_service == "s3") {
579
+ if (preg_match("#^([^/]+)/(.*)$#",$remote_path,$bmatches)) {
580
+ $s3_bucket=$bmatches[1];
581
+ $s3_uri = $bmatches[2]."/".$dofile;
582
+ } else {
583
+ $s3_bucket = $remote_path;
584
+ $s3_uri = $dofile;
585
+ }
586
+ $this->log("$backup_datestamp: Delete remote: bucket=$s3_bucket, URI=$s3_uri");
587
+ # Here we brought in the function deleteObject in order to get more direct access to any error
588
+ $rest = new S3Request('DELETE', $s3_bucket, $s3_uri);
589
+ $rest = $rest->getResponse();
590
+ if ($rest->error === false && $rest->code !== 204) {
591
+ $this->log("S3 Error: Expected HTTP response 204; got: ".$rest->code);
592
+ $this->error("S3 Error: Unexpected HTTP response code ".$rest->code." (expected 204)");
593
+ } elseif ($rest->error !== false) {
594
+ $this->log("S3 Error: ".$rest->error['code'].": ".$rest->error['message']);
595
+ $this->error("S3 delete error: ".$rest->error['code'].": ".$rest->error['message']);
596
+ }
597
+ } elseif ($updraft_service == "ftp") {
598
+ $this->log("$backup_datestamp: Delete remote ftp: $remote_path/$dofile");
599
+ @$remote_object->delete($remote_path.$dofile);
600
+ } elseif ($updraft_service == "googledrive") {
601
+ $this->log("$backup_datestamp: Delete remote file from Google Drive: $dofile");
602
+ $this->googledrive_delete_file($dofile,$remote_object);
603
+ }
604
+ }
605
+ }
606
+ unset($backup_to_examine['plugins']);
607
+ unset($backup_to_examine['themes']);
608
+ unset($backup_to_examine['uploads']);
609
+ unset($backup_to_examine['others']);
610
+ }
611
+ }
612
+ // Delete backup set completely if empty, o/w just remove DB
613
+ if (count($backup_to_examine)==0) {
614
+ $this->log("$backup_datestamp: this backup set is now empty; will remove from history");
615
+ unset($backup_history[$backup_datestamp]);
616
+ } else {
617
+ $this->log("$backup_datestamp: this backup set remains non-empty; will retain in history");
618
+ $backup_history[$backup_datestamp] = $backup_to_examine;
619
+ }
620
+ }
621
+ $this->log("Retain: saving new backup history (sets now: ".count($backup_history).") and finishing retain operation");
622
+ update_option('updraft_backup_history',$backup_history);
623
+ }
624
+
625
+ function s3_backup($backup_array) {
626
+
627
+ if(!class_exists('S3')) require_once(dirname(__FILE__).'/includes/S3.php');
628
+
629
+ $s3 = new S3(get_option('updraft_s3_login'), get_option('updraft_s3_pass'));
630
+
631
+ $bucket_name = untrailingslashit(get_option('updraft_s3_remote_path'));
632
+ $bucket_path = "";
633
+ $orig_bucket_name = $bucket_name;
634
+
635
+ if (preg_match("#^([^/]+)/(.*)$#",$bucket_name,$bmatches)) {
636
+ $bucket_name = $bmatches[1];
637
+ $bucket_path = $bmatches[2]."/";
638
+ }
639
+
640
+ if (@$s3->putBucket($bucket_name, S3::ACL_PRIVATE)) {
641
+
642
+ foreach($backup_array as $file) {
643
+
644
+ // We upload in 5Mb chunks to allow more efficient resuming and hence uploading of larger files
645
+ $fullpath = trailingslashit(get_option('updraft_dir')).$file;
646
+ $chunks = floor(filesize($fullpath) / 5242880)+1;
647
+ $hash = md5($file);
648
+
649
+ $this->log("S3 upload: $fullpath (chunks: $chunks) -> s3://$bucket_name/$bucket_path$file");
650
+
651
+ $filepath = $bucket_path.$file;
652
+
653
+ // This is extra code for the 1-chunk case, but less overhead (no bothering with transients)
654
+ if ($chunks < 2) {
655
+ if (!$s3->putObjectFile($fullpath, $bucket_name, $filepath)) {
656
+ $this->log("S3 regular upload: failed");
657
+ $this->error("S3 Error: Failed to upload $fullpath. Error was ".$php_errormsg);
658
+ } else {
659
+ $this->log("S3 regular upload: success");
660
+ $this->uploaded_file($file);
661
+ }
662
+ } else {
663
+
664
+ // Retrieve the upload ID
665
+ $uploadId = get_transient("updraft_${hash}_uid");
666
+ if (empty($uploadId)) {
667
+ $uploadId = $s3->initiateMultipartUpload($bucket_name, $filepath);
668
+ if (empty($uploadId)) {
669
+ $this->log("S3 upload: failed: could not get uploadId for multipart upload");
670
+ continue;
671
+ } else {
672
+ $this->log("S3 chunked upload: got multipart ID: $uploadId");
673
+ set_transient("updraft_${hash}_uid", $uploadId, 3600*3);
674
+ }
675
+ } else {
676
+ $this->log("S3 chunked upload: retrieved previously obtained multipart ID: $uploadId");
677
+ }
678
+
679
+ $successes = 0;
680
+ $etags = array();
681
+ for ($i = 1 ; $i <= $chunks; $i++) {
682
+ # Shorted to upd here to avoid hitting the 45-character limit
683
+ $etag = get_transient("upd_${hash}_e$i");
684
+ if (strlen($etag) > 0) {
685
+ $this->log("S3 chunk $i: was already completed (etag: $etag)");
686
+ $successes++;
687
+ array_push($etags, $etag);
688
+ } else {
689
+ $etag = $s3->uploadPart($bucket_name, $filepath, $uploadId, $fullpath, $i);
690
+ if (is_string($etag)) {
691
+ $this->log("S3 chunk $i: uploaded (etag: $etag)");
692
+ array_push($etags, $etag);
693
+ set_transient("upd_${hash}_e$i", $etag, 3600*3);
694
+ $successes++;
695
+ } else {
696
+ $this->error("S3 chunk $i: upload failed");
697
+ $this->log("S3 chunk $i: upload failed");
698
+ }
699
+ }
700
+ }
701
+ if ($successes >= $chunks) {
702
+ $this->log("S3 upload: all chunks uploaded; will now instruct S3 to re-assemble");
703
+ if ($s3->completeMultipartUpload ($bucket_name, $filepath, $uploadId, $etags)) {
704
+ $this->log("S3 upload: re-assembly succeeded");
705
+ $this->uploaded_file($file);
706
+ } else {
707
+ $this->log("S3 upload: re-assembly failed");
708
+ $this->error("S3 upload: re-assembly failed");
709
+ }
710
+ } else {
711
+ $this->log("S3 upload: upload was not completely successful on this run");
712
+ }
713
+ }
714
+ }
715
+ $this->prune_retained_backups('s3',$s3,$orig_bucket_name);
716
+ } else {
717
+ $this->log("S3 Error: Failed to create bucket $bucket_name. Error was ".$php_errormsg);
718
+ $this->error("S3 Error: Failed to create bucket $bucket_name. Error was ".$php_errormsg);
719
+ }
720
+ }
721
+
722
+ // This function taken from wordpress.org/extend/plugins/backup, by Sorin Iclanzan, under the GPLv3 or later at your choice
723
+ function is_gdocs( $thing ) {
724
+ if ( is_object( $thing ) && is_a( $thing, 'UpdraftPlus_GDocs' ) )
725
+ return true;
726
+ return false;
727
+ }
728
+
729
+ // This function modified from wordpress.org/extend/plugins/backup, by Sorin Iclanzan, under the GPLv3 or later at your choice
730
+ function need_gdocs() {
731
+
732
+ if ( ! $this->is_gdocs( $this->gdocs ) ) {
733
+ if ( get_option('updraft_googledrive_token') == "" || get_option('updraft_googledrive_clientid') == "" || get_option('updraft_googledrive_secret') == "" ) {
734
+ $this->log("GoogleDrive: this account is not authorised");
735
+ return new WP_Error( "not_authorized", "Account is not authorized." );
736
+ }
737
+
738
+ if ( is_wp_error( $this->gdocs_access_token ) ) return $access_token;
739
+
740
+ $this->gdocs = new UpdraftPlus_GDocs( $this->gdocs_access_token );
741
+ $this->gdocs->set_option( 'chunk_size', 1 ); # 1Mb; change from default of 512Kb
742
+ $this->gdocs->set_option( 'request_timeout', 10 ); # Change from default of 10s
743
+ $this->gdocs->set_option( 'max_resume_attempts', 36 ); # Doesn't look like GDocs class actually uses this anyway
744
+ }
745
+ return true;
746
+ }
747
+
748
+ // Returns:
749
+ // true = already uploaded
750
+ // false = failure
751
+ // otherwise, the file ID
752
+ function googledrive_upload_file( $file, $title, $parent = '') {
753
+
754
+ // Make sure $this->gdocs is a UpdraftPlus_GDocs object, or give an error
755
+ if ( is_wp_error( $e = $this->need_gdocs() ) ) return false;
756
+
757
+ $hash = md5($file);
758
+ $transkey = 'upd_'.$hash.'_gloc';
759
+ // This is unset upon completion, so if it is set then we are resuming
760
+ $possible_location = get_transient($transkey);
761
+
762
+ if ( empty( $possible_location ) ) {
763
+ $this->log("$file: Attempting to upload file to Google Drive.");
764
+ $location = $this->gdocs->prepare_upload( $file, $title, $parent );
765
+ } else {
766
+ $this->log("$file: Attempting to resume upload.");
767
+ $location = $this->gdocs->resume_upload( $file, $possible_location );
768
+ }
769
+
770
+ if ( is_wp_error( $location ) ) {
771
+ $this->log("GoogleDrive upload: an error occurred");
772
+ foreach ($location->get_error_messages() as $msg) {
773
+ $this->error($msg);
774
+ $this->log("Error details: ".$msg);
775
+ }
776
+ return false;
777
+ }
778
+
779
+ if (!is_string($location) && true == $location) {
780
+ $this->log("$file: this file is already uploaded");
781
+ return true;
782
+ }
783
+
784
+ if ( is_string( $location ) ) {
785
+ $res = $location;
786
+ $this->log("Uploading file with title ".$title);
787
+ $d = 0;
788
+ do {
789
+ $this->log("Google Drive upload: chunk d: $d, loc: $res");
790
+ $res = $this->gdocs->upload_chunk();
791
+ if (is_string($res)) set_transient($transkey, $res, 3600*3);
792
+ $p = $this->gdocs->get_upload_percentage();
793
+ if ( $p - $d >= 1 ) {
794
+ $b = intval( $p - $d );
795
+ // echo '<span style="width:' . $b . '%"></span>';
796
+ $d += $b;
797
+ }
798
+ // $this->options['backup_list'][$id]['speed'] = $this->gdocs->get_upload_speed();
799
+ } while ( is_string( $res ) );
800
+ // echo '</div>';
801
+
802
+ if ( is_wp_error( $res ) || $res !== true) {
803
+ $this->log( "An error occurred during GoogleDrive upload (2)" );
804
+ $this->error( "An error occurred during GoogleDrive upload (2)" );
805
+ if (is_wp_error( $res )) {
806
+ foreach ($res->get_error_messages() as $msg) { $this->log($msg); }
807
+ }
808
+ return false;
809
+ }
810
+
811
+ $this->log("The file was successfully uploaded to Google Drive in ".number_format_i18n( $this->gdocs->time_taken(), 3)." seconds at an upload speed of ".size_format( $this->gdocs->get_upload_speed() )."/s.");
812
+
813
+ delete_transient($transkey);
814
+ // unset( $this->options['backup_list'][$id]['location'], $this->options['backup_list'][$id]['attempt'] );
815
+ }
816
+
817
+ return $this->gdocs->get_file_id();
818
+
819
+ // $this->update_quota();
820
+ // Google's "user info" service
821
+ // if ( empty( $this->options['user_info'] ) ) $this->set_user_info();
822
+
823
+ }
824
+
825
+ // This function just does the formalities, and off-loads the main work to googledrive_upload_file
826
+ function googledrive_backup($backup_array) {
827
+
828
+ require_once(dirname(__FILE__).'/includes/class-gdocs.php');
829
+
830
+ // Do we have an access token?
831
+ if ( !$access_token = $this->access_token( get_option('updraft_googledrive_token'), get_option('updraft_googledrive_clientid'), get_option('updraft_googledrive_secret') )) {
832
+ $this->log('ERROR: Have not yet obtained an access token from Google (has the user authorised?)');
833
+ return new WP_Error( "no_access_token", "Have not yet obtained an access token from Google (has the user authorised?");
834
+ }
835
+
836
+ $this->gdocs_access_token = $access_token;
837
+
838
+ foreach ($backup_array as $file) {
839
+ $file_path = trailingslashit(get_option('updraft_dir')).$file;
840
+ $file_name = basename($file_path);
841
+ $this->log("$file_name: Attempting to upload to Google Drive");
842
+ $timer_start = microtime(true);
843
+ if ( $id = $this->googledrive_upload_file( $file_path, $file_name, get_option('updraft_googledrive_remotepath')) ) {
844
+ $this->log('OK: Archive ' . $file_name . ' uploaded to Google Drive in ' . ( round(microtime( true ) - $timer_start,2) ) . ' seconds (id: '.$id.')' );
845
+ $this->uploaded_file($file, $id);
846
+ } else {
847
+ $this->error("$file_name: Failed to upload to Google Drive" );
848
+ $this->log("ERROR: $file_name: Failed to upload to Google Drive" );
849
+ }
850
+ }
851
+ $this->prune_retained_backups("googledrive",$access_token,get_option('updraft_googledrive_remotepath'));
852
+ }
853
+
854
+ function ftp_backup($backup_array) {
855
+ if( !class_exists('ftp_wrapper')) {
856
+ require_once(dirname(__FILE__).'/includes/ftp.class.php');
857
+ }
858
+ //handle SSL and errors at some point TODO
859
+ $ftp = new ftp_wrapper(get_option('updraft_server_address'),get_option('updraft_ftp_login'),get_option('updraft_ftp_pass'));
860
+ $ftp->passive = true;
861
+ $ftp->connect();
862
+ //$ftp->make_dir(); we may need to recursively create dirs? TODO
863
+
864
+ $ftp_remote_path = trailingslashit(get_option('updraft_ftp_remote_path'));
865
+ foreach($backup_array as $file) {
866
+ $fullpath = trailingslashit(get_option('updraft_dir')).$file;
867
+ if ($ftp->put($fullpath,$ftp_remote_path.$file,FTP_BINARY)) {
868
+ $this->log("ERROR: $file_name: Successfully uploaded via FTP");
869
+ $this->uploaded_file($file);
870
+ } else {
871
+ $this->error("$file_name: Failed to upload to FTP" );
872
+ $this->log("ERROR: $file_name: Failed to upload to FTP" );
873
+ }
874
+ }
875
+ $this->prune_retained_backups("ftp",$ftp,$ftp_remote_path);
876
+ }
877
+
878
+ function delete_local($file) {
879
+ if(get_option('updraft_delete_local')) {
880
+ $this->log("Deleting local file: $file");
881
+ //need error checking so we don't delete what isn't successfully uploaded?
882
+ $fullpath = trailingslashit(get_option('updraft_dir')).$file;
883
+ return unlink($fullpath);
884
+ }
885
+ return true;
886
+ }
887
+
888
+ function backup_dirs() {
889
+ if(!$this->backup_time) $this->backup_time_nonce();
890
+ $wp_themes_dir = WP_CONTENT_DIR.'/themes';
891
+ $wp_upload_dir = wp_upload_dir();
892
+ $wp_upload_dir = $wp_upload_dir['basedir'];
893
+ $wp_plugins_dir = WP_PLUGIN_DIR;
894
+
895
+ if(!class_exists('PclZip')) require_once(ABSPATH.'/wp-admin/includes/class-pclzip.php');
896
+
897
+ $updraft_dir = $this->backups_dir_location();
898
+ if(!is_writable($updraft_dir)) $this->error('Backup directory is not writable, or does not exist.','fatal');
899
+
900
+ //get the blog name and rip out all non-alphanumeric chars other than _
901
+ $blog_name = str_replace(' ','_',get_bloginfo());
902
+ $blog_name = preg_replace('/[^A-Za-z0-9_]/','', $blog_name);
903
+ if(!$blog_name) $blog_name = 'non_alpha_name';
904
+
905
+ $backup_file_base = $updraft_dir.'/backup_'.date('Y-m-d-Hi',$this->backup_time).'_'.$blog_name.'_'.$this->nonce;
906
+
907
+ $backup_array = array();
908
+
909
+ # Plugins
910
+ @set_time_limit(900);
911
+ if (get_option('updraft_include_plugins', true)) {
912
+ $this->log("Beginning backup of plugins");
913
+ $full_path = $backup_file_base.'-plugins.zip';
914
+ $plugins = new PclZip($full_path);
915
+ # The paths in the zip should then begin with 'plugins', having removed WP_CONTENT_DIR from the front
916
+ if (!$plugins->create($wp_plugins_dir,PCLZIP_OPT_REMOVE_PATH,WP_CONTENT_DIR)) {
917
+ $this->error('Could not create plugins zip. Error was '.$php_errmsg,'fatal');
918
+ $this->log('ERROR: PclZip failure: Could not create plugins zip');
919
+ } else {
920
+ $this->log("Created plugins zip - file size is ".filesize($full_path)." bytes");
921
+ }
922
+ $backup_array['plugins'] = basename($full_path);
923
+ } else {
924
+ $this->log("No backup of plugins: excluded by user's options");
925
+ }
926
+
927
+ # Themes
928
+ @set_time_limit(900);
929
+ if (get_option('updraft_include_themes', true)) {
930
+ $this->log("Beginning backup of themes");
931
+ $full_path = $backup_file_base.'-themes.zip';
932
+ $themes = new PclZip($full_path);
933
+ if (!$themes->create($wp_themes_dir,PCLZIP_OPT_REMOVE_PATH,WP_CONTENT_DIR)) {
934
+ $this->error('Could not create themes zip. Error was '.$php_errmsg,'fatal');
935
+ $this->log('ERROR: PclZip failure: Could not create themes zip');
936
+ } else {
937
+ $this->log("Created themes zip - file size is ".filesize($full_path)." bytes");
938
+ }
939
+ $backup_array['themes'] = basename($full_path);
940
+ } else {
941
+ $this->log("No backup of themes: excluded by user's options");
942
+ }
943
+
944
+ # Uploads
945
+ @set_time_limit(900);
946
+ if (get_option('updraft_include_uploads', true)) {
947
+ $this->log("Beginning backup of uploads");
948
+ $full_path = $backup_file_base.'-uploads.zip';
949
+ $uploads = new PclZip($full_path);
950
+ if (!$uploads->create($wp_upload_dir,PCLZIP_OPT_REMOVE_PATH,WP_CONTENT_DIR)) {
951
+ $this->error('Could not create uploads zip. Error was '.$php_errmsg,'fatal');
952
+ $this->log('ERROR: PclZip failure: Could not create uploads zip');
953
+ } else {
954
+ $this->log("Created uploads zip - file size is ".filesize($full_path)." bytes");
955
+ }
956
+ $backup_array['uploads'] = basename($full_path);
957
+ } else {
958
+ $this->log("No backup of uploads: excluded by user's options");
959
+ }
960
+
961
+ # Others
962
+ @set_time_limit(900);
963
+ if (get_option('updraft_include_others', true)) {
964
+ $this->log("Beginning backup of other directories found in the content directory");
965
+ $full_path=$backup_file_base.'-others.zip';
966
+ $others = new PclZip($full_path);
967
+ // http://www.phpconcept.net/pclzip/user-guide/53
968
+ /* First parameter to create is:
969
+ An array of filenames or dirnames,
970
+ or
971
+ A string containing the filename or a dirname,
972
+ or
973
+ A string containing a list of filename or dirname separated by a comma.
974
+ */
975
+ // First, see what we can find. We always want to exclude these:
976
+ $wp_themes_dir = WP_CONTENT_DIR.'/themes';
977
+ $wp_upload_dir = wp_upload_dir();
978
+ $wp_upload_dir = $wp_upload_dir['basedir'];
979
+ $wp_plugins_dir = WP_PLUGIN_DIR;
980
+ $updraft_dir = untrailingslashit(get_option('updraft_dir'));
981
+
982
+ # Initialise
983
+ $other_dirlist = array();
984
+
985
+ $others_skip = preg_split("/,/",get_option('updraft_include_others_exclude',UPDRAFT_DEFAULT_OTHERS_EXCLUDE));
986
+ # Make the values into the keys
987
+ $others_skip = array_flip($others_skip);
988
+
989
+ $this->log('Looking for candidates to back up in: '.WP_CONTENT_DIR);
990
+ if ($handle = opendir(WP_CONTENT_DIR)) {
991
+ while (false !== ($entry = readdir($handle))) {
992
+ $candidate = WP_CONTENT_DIR.'/'.$entry;
993
+ if ($entry == "." || $entry == "..") { ; }
994
+ elseif ($candidate == $updraft_dir) { $this->log("$entry: skipping: this is the updraft directory"); }
995
+ elseif ($candidate == $wp_themes_dir) { $this->log("$entry: skipping: this is the themes directory"); }
996
+ elseif ($candidate == $wp_upload_dir) { $this->log("$entry: skipping: this is the uploads directory"); }
997
+ elseif ($candidate == $wp_plugins_dir) { $this->log("$entry: skipping: this is the plugins directory"); }
998
+ elseif (isset($others_skip[$entry])) { $this->log("$entry: skipping: excluded by options"); }
999
+ else { $this->log("$entry: adding to list"); array_push($other_dirlist,$candidate); }
1000
+ }
1001
+ } else {
1002
+ $this->log('ERROR: Could not read the content directory: '.WP_CONTENT_DIR);
1003
+ }
1004
+
1005
+ if (count($other_dirlist)>0) {
1006
+ if (!$others->create($other_dirlist,PCLZIP_OPT_REMOVE_PATH,WP_CONTENT_DIR)) {
1007
+ $this->error('Could not create other zip. Error was '.$php_errmsg,'fatal');
1008
+ $this->log('ERROR: PclZip failure: Could not create other zip');
1009
+ } else {
1010
+ $this->log("Created other directories zip - file size is ".filesize($full_path)." bytes");
1011
+ }
1012
+ $backup_array['others'] = basename($full_path);
1013
+ } else {
1014
+ $this->log("No backup of other directories: there was nothing found to back up");
1015
+ }
1016
+ } else {
1017
+ $this->log("No backup of other directories: excluded by user's options");
1018
+ }
1019
+ return $backup_array;
1020
+ }
1021
+
1022
+ function save_backup_history($backup_array) {
1023
+ //TODO: this stores full paths right now. should probably concatenate with ABSPATH to make it easier to move sites
1024
+ if(is_array($backup_array)) {
1025
+ $backup_history = get_option('updraft_backup_history');
1026
+ $backup_history = (is_array($backup_history)) ? $backup_history : array();
1027
+ $backup_history[$this->backup_time] = $backup_array;
1028
+ update_option('updraft_backup_history',$backup_history);
1029
+ } else {
1030
+ $this->error('Could not save backup history because we have no backup array. Backup probably failed.');
1031
+ }
1032
+ }
1033
+
1034
+ function get_backup_history() {
1035
+ //$backup_history = get_option('updraft_backup_history');
1036
+ //by doing a raw DB query to get the most up-to-date data from this option we slightly narrow the window for the multiple-cron race condition
1037
+ global $wpdb;
1038
+ $backup_history = @unserialize($wpdb->get_var($wpdb->prepare("SELECT option_value from $wpdb->options WHERE option_name='updraft_backup_history'")));
1039
+ if(is_array($backup_history)) {
1040
+ krsort($backup_history); //reverse sort so earliest backup is last on the array. this way we can array_pop
1041
+ } else {
1042
+ $backup_history = array();
1043
+ }
1044
+ return $backup_history;
1045
+ }
1046
+
1047
+
1048
+ /*START OF WB-DB-BACKUP BLOCK*/
1049
+
1050
+ function backup_db() {
1051
+
1052
+ $total_tables = 0;
1053
+
1054
+ global $table_prefix, $wpdb;
1055
+ if(!$this->backup_time) {
1056
+ $this->backup_time_nonce();
1057
+ }
1058
+
1059
+ $all_tables = $wpdb->get_results("SHOW TABLES", ARRAY_N);
1060
+ $all_tables = array_map(create_function('$a', 'return $a[0];'), $all_tables);
1061
+
1062
+ $updraft_dir = $this->backups_dir_location();
1063
+ //get the blog name and rip out all non-alphanumeric chars other than _
1064
+ $blog_name = str_replace(' ','_',get_bloginfo());
1065
+ $blog_name = preg_replace('/[^A-Za-z0-9_]/','', $blog_name);
1066
+ if(!$blog_name) {
1067
+ $blog_name = 'non_alpha_name';
1068
+ }
1069
+
1070
+ $backup_file_base = $updraft_dir.'/backup_'.date('Y-m-d-Hi',$this->backup_time).'_'.$blog_name.'_'.$this->nonce;
1071
+ if (is_writable($updraft_dir)) {
1072
+ if (function_exists('gzopen')) {
1073
+ $this->dbhandle = @gzopen($backup_file_base.'-db.gz','w');
1074
+ } else {
1075
+ $this->dbhandle = @fopen($backup_file_base.'-db.gz', 'w');
1076
+ }
1077
+ if(!$this->dbhandle) {
1078
+ //$this->error(__('Could not open the backup file for writing!','wp-db-backup'));
1079
+ }
1080
+ } else {
1081
+ //$this->error(__('The backup directory is not writable!','wp-db-backup'));
1082
+ }
1083
+
1084
+ //Begin new backup of MySql
1085
+ $this->stow("# " . __('WordPress MySQL database backup','wp-db-backup') . "\n");
1086
+ $this->stow("#\n");
1087
+ $this->stow("# " . sprintf(__('Generated: %s','wp-db-backup'),date("l j. F Y H:i T")) . "\n");
1088
+ $this->stow("# " . sprintf(__('Hostname: %s','wp-db-backup'),DB_HOST) . "\n");
1089
+ $this->stow("# " . sprintf(__('Database: %s','wp-db-backup'),$this->backquote(DB_NAME)) . "\n");
1090
+ $this->stow("# --------------------------------------------------------\n");
1091
+
1092
+
1093
+ if (defined("DB_CHARSET")) {
1094
+ $this->stow("/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n");
1095
+ $this->stow("/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n");
1096
+ $this->stow("/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n");
1097
+ $this->stow("/*!40101 SET NAMES " . DB_CHARSET . " */;\n");
1098
+ }
1099
+ $this->stow("/*!40101 SET foreign_key_checks = 0 */;\n");
1100
+
1101
+ foreach ($all_tables as $table) {
1102
+ $total_tables++;
1103
+ // Increase script execution time-limit to 15 min for every table.
1104
+ if ( !ini_get('safe_mode') || strtolower(ini_get('safe_mode')) == "off") @set_time_limit(15*60);
1105
+ # === is needed, otherwise 'false' matches (i.e. prefix does not match)
1106
+ if ( strpos($table, $table_prefix) === 0 ) {
1107
+ // Create the SQL statements
1108
+ $this->stow("# --------------------------------------------------------\n");
1109
+ $this->stow("# " . sprintf(__('Table: %s','wp-db-backup'),$this->backquote($table)) . "\n");
1110
+ $this->stow("# --------------------------------------------------------\n");
1111
+ $this->backup_table($table);
1112
+ } else {
1113
+ $this->stow("# --------------------------------------------------------\n");
1114
+ $this->stow("# " . sprintf(__('Skipping non-WP table: %s','wp-db-backup'),$this->backquote($table)) . "\n");
1115
+ $this->stow("# --------------------------------------------------------\n");
1116
+ }
1117
+ }
1118
+
1119
+ if (defined("DB_CHARSET")) {
1120
+ $this->stow("/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n");
1121
+ $this->stow("/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n");
1122
+ $this->stow("/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n");
1123
+ }
1124
+
1125
+ $this->close($this->dbhandle);
1126
+
1127
+ if (count($this->errors)) {
1128
+ return false;
1129
+ } else {
1130
+ # Encrypt, if requested
1131
+ $encryption = get_option('updraft_encryptionphrase');
1132
+ if (strlen($encryption) > 0) {
1133
+ $this->log("Database: applying encryption");
1134
+ $encryption_error = 0;
1135
+ require_once(dirname(__FILE__).'/includes/Rijndael.php');
1136
+ $rijndael = new Crypt_Rijndael();
1137
+ $rijndael->setKey($encryption);
1138
+ $in_handle = @fopen($backup_file_base.'-db.gz','r');
1139
+ $buffer = "";
1140
+ while (!feof ($in_handle)) {
1141
+ $buffer .= fread($in_handle, 16384);
1142
+ }
1143
+ fclose ($in_handle);
1144
+ $out_handle = @fopen($backup_file_base.'-db.gz.crypt','w');
1145
+ if (!fwrite($out_handle, $rijndael->encrypt($buffer))) {$encryption_error = 1;}
1146
+ fclose ($out_handle);
1147
+ if (0 == $encryption_error) {
1148
+ # Delete unencrypted file
1149
+ @unlink($backup_file_base.'-db.gz');
1150
+ return basename($backup_file_base.'-db.gz.crypt');
1151
+ } else {
1152
+ $this->error("Encryption error occurred when encrypting database. Aborted.");
1153
+ }
1154
+ } else {
1155
+ return basename($backup_file_base.'-db.gz');
1156
+ }
1157
+ }
1158
+ $this->log("Total database tables backed up: $total_tables");
1159
+
1160
+ } //wp_db_backup
1161
+
1162
+ /**
1163
+ * Taken partially from phpMyAdmin and partially from
1164
+ * Alain Wolf, Zurich - Switzerland
1165
+ * Website: http://restkultur.ch/personal/wolf/scripts/db_backup/
1166
+ * Modified by Scott Merrill (http://www.skippy.net/)
1167
+ * to use the WordPress $wpdb object
1168
+ * @param string $table
1169
+ * @param string $segment
1170
+ * @return void
1171
+ */
1172
+ function backup_table($table, $segment = 'none') {
1173
+ global $wpdb;
1174
+
1175
+ $total_rows = 0;
1176
+
1177
+ $table_structure = $wpdb->get_results("DESCRIBE $table");
1178
+ if (! $table_structure) {
1179
+ //$this->error(__('Error getting table details','wp-db-backup') . ": $table");
1180
+ return false;
1181
+ }
1182
+
1183
+ if(($segment == 'none') || ($segment == 0)) {
1184
+ // Add SQL statement to drop existing table
1185
+ $this->stow("\n\n");
1186
+ $this->stow("#\n");
1187
+ $this->stow("# " . sprintf(__('Delete any existing table %s','wp-db-backup'),$this->backquote($table)) . "\n");
1188
+ $this->stow("#\n");
1189
+ $this->stow("\n");
1190
+ $this->stow("DROP TABLE IF EXISTS " . $this->backquote($table) . ";\n");
1191
+
1192
+ // Table structure
1193
+ // Comment in SQL-file
1194
+ $this->stow("\n\n");
1195
+ $this->stow("#\n");
1196
+ $this->stow("# " . sprintf(__('Table structure of table %s','wp-db-backup'),$this->backquote($table)) . "\n");
1197
+ $this->stow("#\n");
1198
+ $this->stow("\n");
1199
+
1200
+ $create_table = $wpdb->get_results("SHOW CREATE TABLE $table", ARRAY_N);
1201
+ if (false === $create_table) {
1202
+ $err_msg = sprintf(__('Error with SHOW CREATE TABLE for %s.','wp-db-backup'), $table);
1203
+ //$this->error($err_msg);
1204
+ $this->stow("#\n# $err_msg\n#\n");
1205
+ }
1206
+ $this->stow($create_table[0][1] . ' ;');
1207
+
1208
+ if (false === $table_structure) {
1209
+ $err_msg = sprintf(__('Error getting table structure of %s','wp-db-backup'), $table);
1210
+ //$this->error($err_msg);
1211
+ $this->stow("#\n# $err_msg\n#\n");
1212
+ }
1213
+
1214
+ // Comment in SQL-file
1215
+ $this->stow("\n\n");
1216
+ $this->stow("#\n");
1217
+ $this->stow('# ' . sprintf(__('Data contents of table %s','wp-db-backup'),$this->backquote($table)) . "\n");
1218
+ $this->stow("#\n");
1219
+ }
1220
+
1221
+ if(($segment == 'none') || ($segment >= 0)) {
1222
+ $defs = array();
1223
+ $ints = array();
1224
+ foreach ($table_structure as $struct) {
1225
+ if ( (0 === strpos($struct->Type, 'tinyint')) ||
1226
+ (0 === strpos(strtolower($struct->Type), 'smallint')) ||
1227
+ (0 === strpos(strtolower($struct->Type), 'mediumint')) ||
1228
+ (0 === strpos(strtolower($struct->Type), 'int')) ||
1229
+ (0 === strpos(strtolower($struct->Type), 'bigint')) ) {
1230
+ $defs[strtolower($struct->Field)] = ( null === $struct->Default ) ? 'NULL' : $struct->Default;
1231
+ $ints[strtolower($struct->Field)] = "1";
1232
+ }
1233
+ }
1234
+
1235
+
1236
+ // Batch by $row_inc
1237
+ if ( ! defined('ROWS_PER_SEGMENT') ) {
1238
+ define('ROWS_PER_SEGMENT', 100);
1239
+ }
1240
+
1241
+ if($segment == 'none') {
1242
+ $row_start = 0;
1243
+ $row_inc = ROWS_PER_SEGMENT;
1244
+ } else {
1245
+ $row_start = $segment * ROWS_PER_SEGMENT;
1246
+ $row_inc = ROWS_PER_SEGMENT;
1247
+ }
1248
+ do {
1249
+ // don't include extra stuff, if so requested
1250
+ $excs = array('revisions' => 0, 'spam' => 1); //TODO, FIX THIS
1251
+ $where = '';
1252
+ if ( is_array($excs['spam'] ) && in_array($table, $excs['spam']) ) {
1253
+ $where = ' WHERE comment_approved != "spam"';
1254
+ } elseif ( is_array($excs['revisions'] ) && in_array($table, $excs['revisions']) ) {
1255
+ $where = ' WHERE post_type != "revision"';
1256
+ }
1257
+
1258
+ if ( !ini_get('safe_mode') || strtolower(ini_get('safe_mode')) == "off") @set_time_limit(15*60);
1259
+ $table_data = $wpdb->get_results("SELECT * FROM $table $where LIMIT {$row_start}, {$row_inc}", ARRAY_A);
1260
+ $entries = 'INSERT INTO ' . $this->backquote($table) . ' VALUES (';
1261
+ // \x08\\x09, not required
1262
+ $search = array("\x00", "\x0a", "\x0d", "\x1a");
1263
+ $replace = array('\0', '\n', '\r', '\Z');
1264
+ if($table_data) {
1265
+ foreach ($table_data as $row) {
1266
+ $total_rows++;
1267
+ $values = array();
1268
+ foreach ($row as $key => $value) {
1269
+ if ($ints[strtolower($key)]) {
1270
+ // make sure there are no blank spots in the insert syntax,
1271
+ // yet try to avoid quotation marks around integers
1272
+ $value = ( null === $value || '' === $value) ? $defs[strtolower($key)] : $value;
1273
+ $values[] = ( '' === $value ) ? "''" : $value;
1274
+ } else {
1275
+ $values[] = "'" . str_replace($search, $replace, $this->sql_addslashes($value)) . "'";
1276
+ }
1277
+ }
1278
+ $this->stow(" \n" . $entries . implode(', ', $values) . ');');
1279
+ }
1280
+ $row_start += $row_inc;
1281
+ }
1282
+ } while((count($table_data) > 0) and ($segment=='none'));
1283
+ }
1284
+
1285
+ if(($segment == 'none') || ($segment < 0)) {
1286
+ // Create footer/closing comment in SQL-file
1287
+ $this->stow("\n");
1288
+ $this->stow("#\n");
1289
+ $this->stow("# " . sprintf(__('End of data contents of table %s','wp-db-backup'),$this->backquote($table)) . "\n");
1290
+ $this->stow("# --------------------------------------------------------\n");
1291
+ $this->stow("\n");
1292
+ }
1293
+ $this->log("Table $table: Total rows added: $total_rows");
1294
+
1295
+ } // end backup_table()
1296
+
1297
+
1298
+ function stow($query_line) {
1299
+ if (function_exists('gzopen')) {
1300
+ if(! @gzwrite($this->dbhandle, $query_line)) {
1301
+ //$this->error(__('There was an error writing a line to the backup script:','wp-db-backup') . ' ' . $query_line . ' ' . $php_errormsg);
1302
+ }
1303
+ } else {
1304
+ if(false === @fwrite($this->dbhandle, $query_line)) {
1305
+ //$this->error(__('There was an error writing a line to the backup script:','wp-db-backup') . ' ' . $query_line . ' ' . $php_errormsg);
1306
+ }
1307
+ }
1308
+ }
1309
+
1310
+
1311
+ function close($handle) {
1312
+ if (function_exists('gzopen')) {
1313
+ gzclose($handle);
1314
+ } else {
1315
+ fclose($handle);
1316
+ }
1317
+ }
1318
+
1319
+ function error($error,$severity='') {
1320
+ $this->errors[] = $error;
1321
+ return true;
1322
+ }
1323
+
1324
+ /**
1325
+ * Add backquotes to tables and db-names in
1326
+ * SQL queries. Taken from phpMyAdmin.
1327
+ */
1328
+ function backquote($a_name) {
1329
+ if (!empty($a_name) && $a_name != '*') {
1330
+ if (is_array($a_name)) {
1331
+ $result = array();
1332
+ reset($a_name);
1333
+ while(list($key, $val) = each($a_name))
1334
+ $result[$key] = '`' . $val . '`';
1335
+ return $result;
1336
+ } else {
1337
+ return '`' . $a_name . '`';
1338
+ }
1339
+ } else {
1340
+ return $a_name;
1341
+ }
1342
+ }
1343
+
1344
+ /**
1345
+ * Better addslashes for SQL queries.
1346
+ * Taken from phpMyAdmin.
1347
+ */
1348
+ function sql_addslashes($a_string = '', $is_like = false) {
1349
+ if ($is_like) $a_string = str_replace('\\', '\\\\\\\\', $a_string);
1350
+ else $a_string = str_replace('\\', '\\\\', $a_string);
1351
+ return str_replace('\'', '\\\'', $a_string);
1352
+ }
1353
+
1354
+ /*END OF WP-DB-BACKUP BLOCK */
1355
+
1356
+ /*
1357
+ this function is both the backup scheduler and ostensibly a filter callback for saving the option.
1358
+ it is called in the register_setting for the updraft_interval, which means when the admin settings
1359
+ are saved it is called. it returns the actual result from wp_filter_nohtml_kses (a sanitization filter)
1360
+ so the option can be properly saved.
1361
+ */
1362
+ function schedule_backup($interval) {
1363
+ //clear schedule and add new so we don't stack up scheduled backups
1364
+ wp_clear_scheduled_hook('updraft_backup');
1365
+ switch($interval) {
1366
+ case 'daily':
1367
+ case 'weekly':
1368
+ case 'monthly':
1369
+ wp_schedule_event(time()+30, $interval, 'updraft_backup');
1370
+ break;
1371
+ }
1372
+ return wp_filter_nohtml_kses($interval);
1373
+ }
1374
+
1375
+ function schedule_backup_database($interval) {
1376
+ //clear schedule and add new so we don't stack up scheduled backups
1377
+ wp_clear_scheduled_hook('updraft_backup_database');
1378
+ switch($interval) {
1379
+ case 'daily':
1380
+ case 'weekly':
1381
+ case 'monthly':
1382
+ wp_schedule_event(time()+30, $interval, 'updraft_backup_database');
1383
+ break;
1384
+ }
1385
+ return wp_filter_nohtml_kses($interval);
1386
+ }
1387
+
1388
+ //wp-cron only has hourly, daily and twicedaily, so we need to add weekly and monthly.
1389
+ function modify_cron_schedules($schedules) {
1390
+ $schedules['weekly'] = array(
1391
+ 'interval' => 604800,
1392
+ 'display' => 'Once Weekly'
1393
+ );
1394
+ $schedules['monthly'] = array(
1395
+ 'interval' => 2592000,
1396
+ 'display' => 'Once Monthly'
1397
+ );
1398
+ return $schedules;
1399
+ }
1400
+
1401
+ function backups_dir_location() {
1402
+ $updraft_dir = untrailingslashit(get_option('updraft_dir'));
1403
+ $default_backup_dir = WP_CONTENT_DIR.'/updraft';
1404
+ //if the option isn't set, default it to /backups inside the upload dir
1405
+ $updraft_dir = ($updraft_dir)?$updraft_dir:$default_backup_dir;
1406
+ //check for the existence of the dir and an enumeration preventer.
1407
+ if(!is_dir($updraft_dir) || !is_file($updraft_dir.'/index.html') || !is_file($updraft_dir.'/.htaccess')) {
1408
+ @mkdir($updraft_dir,0777,true); //recursively create the dir with 0777 permissions. 0777 is default for php creation. not ideal, but I'll get back to this
1409
+ @file_put_contents($updraft_dir.'/index.html','Nothing to see here.');
1410
+ @file_put_contents($updraft_dir.'/.htaccess','deny from all');
1411
+ }
1412
+ return $updraft_dir;
1413
+ }
1414
+
1415
+ function updraft_download_backup() {
1416
+ $type = $_POST['type'];
1417
+ $timestamp = (int)$_POST['timestamp'];
1418
+ $backup_history = $this->get_backup_history();
1419
+ $file = $backup_history[$timestamp][$type];
1420
+ $fullpath = trailingslashit(get_option('updraft_dir')).$file;
1421
+ if(!is_readable($fullpath)) {
1422
+ //if the file doesn't exist and they're using one of the cloud options, fetch it down from the cloud.
1423
+ $this->download_backup($file);
1424
+ }
1425
+ if(@is_readable($fullpath) && is_file($fullpath)) {
1426
+ $len = filesize($fullpath);
1427
+
1428
+ $filearr = explode('.',$file);
1429
+ // //we've only got zip and gz...for now
1430
+ $file_ext = array_pop($filearr);
1431
+ if($file_ext == 'zip') {
1432
+ header('Content-type: application/zip');
1433
+ } else {
1434
+ // This catches both when what was popped was 'crypt' (*-db.gz.crypt) and when it was 'gz' (unencrypted)
1435
+ header('Content-type: application/x-gzip');
1436
+ }
1437
+ header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
1438
+ header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
1439
+ header("Content-Length: $len;");
1440
+ if ($file_ext == 'crypt') {
1441
+ header("Content-Disposition: attachment; filename=\"".substr($file,0,-6)."\";");
1442
+ } else {
1443
+ header("Content-Disposition: attachment; filename=\"$file\";");
1444
+ }
1445
+ ob_end_flush();
1446
+ if ($file_ext == 'crypt') {
1447
+ $encryption = get_option('updraft_encryptionphrase');
1448
+ if ($encryption == "") {
1449
+ $this->error('Decryption of database failed: the database file is encrypted, but you have no encryption key entered.');
1450
+ } else {
1451
+ require_once(dirname(__FILE__).'/includes/Rijndael.php');
1452
+ $rijndael = new Crypt_Rijndael();
1453
+ $rijndael->setKey($encryption);
1454
+ $in_handle = fopen($fullpath,'r');
1455
+ $ciphertext = "";
1456
+ while (!feof ($in_handle)) {
1457
+ $ciphertext .= fread($in_handle, 16384);
1458
+ }
1459
+ fclose ($in_handle);
1460
+ print $rijndael->decrypt($ciphertext);
1461
+ }
1462
+ } else {
1463
+ readfile($fullpath);
1464
+ }
1465
+ $this->delete_local($file);
1466
+ exit; //we exit immediately because otherwise admin-ajax appends an additional zero to the end for some reason I don't understand. seriously, why die('0')?
1467
+ } else {
1468
+ echo 'Download failed. File '.$fullpath.' did not exist or was unreadable. If you delete local backups then S3 or Google Drive or FTP retrieval may have failed. (Note that Google Drive downloading is not yet supported - you need to download manually if you use Google Drive).';
1469
+ }
1470
+ }
1471
+
1472
+ function download_backup($file) {
1473
+ switch(get_option('updraft_service')) {
1474
+ case 'googledrive':
1475
+ $this->download_googledrive_backup($file);
1476
+ break;
1477
+ case 's3':
1478
+ $this->download_s3_backup($file);
1479
+ break;
1480
+ case 'ftp':
1481
+ $this->download_ftp_backup($file);
1482
+ break;
1483
+ default:
1484
+ $this->error('Automatic backup restoration is only available via S3, FTP, and local. Email and downloaded backup restoration must be performed manually.');
1485
+ }
1486
+ }
1487
+
1488
+ function download_googledrive_backup($file) {
1489
+
1490
+ require_once(dirname(__FILE__).'/includes/class-gdocs.php');
1491
+
1492
+ // Do we have an access token?
1493
+ if ( !$access_token = $this->access_token( get_option('updraft_googledrive_token'), get_option('updraft_googledrive_clientid'), get_option('updraft_googledrive_secret') )) {
1494
+ $this->error('ERROR: Have not yet obtained an access token from Google (has the user authorised?)');
1495
+ return false;
1496
+ }
1497
+
1498
+ $this->gdocs_access_token = $access_token;
1499
+
1500
+ // Make sure $this->gdocs is a UpdraftPlus_GDocs object, or give an error
1501
+ if ( is_wp_error( $e = $this->need_gdocs() ) ) return false;
1502
+
1503
+ $ids = get_option('updraft_file_ids', array());
1504
+ if (!isset($ids[$file])) {
1505
+ $this->error("Google Drive error: $file: could not download: could not find a record of the Google Drive file ID for this file");
1506
+ return;
1507
+ } else {
1508
+ $content_link = $this->gdocs->get_content_link( $ids[$file], $file );
1509
+ if (is_wp_error($content_link)) {
1510
+ $this->error("Could not find $file in order to download it (id: ".$ids[$file].")");
1511
+ foreach ($content_link->get_error_messages() as $msg) {
1512
+ $this->error($msg);
1513
+ }
1514
+ return false;
1515
+ }
1516
+ // Actually download the thing
1517
+ $download_to = trailingslashit(get_option('updraft_dir')).$file;
1518
+ $this->gdocs->download_data($content_link, $download_to);
1519
+
1520
+ if (filesize($download_to) >0) {
1521
+ return true;
1522
+ } else {
1523
+ $this->error("Google Drive error: zero-size file was downloaded");
1524
+ return false;
1525
+ }
1526
+
1527
+ }
1528
+
1529
+ return;
1530
+
1531
+ }
1532
+
1533
+ function download_s3_backup($file) {
1534
+ if(!class_exists('S3')) {
1535
+ require_once(dirname(__FILE__).'/includes/S3.php');
1536
+ }
1537
+ $s3 = new S3(get_option('updraft_s3_login'), get_option('updraft_s3_pass'));
1538
+ $bucket_name = untrailingslashit(get_option('updraft_s3_remote_path'));
1539
+ $bucket_path = "";
1540
+ if (preg_match("#^([^/]+)/(.*)$#",$bucket_name,$bmatches)) {
1541
+ $bucket_name = $bmatches[1];
1542
+ $bucket_path = $bmatches[2]."/";
1543
+ }
1544
+ if (@$s3->putBucket($bucket_name, S3::ACL_PRIVATE)) {
1545
+ $fullpath = trailingslashit(get_option('updraft_dir')).$file;
1546
+ if (!$s3->getObject($bucket_name, $bucket_path.$file, $fullpath)) {
1547
+ $this->error("S3 Error: Failed to download $fullpath. Error was ".$php_errormsg);
1548
+ }
1549
+ } else {
1550
+ $this->error("S3 Error: Failed to create bucket $bucket_name. Error was ".$php_errormsg);
1551
+ }
1552
+ }
1553
+
1554
+ function download_ftp_backup($file) {
1555
+ if( !class_exists('ftp_wrapper')) require_once(dirname(__FILE__).'/includes/ftp.class.php');
1556
+
1557
+ //handle SSL and errors at some point TODO
1558
+ $ftp = new ftp_wrapper(get_option('updraft_server_address'),get_option('updraft_ftp_login'),get_option('updraft_ftp_pass'));
1559
+ $ftp->passive = true;
1560
+ $ftp->connect();
1561
+ //$ftp->make_dir(); we may need to recursively create dirs? TODO
1562
+
1563
+ $ftp_remote_path = trailingslashit(get_option('updraft_ftp_remote_path'));
1564
+ $fullpath = trailingslashit(get_option('updraft_dir')).$file;
1565
+ $ftp->get($fullpath,$ftp_remote_path.$file,FTP_BINARY);
1566
+ }
1567
+
1568
+ function restore_backup($timestamp) {
1569
+ global $wp_filesystem;
1570
+ $backup_history = get_option('updraft_backup_history');
1571
+ if(!is_array($backup_history[$timestamp])) {
1572
+ echo '<p>This backup does not exist in the backup history - restoration aborted. Timestamp: '.$timestamp.'</p><br/>';
1573
+ return false;
1574
+ }
1575
+
1576
+ $credentials = request_filesystem_credentials("options-general.php?page=updraftplus&action=updraft_restore&backup_timestamp=$timestamp");
1577
+ WP_Filesystem($credentials);
1578
+ if ( $wp_filesystem->errors->get_error_code() ) {
1579
+ foreach ( $wp_filesystem->errors->get_error_messages() as $message )
1580
+ show_message($message);
1581
+ exit;
1582
+ }
1583
+
1584
+ //if we make it this far then WP_Filesystem has been instantiated and is functional (tested with ftpext, what about suPHP and other situations where direct may work?)
1585
+ echo '<span style="font-weight:bold">Restoration Progress </span><div id="updraft-restore-progress">';
1586
+
1587
+ $updraft_dir = trailingslashit(get_option('updraft_dir'));
1588
+ foreach($backup_history[$timestamp] as $type=>$file) {
1589
+ $fullpath = $updraft_dir.$file;
1590
+ if(!is_readable($fullpath) && $type != 'db') {
1591
+ $this->download_backup($file);
1592
+ }
1593
+ # Types: uploads, themes, plugins, others, db
1594
+ if(is_readable($fullpath) && $type != 'db') {
1595
+ if(!class_exists('WP_Upgrader')) {
1596
+ require_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
1597
+ }
1598
+ require_once('includes/updraft-restorer.php');
1599
+ $restorer = new Updraft_Restorer();
1600
+ $val = $restorer->restore_backup($fullpath,$type);
1601
+ if(is_wp_error($val)) {
1602
+ print_r($val);
1603
+ echo '</div>'; //close the updraft_restore_progress div even if we error
1604
+ return false;
1605
+ }
1606
+ }
1607
+ }
1608
+ echo '</div>'; //close the updraft_restore_progress div
1609
+ # The 'off' check is for badly configured setups - http://wordpress.org/support/topic/plugin-wp-super-cache-warning-php-safe-mode-enabled-but-safe-mode-is-off
1610
+ if(ini_get('safe_mode') && strtolower(ini_get('safe_mode')) != "off") {
1611
+ echo "<p>DB could not be restored because safe_mode is active on your server. You will need to manually restore the file via phpMyAdmin or another method.</p><br/>";
1612
+ return false;
1613
+ }
1614
+ return true;
1615
+ }
1616
+
1617
+ //deletes the -old directories that are created when a backup is restored.
1618
+ function delete_old_dirs() {
1619
+ global $wp_filesystem;
1620
+ $credentials = request_filesystem_credentials("options-general.php?page=updraftplus&action=updraft_delete_old_dirs");
1621
+ WP_Filesystem($credentials);
1622
+ if ( $wp_filesystem->errors->get_error_code() ) {
1623
+ foreach ( $wp_filesystem->errors->get_error_messages() as $message )
1624
+ show_message($message);
1625
+ exit;
1626
+ }
1627
+
1628
+ $to_delete = array('themes-old','plugins-old','uploads-old','others-old');
1629
+
1630
+ foreach($to_delete as $name) {
1631
+ //recursively delete
1632
+ if(!$wp_filesystem->delete(WP_CONTENT_DIR.'/'.$name, true)) {
1633
+ return false;
1634
+ }
1635
+ }
1636
+ return true;
1637
+ }
1638
+
1639
+ //scans the content dir to see if any -old dirs are present
1640
+ function scan_old_dirs() {
1641
+ $dirArr = scandir(WP_CONTENT_DIR);
1642
+ foreach($dirArr as $dir) {
1643
+ if(strpos($dir,'-old') !== false) {
1644
+ return true;
1645
+ }
1646
+ }
1647
+ return false;
1648
+ }
1649
+
1650
+
1651
+ function retain_range($input) {
1652
+ $input = (int)$input;
1653
+ if($input > 0 && $input < 3650) {
1654
+ return $input;
1655
+ } else {
1656
+ return 1;
1657
+ }
1658
+ }
1659
+
1660
+ function create_backup_dir() {
1661
+ global $wp_filesystem;
1662
+ $credentials = request_filesystem_credentials("options-general.php?page=updraftplus&action=updraft_create_backup_dir");
1663
+ WP_Filesystem($credentials);
1664
+ if ( $wp_filesystem->errors->get_error_code() ) {
1665
+ foreach ( $wp_filesystem->errors->get_error_messages() as $message )
1666
+ show_message($message);
1667
+ exit;
1668
+ }
1669
+
1670
+ $updraft_dir = untrailingslashit(get_option('updraft_dir'));
1671
+ $default_backup_dir = WP_CONTENT_DIR.'/updraft';
1672
+ $updraft_dir = ($updraft_dir)?$updraft_dir:$default_backup_dir;
1673
+
1674
+ //chmod the backup dir to 0777. ideally we'd rather chgrp it but i'm not sure if it's possible to detect the group apache is running under (or what if it's not apache...)
1675
+ if(!$wp_filesystem->mkdir($updraft_dir, 0777)) {
1676
+ return false;
1677
+ }
1678
+ return true;
1679
+ }
1680
+
1681
+
1682
+ function memory_check_current() {
1683
+ # Returns in megabytes
1684
+ $memory_limit = ini_get('memory_limit');
1685
+ $memory_unit = $memory_limit[strlen($memory_limit)-1];
1686
+ $memory_limit = substr($memory_limit,0,strlen($memory_limit)-1);
1687
+ switch($memory_unit) {
1688
+ case 'K':
1689
+ $memory_limit = $memory_limit/1024;
1690
+ break;
1691
+ case 'G':
1692
+ $memory_limit = $memory_limit*1024;
1693
+ break;
1694
+ case 'M':
1695
+ //assumed size, no change needed
1696
+ break;
1697
+ }
1698
+ return $memory_limit;
1699
+ }
1700
+
1701
+ function memory_check($memory) {
1702
+ $memory_limit = $this->memory_check_current();
1703
+ return ($memory_limit >= $memory)?true:false;
1704
+ }
1705
+
1706
+ function execution_time_check($time) {
1707
+ return (ini_get('max_execution_time') >= $time)?true:false;
1708
+ }
1709
+
1710
+ function admin_init() {
1711
+ if(get_option('updraft_debug_mode')) {
1712
+ ini_set('display_errors',1);
1713
+ error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
1714
+ ini_set('track_errors',1);
1715
+ }
1716
+ wp_enqueue_script('jquery');
1717
+ register_setting( 'updraft-options-group', 'updraft_interval', array($this,'schedule_backup') );
1718
+ register_setting( 'updraft-options-group', 'updraft_interval_database', array($this,'schedule_backup_database') );
1719
+ register_setting( 'updraft-options-group', 'updraft_retain', array($this,'retain_range') );
1720
+ register_setting( 'updraft-options-group', 'updraft_encryptionphrase', 'wp_filter_nohtml_kses' );
1721
+ register_setting( 'updraft-options-group', 'updraft_service', 'wp_filter_nohtml_kses' );
1722
+ register_setting( 'updraft-options-group', 'updraft_s3_login', 'wp_filter_nohtml_kses' );
1723
+ register_setting( 'updraft-options-group', 'updraft_s3_pass', 'wp_filter_nohtml_kses' );
1724
+ register_setting( 'updraft-options-group', 'updraft_s3_remote_path', 'wp_filter_nohtml_kses' );
1725
+ register_setting( 'updraft-options-group', 'updraft_googledrive_clientid', 'wp_filter_nohtml_kses' );
1726
+ register_setting( 'updraft-options-group', 'updraft_googledrive_secret', 'wp_filter_nohtml_kses' );
1727
+ register_setting( 'updraft-options-group', 'updraft_googledrive_remotepath', 'wp_filter_nohtml_kses' );
1728
+ register_setting( 'updraft-options-group', 'updraft_ftp_login', 'wp_filter_nohtml_kses' );
1729
+ register_setting( 'updraft-options-group', 'updraft_ftp_pass', 'wp_filter_nohtml_kses' );
1730
+ register_setting( 'updraft-options-group', 'updraft_dir', 'wp_filter_nohtml_kses' );
1731
+ register_setting( 'updraft-options-group', 'updraft_email', 'wp_filter_nohtml_kses' );
1732
+ register_setting( 'updraft-options-group', 'updraft_ftp_remote_path', 'wp_filter_nohtml_kses' );
1733
+ register_setting( 'updraft-options-group', 'updraft_server_address', 'wp_filter_nohtml_kses' );
1734
+ register_setting( 'updraft-options-group', 'updraft_delete_local', 'absint' );
1735
+ register_setting( 'updraft-options-group', 'updraft_debug_mode', 'absint' );
1736
+ register_setting( 'updraft-options-group', 'updraft_include_plugins', 'absint' );
1737
+ register_setting( 'updraft-options-group', 'updraft_include_themes', 'absint' );
1738
+ register_setting( 'updraft-options-group', 'updraft_include_uploads', 'absint' );
1739
+ register_setting( 'updraft-options-group', 'updraft_include_others', 'absint' );
1740
+ register_setting( 'updraft-options-group', 'updraft_include_others_exclude', 'wp_filter_nohtml_kses' );
1741
+
1742
+ /* I see no need for this check; people can only download backups/logs if they can guess a nonce formed from a random number and if .htaccess files have no effect. The database will be encrypted. Very unlikely.
1743
+ if (current_user_can('manage_options')) {
1744
+ $updraft_dir = $this->backups_dir_location();
1745
+ if(strpos($updraft_dir,WP_CONTENT_DIR) !== false) {
1746
+ $relative_dir = str_replace(WP_CONTENT_DIR,'',$updraft_dir);
1747
+ $possible_updraft_url = WP_CONTENT_URL.$relative_dir;
1748
+ $resp = wp_remote_request($possible_updraft_url, array('timeout' => 15));
1749
+ if ( is_wp_error($resp) ) {
1750
+ add_action('admin_notices', array($this,'show_admin_warning_accessible_unknownresult') );
1751
+ } else {
1752
+ if(strpos($resp['response']['code'],'403') === false) {
1753
+ add_action('admin_notices', array($this,'show_admin_warning_accessible') );
1754
+ }
1755
+ }
1756
+ }
1757
+ }
1758
+ */
1759
+ if (current_user_can('manage_options') && get_option('updraft_service') == "googledrive" && get_option('updraft_googledrive_clientid') != "" && get_option('updraft_googledrive_token','xyz') == 'xyz') {
1760
+ add_action('admin_notices', array($this,'show_admin_warning_googledrive') );
1761
+ }
1762
+ }
1763
+
1764
+ function add_admin_pages() {
1765
+ add_submenu_page('options-general.php', "UpdraftPlus", "UpdraftPlus", "manage_options", "updraftplus",
1766
+ array($this,"settings_output"));
1767
+ }
1768
+
1769
+ function wordshell_random_advert($urls) {
1770
+ $url_start = ($urls) ? '<a href="http://wordshell.net">' : "";
1771
+ $url_end = ($urls) ? '</a>' : " (www.wordshell.net)";
1772
+ if (rand(0,1) == 0) {
1773
+ return "Like automating WordPress operations? Use the CLI? ${url_start}You will love WordShell${url_end} - saves time and money fast.";
1774
+ } else {
1775
+ return "${url_start}Check out WordShell${url_end} - manage WordPress from the command line - huge time-saver";
1776
+ }
1777
+ }
1778
+
1779
+ function settings_output() {
1780
+
1781
+ /*
1782
+ we use request here because the initial restore is triggered by a POSTed form. we then may need to obtain credentials
1783
+ for the WP_Filesystem. to do this WP outputs a form that we can't insert variables into (apparently). So the values are
1784
+ passed back in as GET parameters. REQUEST covers both GET and POST so this weird logic works.
1785
+ */
1786
+ if(isset($_REQUEST['action']) && $_REQUEST['action'] == 'updraft_restore' && isset($_REQUEST['backup_timestamp'])) {
1787
+ $backup_success = $this->restore_backup($_REQUEST['backup_timestamp']);
1788
+ if(empty($this->errors) && $backup_success == true) {
1789
+ echo '<p>Restore successful!</p><br/>';
1790
+ echo '<b>Actions:</b> <a href="options-general.php?page=updraftplus&updraft_restore_success=true">Return to Updraft Configuration</a>.';
1791
+ return;
1792
+ } else {
1793
+ echo '<p>Restore failed...</p><ul>';
1794
+ foreach ($this->errors as $err) {
1795
+ echo "<li>";
1796
+ if (is_string($err)) { echo htmlspecialchars($err); } else {
1797
+ print_r($err);
1798
+ }
1799
+ echo "</li>";
1800
+ }
1801
+ echo '</ul><b>Actions:</b> <a href="options-general.php?page=updraftplus">Return to Updraft Configuration</a>.';
1802
+ return;
1803
+ }
1804
+ //uncomment the below once i figure out how i want the flow of a restoration to work.
1805
+ //echo '<b>Actions:</b> <a href="options-general.php?page=updraftplus">Return to Updraft Configuration</a>.';
1806
+ }
1807
+ $deleted_old_dirs = false;
1808
+ if(isset($_REQUEST['action']) && $_REQUEST['action'] == 'updraft_delete_old_dirs') {
1809
+ if($this->delete_old_dirs()) {
1810
+ $deleted_old_dirs = true;
1811
+ } else {
1812
+ echo '<p>Old directory removal failed for some reason. You may want to do this manually.</p><br/>';
1813
+ }
1814
+ echo '<p>Old directories successfully removed.</p><br/>';
1815
+ echo '<b>Actions:</b> <a href="options-general.php?page=updraftplus">Return to Updraft Configuration</a>.';
1816
+ return;
1817
+ }
1818
+
1819
+ if(isset($_GET['error'])) {
1820
+ echo "<p><strong>ERROR:</strong> ".htmlspecialchars($_GET['error'])."</p>";
1821
+ }
1822
+ if(isset($_GET['message'])) {
1823
+ echo "<p><strong>Note:</strong> ".htmlspecialchars($_GET['message'])."</p>";
1824
+ }
1825
+
1826
+ if(isset($_GET['action']) && $_GET['action'] == 'updraft_create_backup_dir') {
1827
+ if(!$this->create_backup_dir()) {
1828
+ echo '<p>Backup directory could not be created...</p><br/>';
1829
+ }
1830
+ echo '<p>Backup directory successfully created.</p><br/>';
1831
+ echo '<b>Actions:</b> <a href="options-general.php?page=updraftplus">Return to Updraft Configuration</a>.';
1832
+ return;
1833
+ }
1834
+
1835
+ if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup') {
1836
+ echo '<div class="updated fade" style="max-width: 800px; font-size:140%; padding:14px; clear:left;"><strong>Schedule backup:</strong> ';
1837
+ if (wp_schedule_single_event(time()+5, 'updraft_backup_all') === false) {
1838
+ echo "Failed.";
1839
+ } else {
1840
+ echo "OK. Now load a page from your site to make sure the schedule can trigger.";
1841
+ }
1842
+ echo '</div>';
1843
+ }
1844
+ if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup_debug_all') {
1845
+ $this->backup(true,true);
1846
+ }
1847
+ if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup_debug_db') {
1848
+ $this->backup_db();
1849
+ }
1850
+
1851
+ ?>
1852
+ <div class="wrap">
1853
+ <h1>UpdraftPlus - Backup/Restore</h1>
1854
+
1855
+ <!-- Version: <b><?php echo $this->version; ?></b><br>-->
1856
+ Maintained by <b>David Anderson</b> (<a href="http://david.dw-perspective.org.uk">Homepage</a> | <a href="http://wordshell.net">WordShell - WordPress command line</a> | <a href="http://david.dw-perspective.org.uk/donate">Donate</a> | <a href="http://wordpress.org/extend/plugins/updraftplus/faq/">FAQs</a> | <a href="http://profiles.wordpress.org/davidanderson/">My other WordPress plugins</a>). Version: <?php echo $this->version; ?>
1857
+ <br>
1858
+ <?php
1859
+ if(isset($_GET['updraft_restore_success'])) {
1860
+ echo "<div style=\"color:blue\">Your backup has been restored. Your old themes, uploads, and plugins directories have been retained with \"-old\" appended to their name. Remove them when you are satisfied that the backup worked properly. At this time Updraft does not automatically restore your DB. You will need to use an external tool like phpMyAdmin to perform that task.</div>";
1861
+ }
1862
+
1863
+ $ws_advert = $this->wordshell_random_advert(1);
1864
+ echo <<<ENDHERE
1865
+ <div class="updated fade" style="max-width: 800px; font-size:140%; padding:14px; clear:left;">${ws_advert}</div>
1866
+ ENDHERE;
1867
+
1868
+
1869
+ if($deleted_old_dirs) {
1870
+ echo '<div style="color:blue">Old directories successfully deleted.</div>';
1871
+ }
1872
+ if(!$this->memory_check(96)) {?>
1873
+ <div style="color:orange">Your PHP memory limit is too low. Updraft attempted to raise it but was unsuccessful. This plugin may not work properly with a memory limit of less than 96 Mb (though on the other hand, it has been used successfully with a 32Mb limit - your mileage may vary, but don't blame us!). Current limit is: <?php echo $this->memory_check_current(); ?> Mb</div>
1874
+ <?php
1875
+ }
1876
+ if(!$this->execution_time_check(300)) {?>
1877
+ <div style="color:orange">Your PHP max_execution_time is less than 300 seconds. This probably means you're running in safe_mode. Either disable safe_mode or modify your php.ini to set max_execution_time to a higher number. If you do not, there is a chance Updraft will be unable to complete a backup. Present limit is: <?php echo ini_get('max_execution_time'); ?> seconds.</div>
1878
+ <?php
1879
+ }
1880
+
1881
+ if($this->scan_old_dirs()) {?>
1882
+ <div style="color:orange">You have old directories from a previous backup. Click to delete them after you have verified that the restoration worked.</div>
1883
+ <form method="post" action="<?php echo remove_query_arg(array('updraft_restore_success','action')) ?>">
1884
+ <input type="hidden" name="action" value="updraft_delete_old_dirs" />
1885
+ <input type="submit" class="button-primary" value="Delete Old Dirs" onclick="return(confirm('Are you sure you want to delete the old directories? This cannot be undone.'))" />
1886
+ </form>
1887
+ <?php
1888
+ }
1889
+ if(!empty($this->errors)) {
1890
+ foreach($this->errors as $error) {
1891
+ //ignoring severity here right now
1892
+ echo '<div style="color:red">'.$error['error'].'</div>';
1893
+ }
1894
+ }
1895
+ ?>
1896
+
1897
+ <h2 style="clear:left;">Existing Schedule And Backups</h2>
1898
+ <table class="form-table" style="float:left; clear: both; width:475px">
1899
+ <tr>
1900
+ <?php
1901
+ $next_scheduled_backup = wp_next_scheduled('updraft_backup');
1902
+ $next_scheduled_backup = ($next_scheduled_backup) ? date('D, F j, Y H:i T',$next_scheduled_backup) : 'No backups are scheduled at this time.';
1903
+ $next_scheduled_backup_database = wp_next_scheduled('updraft_backup_database');
1904
+ if (get_option('updraft_interval_database',get_option('updraft_interval')) == get_option('updraft_interval')) {
1905
+ $next_scheduled_backup_database = "Will take place at the same time as the files backup.";
1906
+ } else {
1907
+ $next_scheduled_backup_database = ($next_scheduled_backup_database) ? date('D, F j, Y H:i T',$next_scheduled_backup_database) : 'No backups are scheduled at this time.';
1908
+ }
1909
+ $current_time = date('D, F j, Y H:i T',time());
1910
+ $updraft_last_backup = get_option('updraft_last_backup');
1911
+ if($updraft_last_backup) {
1912
+ if($updraft_last_backup['success']) {
1913
+ $last_backup = date('D, F j, Y H:i T',$updraft_last_backup['backup_time']);
1914
+ $last_backup_color = 'green';
1915
+ } else {
1916
+ $last_backup = print_r($updraft_last_backup['errors'],true);
1917
+ $last_backup_color = 'red';
1918
+ }
1919
+ } else {
1920
+ $last_backup = 'No backup has been completed.';
1921
+ $last_backup_color = 'blue';
1922
+ }
1923
+
1924
+ $updraft_dir = $this->backups_dir_location();
1925
+ if(is_writable($updraft_dir)) {
1926
+ $dir_info = '<span style="color:green">Backup directory specified is writable, which is good.</span>';
1927
+ $backup_disabled = "";
1928
+ } else {
1929
+ $backup_disabled = 'disabled="disabled"';
1930
+ $dir_info = '<span style="color:red">Backup directory specified is <b>not</b> writable, or does not exist. <span style="font-size:110%;font-weight:bold"><a href="options-general.php?page=updraftplus&action=updraft_create_backup_dir">Click here</a></span> to attempt to create the directory and set the permissions. If that is unsuccessful check the permissions on your server or change it to another directory that is writable by your web server process.</span>';
1931
+ }
1932
+ ?>
1933
+
1934
+ <th>The Time Now:</th>
1935
+ <td style="color:blue"><?php echo $current_time?></td>
1936
+ </tr>
1937
+ <tr>
1938
+ <th>Next Scheduled Files Backup:</th>
1939
+ <td style="color:blue"><?php echo $next_scheduled_backup?></td>
1940
+ </tr>
1941
+ <tr>
1942
+ <th>Next Scheduled DB Backup:</th>
1943
+ <td style="color:blue"><?php echo $next_scheduled_backup_database?></td>
1944
+ </tr>
1945
+ <tr>
1946
+ <th>Last Backup:</th>
1947
+ <td style="color:<?php echo $last_backup_color ?>"><?php echo $last_backup?></td>
1948
+ </tr>
1949
+ </table>
1950
+ <div style="float:left; width:200px; padding-top: 100px;">
1951
+ <form method="post" action="">
1952
+ <input type="hidden" name="action" value="updraft_backup" />
1953
+ <p><input type="submit" <?php echo $backup_disabled ?> class="button-primary" value="Backup Now!" style="padding-top:7px;padding-bottom:7px;font-size:24px !important" onclick="return(confirm('This will schedule a one-time backup. To trigger the backup immediately you may need to load a page on your site.'))" /></p>
1954
+ </form>
1955
+ <div style="position:relative">
1956
+ <div style="position:absolute;top:0;left:0">
1957
+ <?php
1958
+ $backup_history = get_option('updraft_backup_history');
1959
+ $backup_history = (is_array($backup_history))?$backup_history:array();
1960
+ $restore_disabled = (count($backup_history) == 0) ? 'disabled="disabled"' : "";
1961
+ ?>
1962
+ <input type="button" class="button-primary" <?php echo $restore_disabled ?> value="Restore" style="padding-top:7px;padding-bottom:7px;font-size:24px !important" onclick="jQuery('#backup-restore').fadeIn('slow');jQuery(this).parent().fadeOut('slow')" />
1963
+ </div>
1964
+ <div style="display:none;position:absolute;top:0;left:0" id="backup-restore">
1965
+ <form method="post" action="">
1966
+ <b>Choose: </b>
1967
+ <select name="backup_timestamp" style="display:inline">
1968
+ <?php
1969
+ foreach($backup_history as $key=>$value) {
1970
+ echo "<option value='$key'>".date('Y-m-d G:i',$key)."</option>\n";
1971
+ }
1972
+ ?>
1973
+ </select>
1974
+
1975
+ <input type="hidden" name="action" value="updraft_restore" />
1976
+ <input type="submit" <?php echo $restore_disabled ?> class="button-primary" value="Restore Now!" style="padding-top:7px;margin-top:5px;padding-bottom:7px;font-size:24px !important" onclick="return(confirm('Restoring from backup will replace this site\'s themes, plugins, uploads and other content directories (according to what is contained in the backup set which you select). Database restoration cannot be done through this process - you must download the database and import yourself (e.g. through PHPMyAdmin). Do you wish to continue with the restoration process?'))" />
1977
+ </form>
1978
+ </div>
1979
+ </div>
1980
+ </div>
1981
+ <br style="clear:both" />
1982
+ <table class="form-table">
1983
+ <tr>
1984
+ <th>Download Backups</th>
1985
+ <td><a href="#" title="Click to see available backups" onclick="jQuery('.download-backups').toggle();return false;"><?php echo count($backup_history)?> available</a></td>
1986
+ </tr>
1987
+ <tr>
1988
+ <td></td><td class="download-backups" style="display:none">
1989
+ <em>Click on a button to download the corresponding file to your computer. If you are using Opera, you should turn Turbo mode off.</em>
1990
+ <table>
1991
+ <?php
1992
+ foreach($backup_history as $key=>$value) {
1993
+ ?>
1994
+ <tr>
1995
+ <td><b><?php echo date('Y-m-d G:i',$key)?></b></td>
1996
+ <td>
1997
+ <?php if (isset($value['db'])) { ?>
1998
+ <form action="admin-ajax.php" method="post">
1999
+ <input type="hidden" name="action" value="updraft_download_backup" />
2000
+ <input type="hidden" name="type" value="db" />
2001
+ <input type="hidden" name="timestamp" value="<?php echo $key?>" />
2002
+ <input type="submit" value="Database" />
2003
+ </form>
2004
+ <?php } else { echo "(No database in backup)"; } ?>
2005
+ </td>
2006
+ <td>
2007
+ <?php if (isset($value['plugins'])) { ?>
2008
+ <form action="admin-ajax.php" method="post">
2009
+ <input type="hidden" name="action" value="updraft_download_backup" />
2010
+ <input type="hidden" name="type" value="plugins" />
2011
+ <input type="hidden" name="timestamp" value="<?php echo $key?>" />
2012
+ <input type="submit" value="Plugins" />
2013
+ </form>
2014
+ <?php } else { echo "(No plugins in backup)"; } ?>
2015
+ </td>
2016
+ <td>
2017
+ <?php if (isset($value['themes'])) { ?>
2018
+ <form action="admin-ajax.php" method="post">
2019
+ <input type="hidden" name="action" value="updraft_download_backup" />
2020
+ <input type="hidden" name="type" value="themes" />
2021
+ <input type="hidden" name="timestamp" value="<?php echo $key?>" />
2022
+ <input type="submit" value="Themes" />
2023
+ </form>
2024
+ <?php } else { echo "(No themes in backup)"; } ?>
2025
+ </td>
2026
+ <td>
2027
+ <?php if (isset($value['uploads'])) { ?>
2028
+ <form action="admin-ajax.php" method="post">
2029
+ <input type="hidden" name="action" value="updraft_download_backup" />
2030
+ <input type="hidden" name="type" value="uploads" />
2031
+ <input type="hidden" name="timestamp" value="<?php echo $key?>" />
2032
+ <input type="submit" value="Uploads" />
2033
+ </form>
2034
+ <?php } else { echo "(No uploads in backup)"; } ?>
2035
+ </td>
2036
+ <td>
2037
+ <?php if (isset($value['others'])) { ?>
2038
+ <form action="admin-ajax.php" method="post">
2039
+ <input type="hidden" name="action" value="updraft_download_backup" />
2040
+ <input type="hidden" name="type" value="others" />
2041
+ <input type="hidden" name="timestamp" value="<?php echo $key?>" />
2042
+ <input type="submit" value="Others" />
2043
+ </form>
2044
+ <?php } else { echo "(No others in backup)"; } ?>
2045
+ </td>
2046
+ </tr>
2047
+ <?php }?>
2048
+ </table>
2049
+ </td>
2050
+ </tr>
2051
+ </table>
2052
+ <form method="post" action="options.php">
2053
+ <?php settings_fields('updraft-options-group'); ?>
2054
+ <h2>Configure Backup Contents And Schedule</h2>
2055
+ <table class="form-table" style="width:850px;">
2056
+ <tr>
2057
+ <th>File Backup Intervals:</th>
2058
+ <td><select name="updraft_interval">
2059
+ <?php
2060
+ $intervals = array ("manual", "daily", "weekly", "monthly");
2061
+ foreach ($intervals as $ival) {
2062
+ echo "<option value=\"$ival\" ";
2063
+ if ($ival == get_option('updraft_interval','manual')) { echo 'selected="selected"';}
2064
+ echo ">".ucfirst($ival)."</option>\n";
2065
+ }
2066
+ ?>
2067
+ </select></td>
2068
+ </tr>
2069
+ <tr>
2070
+ <th>Database Backup Intervals:</th>
2071
+ <td><select name="updraft_interval_database">
2072
+ <?php
2073
+ $intervals = array ("manual", "daily", "weekly", "monthly");
2074
+ foreach ($intervals as $ival) {
2075
+ echo "<option value=\"$ival\" ";
2076
+ if ($ival == get_option('updraft_interval_database',get_option('updraft_interval'))) { echo 'selected="selected"';}
2077
+ echo ">".ucfirst($ival)."</option>\n";
2078
+ }
2079
+ ?>
2080
+ </select></td>
2081
+ </tr>
2082
+ <tr class="backup-interval-description">
2083
+ <td></td><td>If you would like to automatically schedule backups, choose schedules from the dropdown above. Backups will occur at the interval specified starting just after the current time. If you choose manual you must click the &quot;Backup Now!&quot; button whenever you wish a backup to occur. If the two schedules are the same, then the two backups will take place together.</td>
2084
+ </tr>
2085
+ <?php
2086
+ # The true (default value if non-existent) here has the effect of forcing a default of on.
2087
+ $include_themes = (get_option('updraft_include_themes',true)) ? 'checked="checked"' : "";
2088
+ $include_plugins = (get_option('updraft_include_plugins',true)) ? 'checked="checked"' : "";
2089
+ $include_uploads = (get_option('updraft_include_uploads',true)) ? 'checked="checked"' : "";
2090
+ $include_others = (get_option('updraft_include_others',true)) ? 'checked="checked"' : "";
2091
+ $include_others_exclude = get_option('updraft_include_others_exclude',UPDRAFT_DEFAULT_OTHERS_EXCLUDE);
2092
+ ?>
2093
+ <tr>
2094
+ <th>Include in Files Backup:</th>
2095
+ <td>
2096
+ <input type="checkbox" name="updraft_include_plugins" value="1" <?php echo $include_plugins; ?> /> Plugins<br>
2097
+ <input type="checkbox" name="updraft_include_themes" value="1" <?php echo $include_themes; ?> /> Themes<br>
2098
+ <input type="checkbox" name="updraft_include_uploads" value="1" <?php echo $include_uploads; ?> /> Uploads<br>
2099
+ <input type="checkbox" name="updraft_include_others" value="1" <?php echo $include_others; ?> /> Any other directories found inside wp-content - but exclude these directories: <input type="text" name="updraft_include_others_exclude" size="32" value="<?php echo htmlspecialchars($include_others_exclude); ?>"/><br>
2100
+ Include all of these, unless you are backing them up separately. Note that presently UpdraftPlus backs up these directories only - which is usually everything (except for WordPress core itself which you can download afresh from WordPress.org). But if you have made customised modifications outside of these directories, you need to back them up another way.<br>(<a href="http://wordshell.net">Use WordShell</a> for automatic backup, version control and patching).<br></td>
2101
+ </td>
2102
+ </tr>
2103
+ <tr>
2104
+ <th>Retain Backups:</th>
2105
+ <?php
2106
+ $updraft_retain = get_option('updraft_retain');
2107
+ $retain = ((int)$updraft_retain > 0)?get_option('updraft_retain'):1;
2108
+ ?>
2109
+ <td><input type="text" name="updraft_retain" value="<?php echo $retain ?>" style="width:50px" /></td>
2110
+ </tr>
2111
+ <tr class="email" <?php echo $email_display?>>
2112
+ <th>Email:</th>
2113
+ <td><input type="text" style="width:260px" name="updraft_email" value="<?php echo get_option('updraft_email'); ?>" /> <br>Enter an address here to have a report sent (and the whole backup, if you choose) to it.</td>
2114
+ </tr>
2115
+ <tr class="deletelocal s3 ftp email" <?php echo $display_delete_local?>>
2116
+ <th>Delete local backup:</th>
2117
+ <td><input type="checkbox" name="updraft_delete_local" value="1" <?php $delete_local = (get_option('updraft_delete_local')) ? 'checked="checked"' : "";
2118
+ echo $delete_local; ?> /> <br>Check this to delete the local backup file (only sensible if you have enabled a remote backup (below), otherwise you will have no backup remaining).</td>
2119
+ </tr>
2120
+
2121
+ <tr class="backup-retain-description">
2122
+ <td></td><td>By default only the most recent backup is retained. If you'd like to preserve more, specify the number here. (This many of <strong>both</strong> files and database backups will be retained.)</td>
2123
+ </tr>
2124
+ <tr>
2125
+ <th>Database encryption phrase:</th>
2126
+ <?php
2127
+ $updraft_encryptionphrase = get_option('updraft_encryptionphrase');
2128
+ ?>
2129
+ <td><input type="text" name="updraft_encryptionphrase" value="<?php echo $updraft_encryptionphrase ?>" style="width:132px" /></td>
2130
+ </tr>
2131
+ <tr class="backup-crypt-description">
2132
+ <td></td><td>If you enter a string here, it is used to encrypt backups (Rijndael). Do not lose it, or all your backups will be useless. Presently, only the database file is encrypted. This is also the key used to decrypt backups from this admin interface (so if you change it, then automatic decryption will not work until you change it back). You can also use the file example-decrypt.php from inside the UpdraftPlus plugin directory to decrypt manually.</td>
2133
+ </tr>
2134
+ </table>
2135
+
2136
+ <h2>Copying Your Backup To Remote Storage</h2>
2137
+
2138
+ <table class="form-table" style="width:850px;">
2139
+ <tr>
2140
+ <th>Remote backup:</th>
2141
+ <td><select name="updraft_service" id="updraft-service">
2142
+ <?php
2143
+ $debug_mode = (get_option('updraft_debug_mode')) ? 'checked="checked"' : "";
2144
+
2145
+ $display_none = 'style="display:none"';
2146
+ $s3 = ""; $ftp = ""; $email = ""; $googledrive="";
2147
+ $email_display="";
2148
+ $display_email_complete = "";
2149
+ $set = 'selected="selected"';
2150
+ switch(get_option('updraft_service')) {
2151
+ case 's3':
2152
+ $s3 = $set;
2153
+ $googledrive_display = $display_none;
2154
+ $ftp_display = $display_none;
2155
+ break;
2156
+ case 'googledrive':
2157
+ $googledrive = $set;
2158
+ $s3_display = $display_none;
2159
+ $ftp_display = $display_none;
2160
+ break;
2161
+ case 'ftp':
2162
+ $ftp = $set;
2163
+ $googledrive_display = $display_none;
2164
+ $s3_display = $display_none;
2165
+ break;
2166
+ case 'email':
2167
+ $email = $set;
2168
+ $ftp_display = $display_none;
2169
+ $s3_display = $display_none;
2170
+ $googledrive_display = $display_none;
2171
+ $display_email_complete = $display_none;
2172
+ break;
2173
+ default:
2174
+ $none = $set;
2175
+ $ftp_display = $display_none;
2176
+ $googledrive_display = $display_none;
2177
+ $s3_display = $display_none;
2178
+ $display_delete_local = $display_none;
2179
+ break;
2180
+ }
2181
+ ?>
2182
+ <option value="none" <?php echo $none?>>None</option>
2183
+ <option value="s3" <?php echo $s3?>>Amazon S3</option>
2184
+ <option value="googledrive" <?php echo $googledrive?>>Google Drive</option>
2185
+ <option value="ftp" <?php echo $ftp?>>FTP</option>
2186
+ <option value="email" <?php echo $email?>>E-mail</option>
2187
+ </select></td>
2188
+ </tr>
2189
+ <tr class="backup-service-description">
2190
+ <td></td><td>Choose your backup method. If choosing &quot;E-Mail&quot;, then be aware that mail servers tend to have size limits; typically around 10-20Mb; backups larger than any limits will not arrive.</td>
2191
+
2192
+ </tr>
2193
+
2194
+ <!-- Amazon S3 -->
2195
+ <tr class="s3" <?php echo $s3_display?>>
2196
+ <td></td>
2197
+ <td><em>Amazon S3 is a great choice, because UpdraftPlus supports chunked uploads - no matter how big your blog is, UpdraftPlus can upload it a little at a time, and not get thwarted by timeouts.</em></td>
2198
+ </tr>
2199
+ <tr class="s3" <?php echo $s3_display?>>
2200
+ <th>S3 access key:</th>
2201
+ <td><input type="text" autocomplete="off" style="width:292px" name="updraft_s3_login" value="<?php echo get_option('updraft_s3_login') ?>" /></td>
2202
+ </tr>
2203
+ <tr class="s3" <?php echo $s3_display?>>
2204
+ <th>S3 secret key:</th>
2205
+ <td><input type="text" autocomplete="off" style="width:292px" name="updraft_s3_pass" value="<?php echo get_option('updraft_s3_pass'); ?>" /></td>
2206
+ </tr>
2207
+ <tr class="s3" <?php echo $s3_display?>>
2208
+ <th>S3 location:</th>
2209
+ <td>s3://<input type="text" style="width:292px" name="updraft_s3_remote_path" value="<?php echo get_option('updraft_s3_remote_path'); ?>" /></td>
2210
+ </tr>
2211
+ <tr class="s3" <?php echo $s3_display?>>
2212
+ <th></th>
2213
+ <td><p>Get your access key and secret key from your AWS page, then pick a (globally unique) bucket name (letters and numbers) (and optionally a path) to use for storage.</p></td>
2214
+ </tr>
2215
+
2216
+ <!-- Google Drive -->
2217
+
2218
+ <tr class="googledrive" <?php echo $googledrive_display?>>
2219
+ <th>Google Drive:</th>
2220
+ <td>
2221
+ <p><a href="http://david.dw-perspective.org.uk/da/index.php/computer-resources/updraftplus-googledrive-authorisation/"><strong>For longer help, including screenshots, follow this link. The description below is sufficient for more expert users.</strong></a></p>
2222
+ <p><a href="https://code.google.com/apis/console/">Follow this link to your Google API Console</a>, and there create a Client ID in the API Access section. Select 'Web Application' as the application type.</p><p>You must add <kbd><?php echo admin_url('options-general.php?page=updraftplus&action=auth'); ?></kbd> as the authorised redirect URI when asked.
2223
+
2224
+ <?php
2225
+ if (!class_exists('SimpleXMLElement')) { echo " <b>WARNING:</b> You do not have the SimpleXMLElement installed. Google Drive backups will <b>not</b> work until you do."; }
2226
+ ?>
2227
+ </p>
2228
+ </td>
2229
+ </tr>
2230
+
2231
+ <tr class="googledrive" <?php echo $googledrive_display?>>
2232
+ <th>Google Drive Client ID:</th>
2233
+ <td><input type="text" autocomplete="off" style="width:332px" name="updraft_googledrive_clientid" value="<?php echo get_option('updraft_googledrive_clientid') ?>" /></td>
2234
+ </tr>
2235
+ <tr class="googledrive" <?php echo $googledrive_display?>>
2236
+ <th>Google Drive Client Secret:</th>
2237
+ <td><input type="text" style="width:332px" name="updraft_googledrive_secret" value="<?php echo get_option('updraft_googledrive_secret'); ?>" /></td>
2238
+ </tr>
2239
+ <tr class="googledrive" <?php echo $googledrive_display?>>
2240
+ <th>Google Drive Folder ID:</th>
2241
+ <td><input type="text" style="width:332px" name="updraft_googledrive_remotepath" value="<?php echo get_option('updraft_googledrive_remotepath'); ?>" /> <em>(To get a folder's ID navigate to that folder in Google Drive in your web browser and copy the ID from your browser's address bar. It is the part that comes after <kbd>#folders/.</kbd> Leave empty to use your root folder)</em></td>
2242
+ </tr>
2243
+ <tr class="googledrive" <?php echo $googledrive_display?>>
2244
+ <th>Authenticate with Google:</th>
2245
+ <td><p><?php if (get_option('updraft_googledrive_token','xyz') != 'xyz') echo "<strong>(You appear to be already authenticated).</strong>"; ?> <a href="?page=updraftplus&action=auth&updraftplus_googleauth=doit"><strong>After</strong> you have saved your settings (by clicking &quot;Save Changes&quot; below), then come back here once and click this link to complete authentication with Google.</a>
2246
+
2247
+ </p>
2248
+ </td>
2249
+ </tr>
2250
+
2251
+ <tr class="ftp" <?php echo $ftp_display?>>
2252
+ <th><a href="#" title="Click for help!" onclick="jQuery('.ftp-description').toggle();return false;">FTP Server:</a></th>
2253
+ <td><input type="text" style="width:260px" name="updraft_server_address" value="<?php echo get_option('updraft_server_address'); ?>" /></td>
2254
+ </tr>
2255
+ <tr class="ftp" <?php echo $ftp_display?>>
2256
+ <th><a href="#" title="Click for help!" onclick="jQuery('.ftp-description').toggle();return false;">FTP Login:</a></th>
2257
+ <td><input type="text" autocomplete="off" name="updraft_ftp_login" value="<?php echo get_option('updraft_ftp_login') ?>" /></td>
2258
+ </tr>
2259
+ <tr class="ftp" <?php echo $ftp_display?>>
2260
+ <th><a href="#" title="Click for help!" onclick="jQuery('.ftp-description').toggle();return false;">FTP Password:</a></th>
2261
+ <td><input type="text" autocomplete="off" style="width:260px" name="updraft_ftp_pass" value="<?php echo get_option('updraft_ftp_pass'); ?>" /></td>
2262
+ </tr>
2263
+ <tr class="ftp" <?php echo $ftp_display?>>
2264
+ <th><a href="#" title="Click for help!" onclick="jQuery('.ftp-description').toggle();return false;">Remote Path:</a></th>
2265
+ <td><input type="text" style="width:260px" name="updraft_ftp_remote_path" value="<?php echo get_option('updraft_ftp_remote_path'); ?>" /></td>
2266
+ </tr>
2267
+ <tr class="ftp-description" style="display:none">
2268
+ <td colspan="2">An FTP remote path will look like '/home/backup/some/folder'</td>
2269
+ </tr>
2270
+ </table>
2271
+ <table class="form-table" style="width:850px;">
2272
+ <tr><td colspan="2"><h2>Advanced / Debugging Settings</h2></td></tr>
2273
+ <tr>
2274
+ <th>Backup Directory:</th>
2275
+ <td><input type="text" name="updraft_dir" style="width:525px" value="<?php echo $updraft_dir ?>" /></td>
2276
+ </tr>
2277
+ <tr>
2278
+ <td></td><td><?php echo $dir_info ?> This is where Updraft Backup/Restore will write the zip files it creates initially. This directory must be writable by your web server. Typically you'll want to have it inside your wp-content folder (this is the default). <b>Do not</b> place it inside your uploads dir, as that will cause recursion issues (backups of backups of backups of...).</td>
2279
+ </tr>
2280
+ <tr>
2281
+ <th>Debug mode:</th>
2282
+ <td><input type="checkbox" name="updraft_debug_mode" value="1" <?php echo $debug_mode; ?> /> <br>Check this for more information, if something is going wrong. Will also drop a log file in your backup directory which you can examine.</td>
2283
+ </tr>
2284
+ <tr>
2285
+ <td>
2286
+ <input type="hidden" name="action" value="update" />
2287
+ <input type="submit" class="button-primary" value="Save Changes" />
2288
+ </td>
2289
+ </tr>
2290
+ </table>
2291
+ </form>
2292
+ <?php
2293
+ if(get_option('updraft_debug_mode')) {
2294
+ ?>
2295
+ <div style="padding-top: 40px;">
2296
+ <hr>
2297
+ <h3>Debug Information</h3>
2298
+ <?php
2299
+ $peak_memory_usage = memory_get_peak_usage(true)/1024/1024;
2300
+ $memory_usage = memory_get_usage(true)/1024/1024;
2301
+ echo 'Peak memory usage: '.$peak_memory_usage.' MB<br/>';
2302
+ echo 'Current memory usage: '.$memory_usage.' MB<br/>';
2303
+ echo 'PHP memory limit: '.ini_get('memory_limit').' <br/>';
2304
+ ?>
2305
+ <form method="post" action="">
2306
+ <input type="hidden" name="action" value="updraft_backup_debug_all" />
2307
+ <p><input type="submit" class="button-primary" <?php echo $backup_disabled ?> value="Debug Backup" onclick="return(confirm('This will cause an immediate backup. The page will stall loading until it finishes (ie, unscheduled). Use this if you\'re trying to see peak memory usage.'))" /></p>
2308
+ </form>
2309
+ <form method="post" action="">
2310
+ <input type="hidden" name="action" value="updraft_backup_debug_db" />
2311
+ <p><input type="submit" class="button-primary" <?php echo $backup_disabled ?> value="Debug DB Backup" onclick="return(confirm('This will cause an immediate DB backup. The page will stall loading until it finishes (ie, unscheduled). The backup will remain locally despite your prefs and will not go into the backup history or up into the cloud.'))" /></p>
2312
+ </form>
2313
+ </div>
2314
+ <?php } ?>
2315
+
2316
+ <p><em>UpdraftPlus is based on the original Updraft by <b>Paul Kehrer</b> (<a href="http://langui.sh" target="_blank">Blog</a> | <a href="http://twitter.com/reaperhulk" target="_blank">Twitter</a> )</em></p>
2317
+
2318
+
2319
+ <script type="text/javascript">
2320
+ jQuery(document).ready(function() {
2321
+ jQuery('#updraft-service').change(function() {
2322
+ switch(jQuery(this).val()) {
2323
+ case 'none':
2324
+ jQuery('.deletelocal,.s3,.ftp,.googledrive,.s3-description,.ftp-description').fadeOut()
2325
+ jQuery('.email,.email-complete').fadeIn()
2326
+ break;
2327
+ case 's3':
2328
+ jQuery('.ftp,.ftp-description,.googledrive').fadeOut()
2329
+ jQuery('.s3,.deletelocal,.email,.email-complete').fadeIn()
2330
+ break;
2331
+ case 'googledrive':
2332
+ jQuery('.ftp,.ftp-description,.s3').fadeOut()
2333
+ jQuery('.googledrive,.deletelocal,.googledrive,.email,.email-complete').fadeIn()
2334
+ break;
2335
+ case 'ftp':
2336
+ jQuery('.googledrive,.s3,.s3-description').fadeOut()
2337
+ jQuery('.ftp,.deletelocal,.email,.email-complete').fadeIn()
2338
+ break;
2339
+ case 'email':
2340
+ jQuery('.s3,.ftp,.s3-description,.googledrive,.ftp-description,.email-complete').fadeOut()
2341
+ jQuery('.email,.deletelocal').fadeIn()
2342
+ break;
2343
+ }
2344
+ })
2345
+ })
2346
+ jQuery(window).load(function() {
2347
+ //this is for hiding the restore progress at the top after it is done
2348
+ setTimeout('jQuery("#updraft-restore-progress").toggle(1000)',3000)
2349
+ jQuery('#updraft-restore-progress-toggle').click(function() {
2350
+ jQuery('#updraft-restore-progress').toggle(500)
2351
+ })
2352
+ })
2353
+ </script>
2354
+ <?php
2355
+ }
2356
+
2357
+ /*array2json provided by bin-co.com under BSD license*/
2358
+ function array2json($arr) {
2359
+ if(function_exists('json_encode')) return stripslashes(json_encode($arr)); //Latest versions of PHP already have this functionality.
2360
+ $parts = array();
2361
+ $is_list = false;
2362
+
2363
+ //Find out if the given array is a numerical array
2364
+ $keys = array_keys($arr);
2365
+ $max_length = count($arr)-1;
2366
+ if(($keys[0] == 0) and ($keys[$max_length] == $max_length)) {//See if the first key is 0 and last key is length - 1
2367
+ $is_list = true;
2368
+ for($i=0; $i<count($keys); $i++) { //See if each key correspondes to its position
2369
+ if($i != $keys[$i]) { //A key fails at position check.
2370
+ $is_list = false; //It is an associative array.
2371
+ break;
2372
+ }
2373
+ }
2374
+ }
2375
+
2376
+ foreach($arr as $key=>$value) {
2377
+ if(is_array($value)) { //Custom handling for arrays
2378
+ if($is_list) $parts[] = $this->array2json($value); /* :RECURSION: */
2379
+ else $parts[] = '"' . $key . '":' . $this->array2json($value); /* :RECURSION: */
2380
+ } else {
2381
+ $str = '';
2382
+ if(!$is_list) $str = '"' . $key . '":';
2383
+
2384
+ //Custom handling for multiple data types
2385
+ if(is_numeric($value)) $str .= $value; //Numbers
2386
+ elseif($value === false) $str .= 'false'; //The booleans
2387
+ elseif($value === true) $str .= 'true';
2388
+ else $str .= '"' . addslashes($value) . '"'; //All other things
2389
+ // :TODO: Is there any more datatype we should be in the lookout for? (Object?)
2390
+
2391
+ $parts[] = $str;
2392
+ }
2393
+ }
2394
+ $json = implode(',',$parts);
2395
+
2396
+ if($is_list) return '[' . $json . ']';//Return numerical JSON
2397
+ return '{' . $json . '}';//Return associative JSON
2398
+ }
2399
+
2400
+ function show_admin_warning($message) {
2401
+ echo '<div id="updraftmessage" class="updated fade">';
2402
+ echo "<p>$message</p></div>";
2403
+ }
2404
+ function show_admin_warning_accessible() {
2405
+ $this->show_admin_warning("UpdraftPlus backup directory specified is accessible via the web. This is a potential security problem (people may be able to download your backups - which is undesirable if your database is not encrypted and if you have non-public assets amongst the files). If using Apache, enable .htaccess support to allow web access to be denied; otherwise, you should deny access manually.");
2406
+ }
2407
+ function show_admin_warning_googledrive() {
2408
+ $this->show_admin_warning('<strong>UpdraftPlus notice:</strong> <a href="?page=updraftplus&action=auth&updraftplus_googleauth=doit">Click here to authenticate your Google Drive account (you will not be able to back up to Google Drive without it).</a>');
2409
+ }
2410
+ function show_admin_warning_accessible_unknownresult() {
2411
+ $this->show_admin_warning("UpdraftPlus tried to check if the backup directory is accessible via web, but the result was unknown.");
2412
+ }
2413
+
2414
+
2415
+ }
2416
+
2417
+
2418
+ ?>
updraftplus.php CHANGED
@@ -4,7 +4,7 @@ Plugin Name: UpdraftPlus - Backup/Restore
4
  Plugin URI: http://wordpress.org/extend/plugins/updraftplus
5
  Description: Uploads, themes, plugins, and your DB can be automatically backed up to Amazon S3, Google Drive, FTP, or emailed, on separate schedules.
6
  Author: David Anderson.
7
- Version: 1.0.6
8
  Donate link: http://david.dw-perspective.org.uk/donate
9
  License: GPLv3 or later
10
  Author URI: http://wordshell.net
@@ -60,7 +60,7 @@ define('UPDRAFT_DEFAULT_OTHERS_EXCLUDE','upgrade,cache,updraft,index.php');
60
 
61
  class UpdraftPlus {
62
 
63
- var $version = '1.0.6';
64
 
65
  var $dbhandle;
66
  var $errors = array();
@@ -202,7 +202,7 @@ class UpdraftPlus {
202
  $result = json_decode( $result, true );
203
  if ( isset( $result['refresh_token'] ) ) {
204
  update_option('updraft_googledrive_token',$result['refresh_token']); // Save token
205
- header('Location: '.admin_url('options-general.php?page=updraftplus&message=' . __( 'Authorization was successful.', 'updraftplus' ) ) );
206
  }
207
  else {
208
  header('Location: '.admin_url('options-general.php?page=updraftplus&error=' . __( 'No refresh token was received!', 'updraftplus' ) ) );
@@ -738,7 +738,7 @@ class UpdraftPlus {
738
  if ( is_wp_error( $this->gdocs_access_token ) ) return $access_token;
739
 
740
  $this->gdocs = new UpdraftPlus_GDocs( $this->gdocs_access_token );
741
- $this->gdocs->set_option( 'chunk_size', 1*1024*1024 ); # 1Mb; change from default of 512Kb
742
  $this->gdocs->set_option( 'request_timeout', 10 ); # Change from default of 10s
743
  $this->gdocs->set_option( 'max_resume_attempts', 36 ); # Doesn't look like GDocs class actually uses this anyway
744
  }
@@ -2404,7 +2404,7 @@ echo $delete_local; ?> /> <br>Check this to delete the local backup file (only s
2404
  $this->show_admin_warning("UpdraftPlus backup directory specified is accessible via the web. This is a potential security problem (people may be able to download your backups - which is undesirable if your database is not encrypted and if you have non-public assets amongst the files). If using Apache, enable .htaccess support to allow web access to be denied; otherwise, you should deny access manually.");
2405
  }
2406
  function show_admin_warning_googledrive() {
2407
- $this->show_admin_warning('UpdraftPlus notice: <a href="?page=updraftplus&action=auth&updraftplus_googleauth=doit">Click here to authenticate your Google Drive account (you will not be able to back up to Google Drive without it).</a>');
2408
  }
2409
  function show_admin_warning_accessible_unknownresult() {
2410
  $this->show_admin_warning("UpdraftPlus tried to check if the backup directory is accessible via web, but the result was unknown.");
4
  Plugin URI: http://wordpress.org/extend/plugins/updraftplus
5
  Description: Uploads, themes, plugins, and your DB can be automatically backed up to Amazon S3, Google Drive, FTP, or emailed, on separate schedules.
6
  Author: David Anderson.
7
+ Version: 1.0.7
8
  Donate link: http://david.dw-perspective.org.uk/donate
9
  License: GPLv3 or later
10
  Author URI: http://wordshell.net
60
 
61
  class UpdraftPlus {
62
 
63
+ var $version = '1.0.7';
64
 
65
  var $dbhandle;
66
  var $errors = array();
202
  $result = json_decode( $result, true );
203
  if ( isset( $result['refresh_token'] ) ) {
204
  update_option('updraft_googledrive_token',$result['refresh_token']); // Save token
205
+ header('Location: '.admin_url('options-general.php?page=updraftplus&message=' . __( 'Google Drive authorization was successful.', 'updraftplus' ) ) );
206
  }
207
  else {
208
  header('Location: '.admin_url('options-general.php?page=updraftplus&error=' . __( 'No refresh token was received!', 'updraftplus' ) ) );
738
  if ( is_wp_error( $this->gdocs_access_token ) ) return $access_token;
739
 
740
  $this->gdocs = new UpdraftPlus_GDocs( $this->gdocs_access_token );
741
+ $this->gdocs->set_option( 'chunk_size', 1 ); # 1Mb; change from default of 512Kb
742
  $this->gdocs->set_option( 'request_timeout', 10 ); # Change from default of 10s
743
  $this->gdocs->set_option( 'max_resume_attempts', 36 ); # Doesn't look like GDocs class actually uses this anyway
744
  }
2404
  $this->show_admin_warning("UpdraftPlus backup directory specified is accessible via the web. This is a potential security problem (people may be able to download your backups - which is undesirable if your database is not encrypted and if you have non-public assets amongst the files). If using Apache, enable .htaccess support to allow web access to be denied; otherwise, you should deny access manually.");
2405
  }
2406
  function show_admin_warning_googledrive() {
2407
+ $this->show_admin_warning('<strong>UpdraftPlus notice:</strong> <a href="?page=updraftplus&action=auth&updraftplus_googleauth=doit">Click here to authenticate your Google Drive account (you will not be able to back up to Google Drive without it).</a>');
2408
  }
2409
  function show_admin_warning_accessible_unknownresult() {
2410
  $this->show_admin_warning("UpdraftPlus tried to check if the backup directory is accessible via web, but the result was unknown.");